mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-01-05 13:45:25 -06:00
583 lines
19 KiB
JavaScript
583 lines
19 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, crashReporter } = 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');
|
|
const getMediaSource = require('../desktopCapturer/getSource');
|
|
const { TitleBar } = require('../windowsTitlebar');
|
|
const titleBar = new TitleBar();
|
|
const { buildNumber } = require('../../package.json');
|
|
const SnackBar = require('../snackBar').SnackBar;
|
|
const KeyCodes = {
|
|
Esc: 27,
|
|
Alt: 18,
|
|
};
|
|
|
|
let Search;
|
|
let SearchUtils;
|
|
let CryptoLib;
|
|
let spellChecker;
|
|
let isAltKey = false;
|
|
let isMenuOpen = false;
|
|
|
|
try {
|
|
Search = remote.require('swift-search').Search;
|
|
} catch (e) {
|
|
// eslint-disable-next-line no-console
|
|
console.warn("Failed to initialize swift search. You'll need to include the search dependency. Contact the developers for more details");
|
|
}
|
|
|
|
try {
|
|
SearchUtils = remote.require('swift-search').SearchUtils;
|
|
} catch (e) {
|
|
// eslint-disable-next-line no-console
|
|
console.warn("Failed to initialize swift search (Utils). You'll need to include the search dependency. Contact the developers for more details");
|
|
}
|
|
|
|
try {
|
|
CryptoLib = remote.require('./cryptoLib.js');
|
|
} catch (e) {
|
|
CryptoLib = null;
|
|
// eslint-disable-next-line no-console
|
|
console.warn("Failed to initialize Crypto Lib. You'll need to include the Crypto library. Contact the developers for more details");
|
|
}
|
|
|
|
require('../downloadManager');
|
|
let snackBar;
|
|
|
|
/**
|
|
* Loads up the spell checker module
|
|
*/
|
|
function loadSpellChecker() {
|
|
try {
|
|
/* eslint-disable global-require */
|
|
const SpellCheckerHelper = require('../spellChecker').SpellCheckHelper;
|
|
/* eslint-enable global-require */
|
|
// Method to initialize spell checker
|
|
spellChecker = new SpellCheckerHelper();
|
|
spellChecker.initializeSpellChecker();
|
|
} catch (err) {
|
|
/* eslint-disable no-console */
|
|
console.error('unable to load the spell checker module, hence, skipping the spell check feature ' + err);
|
|
/* eslint-enable no-console */
|
|
}
|
|
}
|
|
|
|
// 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
|
|
});
|
|
});
|
|
|
|
const throttledSetIsInMeetingStatus = throttle(1000, function (isInMeeting) {
|
|
local.ipcRenderer.send(apiName, {
|
|
cmd: apiCmds.setIsInMeeting,
|
|
isInMeeting
|
|
});
|
|
});
|
|
|
|
/**
|
|
* an event triggered by the main process onload event
|
|
*/
|
|
local.ipcRenderer.on('on-page-load', () => {
|
|
loadSpellChecker();
|
|
snackBar = new SnackBar();
|
|
|
|
// only registers main window's preload
|
|
if (window.name === 'main') {
|
|
local.ipcRenderer.send(apiName, {
|
|
cmd: apiCmds.optimizeMemoryRegister,
|
|
});
|
|
}
|
|
});
|
|
|
|
const throttledActivate = throttle(1000, function (windowName) {
|
|
local.ipcRenderer.send(apiName, {
|
|
cmd: apiCmds.activate,
|
|
windowName: windowName
|
|
});
|
|
});
|
|
|
|
const throttledBringToFront = throttle(1000, function (windowName, reason) {
|
|
local.ipcRenderer.send(apiName, {
|
|
cmd: apiCmds.bringToFront,
|
|
windowName: windowName,
|
|
reason: reason
|
|
});
|
|
});
|
|
|
|
const throttledSetLocale = throttle(1000, function (locale) {
|
|
local.ipcRenderer.send(apiName, {
|
|
cmd: apiCmds.setLocale,
|
|
locale,
|
|
});
|
|
});
|
|
|
|
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 = {
|
|
getVersionInfo: function () {
|
|
return new Promise(function (resolve) {
|
|
let appName = remote.app.getName();
|
|
let appVer = remote.app.getVersion();
|
|
|
|
const verInfo = {
|
|
containerIdentifier: appName,
|
|
containerVer: appVer,
|
|
buildNumber: buildNumber,
|
|
apiVer: '2.0.0',
|
|
searchApiVer: '3.0.0'
|
|
};
|
|
resolve(verInfo);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 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);
|
|
},
|
|
|
|
/**
|
|
* 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/index.js
|
|
*/
|
|
ScreenSnippet: remote.require('./screenSnippet/index.js').ScreenSnippet,
|
|
|
|
/**
|
|
* Provides api for client side searching
|
|
* using the SymphonySearchEngine library
|
|
* details in ./search/search.js & ./search/searchLibrary.js
|
|
*/
|
|
Search: Search || null,
|
|
|
|
/**
|
|
* Provides api for search module utils
|
|
* like checking free space / search user config data to the client app
|
|
* details in ./search/searchUtils.js & ./search/searchConfig.js
|
|
*/
|
|
SearchUtils: SearchUtils || null,
|
|
|
|
/**
|
|
* Native encryption and decryption.
|
|
*/
|
|
CryptoLib: CryptoLib,
|
|
|
|
/**
|
|
* Brings window forward and gives focus.
|
|
* @param {String} windowName Name of window. Note: main window name is 'main'
|
|
*/
|
|
activate: function (windowName) {
|
|
if (typeof windowName === 'string') {
|
|
throttledActivate(windowName);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Brings window forward and gives focus.
|
|
* @param {String} windowName Name of window. Note: main window name is 'main'
|
|
* @param {String} reason, The reason for which the window is to be activated
|
|
*/
|
|
bringToFront: function (windowName, reason) {
|
|
if (typeof windowName === 'string') {
|
|
throttledBringToFront(windowName, reason);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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 {Function} callback will be called when app is
|
|
* invoked with registered protocol (e.g., symphony). The callback
|
|
* receives a single string argument: full uri that the app was
|
|
* invoked with e.g., symphony://?streamId=xyz123&streamType=chatroom
|
|
*
|
|
* Note: this function should only be called after client app is fully
|
|
* able for protocolHandler callback to be invoked. It is possible
|
|
* the app was started using protocol handler, in this case as soon as
|
|
* this registration func is invoked then the protocolHandler callback
|
|
* will be immediately called.
|
|
*/
|
|
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
|
|
*
|
|
* @deprecated instead use getMediaSource
|
|
*/
|
|
getMediaSources: getMediaSources,
|
|
|
|
/**
|
|
* 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/getSource.js
|
|
*
|
|
* This opens a window and displays all the desktop sources
|
|
* and returns selected source
|
|
*/
|
|
getMediaSource: getMediaSource,
|
|
|
|
/**
|
|
* Opens a modal window to configure notification preference.
|
|
*/
|
|
showNotificationSettings: function () {
|
|
let windowName = remote.getCurrentWindow().winName;
|
|
local.ipcRenderer.send(apiName, {
|
|
cmd: apiCmds.showNotificationSettings,
|
|
windowName: windowName
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Sets if the user is in an active meeting
|
|
* will be used to handle memory refresh functionality
|
|
*/
|
|
setIsInMeeting: function (isInMeeting) {
|
|
throttledSetIsInMeetingStatus(isInMeeting);
|
|
},
|
|
|
|
/**
|
|
* Sets the language which updates the application locale
|
|
* @param {string} locale - language identifier and a region identifier
|
|
* Ex: en-US, ja-JP
|
|
*/
|
|
setLocale: function (locale) {
|
|
if (typeof locale === 'string') {
|
|
throttledSetLocale(locale);
|
|
}
|
|
},
|
|
};
|
|
|
|
// 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 (arg && local.logger) {
|
|
local.logger(arg.msgs || [], arg.logLevel, arg.showInConsole);
|
|
}
|
|
});
|
|
|
|
// 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);
|
|
}
|
|
|
|
});
|
|
|
|
/**
|
|
* an event triggered by the main process to start crash reporter
|
|
* in the render thread
|
|
*/
|
|
local.ipcRenderer.on('register-crash-reporter', (event, arg) => {
|
|
if (arg && typeof arg === 'object') {
|
|
crashReporter.start(arg);
|
|
}
|
|
});
|
|
|
|
// Adds custom title bar style for Windows 10 OS
|
|
local.ipcRenderer.on('initiate-windows-title-bar', (event, arg) => {
|
|
if (arg && typeof arg === 'string') {
|
|
titleBar.initiateWindowsTitleBar(arg);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* an event triggered by the main process when
|
|
* the locale is changed
|
|
* @param dataObj {Object} - locale content
|
|
*/
|
|
local.ipcRenderer.on('locale-changed', (event, dataObj) => {
|
|
if (dataObj && typeof dataObj === 'object') {
|
|
if (dataObj.titleBar) {
|
|
titleBar.updateLocale(dataObj.titleBar);
|
|
}
|
|
|
|
if (dataObj.contextMenu && spellChecker) {
|
|
spellChecker.updateContextMenuLocale(dataObj.contextMenu);
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* an event triggered by the main process when
|
|
* the window enters full screen
|
|
*/
|
|
local.ipcRenderer.on('window-enter-full-screen', (event, arg) => {
|
|
if (snackBar && typeof arg === 'object' && arg.snackBar) {
|
|
setTimeout(() => snackBar.showSnackBar(arg.snackBar), 500);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* an event triggered by the main process when
|
|
* the window leave full screen
|
|
*/
|
|
local.ipcRenderer.on('window-leave-full-screen', () => {
|
|
if (snackBar) {
|
|
snackBar.removeSnackBar();
|
|
}
|
|
});
|
|
|
|
local.ipcRenderer.on('memory-info-request', () => {
|
|
if (window.name === 'main') {
|
|
const memory = process.getProcessMemoryInfo();
|
|
const cpuUsage = process.getCPUUsage();
|
|
local.ipcRenderer.send(apiName, {
|
|
cmd: apiCmds.optimizeMemoryConsumption,
|
|
memory,
|
|
cpuUsage,
|
|
});
|
|
}
|
|
});
|
|
|
|
function updateOnlineStatus() {
|
|
local.ipcRenderer.send(apiName, {
|
|
cmd: apiCmds.isOnline,
|
|
isOnline: window.navigator.onLine
|
|
});
|
|
}
|
|
|
|
// Invoked whenever the app is reloaded/navigated
|
|
function sanitize() {
|
|
local.ipcRenderer.send(apiName, {
|
|
cmd: apiCmds.sanitize,
|
|
windowName: window.name || 'main'
|
|
});
|
|
}
|
|
|
|
// Handle key down events
|
|
const throttledKeyDown = throttle(500, (event) => {
|
|
isAltKey = event.keyCode === KeyCodes.Alt;
|
|
if (event.keyCode === KeyCodes.Esc) {
|
|
local.ipcRenderer.send(apiName, {
|
|
cmd: apiCmds.keyPress,
|
|
keyCode: event.keyCode
|
|
});
|
|
}
|
|
});
|
|
|
|
// Handle key up events
|
|
const throttledKeyUp = throttle(500, (event) => {
|
|
if (isAltKey && (event.keyCode === KeyCodes.Alt || KeyCodes.Esc)) {
|
|
isMenuOpen = !isMenuOpen;
|
|
}
|
|
if (isAltKey && isMenuOpen && event.keyCode === KeyCodes.Alt) {
|
|
local.ipcRenderer.send(apiName, {
|
|
cmd: apiCmds.keyPress,
|
|
keyCode: event.keyCode
|
|
});
|
|
}
|
|
});
|
|
|
|
const throttleMouseDown = throttle(500, () => {
|
|
if (isAltKey && isMenuOpen) {
|
|
isMenuOpen = !isMenuOpen;
|
|
}
|
|
});
|
|
|
|
window.addEventListener('offline', updateOnlineStatus, false);
|
|
window.addEventListener('online', updateOnlineStatus, false);
|
|
window.addEventListener('beforeunload', sanitize, false);
|
|
window.addEventListener('keyup', throttledKeyUp, true);
|
|
window.addEventListener('keydown', throttledKeyDown, true);
|
|
window.addEventListener('mousedown', throttleMouseDown, { capture: true });
|
|
|
|
updateOnlineStatus();
|
|
}
|