SymphonyElectron/js/preload/preloadMain.js
Vishwas Shashidhar cda34b1d70 Electron 24 - protocol handler (#85)
* added idea and coverage directories under gitignore

* electron-24: implemented handlers to process protocol actions

* electron-17: implemented use case for opening app if it is not open and handle the protocol url

* electron-24: added code and documentation comments

* electron-24: added unit tests for the protocol handler

* added npm-debug log to gitignore

* electron-24: added protocol handler support for windows

* electron-24: made changes as per comments on the PR

* electron-16: added more comments and further refactoring
2017-05-13 11:23:44 -07:00

294 lines
9.7 KiB
JavaScript

'use strict';
// script run before others and still has access to node integration, even
// when turned off - allows us to leak only what want into window object.
// see: http://electron.atom.io/docs/api/browser-window/
//
// to leak some node module into:
// https://medium.com/@leonli/securing-embedded-external-content-in-electron-node-js-8b6ef665cd8e#.fex4e68p7
// https://slack.engineering/building-hybrid-applications-with-electron-dc67686de5fb#.tp6zz1nrk
//
// also to bring pieces of node.js:
// https://github.com/electron/electron/issues/2984
//
const { ipcRenderer, remote } = require('electron');
const throttle = require('../utils/throttle.js');
const apiEnums = require('../enums/api.js');
const apiCmds = apiEnums.cmds;
const apiName = apiEnums.apiName;
const getMediaSources = require('../desktopCapturer/getSources');
// hold ref so doesn't get GC'ed
const local = {
ipcRenderer: ipcRenderer
};
// throttle calls to this func to at most once per sec, called on leading edge.
const throttledSetBadgeCount = throttle(1000, function(count) {
local.ipcRenderer.send(apiName, {
cmd: apiCmds.setBadgeCount,
count: count
});
});
// check to see if the app was opened via a url
const checkProtocolAction = function () {
local.ipcRenderer.send(apiName, {
cmd: apiCmds.checkProtocolAction
});
};
createAPI();
// creates API exposed from electron.
// wrapped in a function so we can abort early in function coming from an iframe
function createAPI() {
// iframes (and any other non-top level frames) get no api access
// http://stackoverflow.com/questions/326069/how-to-identify-if-a-webpage-is-being-loaded-inside-an-iframe-or-directly-into-t/326076
if (window.self !== window.top) {
return;
}
// note: window.open from main window (if in the same domain) will get
// api access. window.open in another domain will be opened in the default
// browser (see: handler for event 'new-window' in windowMgr.js)
//
// API exposed to renderer process.
//
window.ssf = {
// api version
version: '1.0.0',
/**
* sets the count on the tray icon to the given number.
* @param {number} count count to be displayed
* note: count of 0 will remove the displayed count.
* note: for mac the number displayed will be 1 to infinity
* note: for windws the number displayed will be 1 to 99 and 99+
*/
setBadgeCount: function(count) {
throttledSetBadgeCount(count);
},
/**
* checks to see if the app was opened from a url.
*/
checkProtocolAction: function () {
checkProtocolAction();
},
/**
* provides api similar to html5 Notification, see details
* in notify/notifyImpl.js
*/
Notification: remote.require('./notify/notifyImpl.js'),
/**
* provides api to allow user to capture portion of screen, see api
* details in screenSnipper/ScreenSnippet.js
*/
ScreenSnippet: remote.require('./screenSnippet/ScreenSnippet.js'),
/**
* Brings window forward and gives focus.
* @param {String} windowName Name of window. Note: main window name is 'main'
*/
activate: function(windowName) {
local.ipcRenderer.send(apiName, {
cmd: apiCmds.activate,
windowName: windowName
});
},
/**
* Allows JS to register a callback to be invoked when size/positions
* changes for any pop-out window (i.e., window.open). The main
* process will emit IPC event 'boundsChange' (see below). Currently
* only one window can register for bounds change.
* @param {Function} callback Function invoked when bounds changes.
*/
registerBoundsChange: function(callback) {
if (typeof callback === 'function') {
local.boundsChangeCallback = callback;
local.ipcRenderer.send(apiName, {
cmd: apiCmds.registerBoundsChange
});
}
},
/**
* allows JS to register a logger that can be used by electron main process.
* @param {Object} logger function that can be called accepting
* object: {
* logLevel: 'ERROR'|'CONFLICT'|'WARN'|'ACTION'|'INFO'|'DEBUG',
* logDetails: String
* }
*/
registerLogger: function(logger) {
if (typeof logger === 'function') {
local.logger = logger;
// only main window can register
local.ipcRenderer.send(apiName, {
cmd: apiCmds.registerLogger
});
}
},
/**
* allows JS to register a protocol handler that can be used by the electron main process.
* @param protocolHandler {Object} protocolHandler a callback to register the protocol handler
*/
registerProtocolHandler: function (protocolHandler) {
if (typeof protocolHandler === 'function') {
local.processProtocolAction = protocolHandler;
local.ipcRenderer.send(apiName, {
cmd: apiCmds.registerProtocolHandler
});
}
},
/**
* allows JS to register a activity detector that can be used by electron main process.
* @param {Object} activityDetection - function that can be called accepting
* @param {Object} period - minimum user idle time in millisecond
* object: {
* period: Number
* systemIdleTime: Number
* }
*/
registerActivityDetection: function(period, activityDetection) {
if (typeof activityDetection === 'function') {
local.activityDetection = activityDetection;
// only main window can register
local.ipcRenderer.send(apiName, {
cmd: apiCmds.registerActivityDetection,
period: period
});
}
},
/**
* Implements equivalent of desktopCapturer.getSources - that works in
* a sandboxed renderer process.
* see: https://electron.atom.io/docs/api/desktop-capturer/
* for interface: see documentation in desktopCapturer/getSources.js
*/
getMediaSources: getMediaSources
};
// add support for both ssf and SYM_API name-space.
window.SYM_API = window.ssf;
Object.freeze(window.ssf);
Object.freeze(window.SYM_API);
// listen for log message from main process
local.ipcRenderer.on('log', (event, arg) => {
if (local.logger && arg && arg.level && arg.details) {
local.logger(arg.level, arg.details);
}
});
// listen for notifications that some window size/position has changed
local.ipcRenderer.on('boundsChange', (event, arg) => {
if (local.boundsChangeCallback && arg.windowName &&
arg.x && arg.y && arg.width && arg.height) {
local.boundsChangeCallback({
x: arg.x,
y: arg.y,
width: arg.width,
height: arg.height,
windowName: arg.windowName
});
}
});
// listen for user activity from main process
local.ipcRenderer.on('activity', (event, arg) => {
if (local.activityDetection && arg && arg.systemIdleTime) {
local.activityDetection(arg.systemIdleTime);
}
});
/**
* Use render process to create badge count img and send back to main process.
* If number is greater than 99 then 99+ img is returned.
* note: with sandboxing turned on only get arg and no event passed in, so
* need to use ipcRenderer to callback to main process.
* @type {object} arg.count - number: count to be displayed
*/
local.ipcRenderer.on('createBadgeDataUrl', (event, arg) => {
const count = arg && arg.count || 0;
// create 32 x 32 img
let radius = 16;
let canvas = document.createElement('canvas');
canvas.height = radius * 2;
canvas.width = radius * 2;
let ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(radius, radius, radius, 0, 2 * Math.PI, false);
ctx.fill();
ctx.textAlign = 'center';
ctx.fillStyle = 'white';
let text = count > 99 ? '99+' : count.toString();
if (text.length > 2) {
ctx.font = 'bold 18px sans-serif';
ctx.fillText(text, radius, 22);
} else if (text.length > 1) {
ctx.font = 'bold 24px sans-serif';
ctx.fillText(text, radius, 24);
} else {
ctx.font = 'bold 26px sans-serif';
ctx.fillText(text, radius, 26);
}
let dataUrl = canvas.toDataURL('image/png', 1.0);
local.ipcRenderer.send(apiName, {
cmd: apiCmds.badgeDataUrl,
dataUrl: dataUrl,
count: count
});
});
/**
* an event triggered by the main process for processing protocol urls
* @type {String} arg - the protocol url
*/
local.ipcRenderer.on('protocol-action', (event, arg) => {
if (local.processProtocolAction && arg) {
local.processProtocolAction(arg);
}
});
function updateOnlineStatus() {
local.ipcRenderer.send(apiName, {
cmd: apiCmds.isOnline,
isOnline: window.navigator.onLine
});
}
window.addEventListener('offline', updateOnlineStatus, false);
window.addEventListener('online', updateOnlineStatus, false);
updateOnlineStatus();
}