153 lines
4.3 KiB
JavaScript
153 lines
4.3 KiB
JavaScript
|
"use strict";
|
||
|
|
||
|
Object.defineProperty(exports, "__esModule", {
|
||
|
value: true
|
||
|
});
|
||
|
exports.closePopouts = closePopouts;
|
||
|
exports.getWindow = getWindow;
|
||
|
exports.hasInit = void 0;
|
||
|
exports.init = init;
|
||
|
exports.openOrFocusWindow = openOrFocusWindow;
|
||
|
exports.setupPopout = setupPopout;
|
||
|
var _electron = require("electron");
|
||
|
var _securityUtils = require("../common/securityUtils");
|
||
|
var _appFeatures = require("./appFeatures");
|
||
|
const ALLOWED_FEATURES = ['width', 'height', 'left', 'top', 'resizable', 'movable', 'alwaysOnTop', 'frame', 'transparent', 'hasShadow', 'closable', 'skipTaskbar', 'backgroundColor', 'menubar', 'toolbar', 'location', 'directories', 'titleBarStyle'];
|
||
|
const MIN_POPOUT_WIDTH = 320;
|
||
|
const MIN_POPOUT_HEIGHT = 180;
|
||
|
const DEFAULT_POPOUT_OPTIONS = {
|
||
|
title: 'Discord Popout',
|
||
|
backgroundColor: '#2f3136',
|
||
|
minWidth: MIN_POPOUT_WIDTH,
|
||
|
minHeight: MIN_POPOUT_HEIGHT,
|
||
|
transparent: false,
|
||
|
frame: process.platform === 'linux',
|
||
|
resizable: true,
|
||
|
show: true,
|
||
|
titleBarStyle: process.platform === 'darwin' ? 'hidden' : undefined,
|
||
|
trafficLightPosition: process.platform === 'darwin' ? {
|
||
|
x: 10,
|
||
|
y: 3
|
||
|
} : undefined,
|
||
|
webPreferences: {
|
||
|
nodeIntegration: false,
|
||
|
nativeWindowOpen: true,
|
||
|
enableRemoteModule: false,
|
||
|
contextIsolation: true
|
||
|
}
|
||
|
};
|
||
|
const features = (0, _appFeatures.getFeatures)();
|
||
|
let hasInit = false;
|
||
|
exports.hasInit = hasInit;
|
||
|
let popoutWindows = {};
|
||
|
function init() {
|
||
|
if (hasInit) {
|
||
|
console.warn('popoutWindows: Has already init! Cancelling init.');
|
||
|
return;
|
||
|
}
|
||
|
exports.hasInit = hasInit = true;
|
||
|
features.declareSupported('popout_windows');
|
||
|
}
|
||
|
function focusWindow(window) {
|
||
|
if (window == null) return;
|
||
|
// The focus call is not always respected.
|
||
|
// This uses a hack defined in https://github.com/electron/electron/issues/2867
|
||
|
window.setAlwaysOnTop(true);
|
||
|
window.focus();
|
||
|
window.setAlwaysOnTop(false);
|
||
|
}
|
||
|
function getWindow(key) {
|
||
|
return popoutWindows[key];
|
||
|
}
|
||
|
function parseFeatureValue(feature) {
|
||
|
if (feature === 'yes') {
|
||
|
return true;
|
||
|
} else if (feature === 'no') {
|
||
|
return false;
|
||
|
}
|
||
|
const parsedNumber = Number.parseInt(feature);
|
||
|
if (!Number.isNaN(parsedNumber)) {
|
||
|
return parsedNumber;
|
||
|
}
|
||
|
return feature;
|
||
|
}
|
||
|
function filterWindowFeatures(hasKey, getKey) {
|
||
|
return ALLOWED_FEATURES.reduce((acc, curr) => {
|
||
|
if (hasKey(curr)) {
|
||
|
return {
|
||
|
...acc,
|
||
|
[curr]: getKey(curr)
|
||
|
};
|
||
|
}
|
||
|
return acc;
|
||
|
}, {});
|
||
|
}
|
||
|
function parseWindowFeatures(features) {
|
||
|
const keyValuesParsed = features.split(',');
|
||
|
const parsedFeatures = keyValuesParsed.reduce((acc, curr) => {
|
||
|
const [key, value] = curr.split('=');
|
||
|
return {
|
||
|
...acc,
|
||
|
[key]: parseFeatureValue(value)
|
||
|
};
|
||
|
}, {});
|
||
|
return filterWindowFeatures(key => parsedFeatures.hasOwnProperty(key), key => parsedFeatures[key]);
|
||
|
}
|
||
|
function openOrFocusWindow(windowURL, key, features) {
|
||
|
const existingWindow = popoutWindows[key];
|
||
|
if (existingWindow != null) {
|
||
|
focusWindow(existingWindow);
|
||
|
return {
|
||
|
action: 'deny'
|
||
|
};
|
||
|
}
|
||
|
return {
|
||
|
action: 'allow',
|
||
|
overrideBrowserWindowOptions: {
|
||
|
...DEFAULT_POPOUT_OPTIONS,
|
||
|
...parseWindowFeatures(features)
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
function setupPopout(popoutWindow, key, options, webappEndpoint) {
|
||
|
popoutWindow.windowKey = key;
|
||
|
popoutWindows[popoutWindow.windowKey] = popoutWindow;
|
||
|
|
||
|
/**
|
||
|
* Do not allow navigation to arbitrary domains.
|
||
|
*/
|
||
|
popoutWindow.webContents.on('will-navigate', (evt, url) => {
|
||
|
if (!(0, _securityUtils.checkUrlOriginMatches)(url, webappEndpoint)) {
|
||
|
evt.preventDefault();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Any window.open or links within the popout window should just open externally.
|
||
|
*/
|
||
|
popoutWindow.webContents.setWindowOpenHandler(({
|
||
|
url
|
||
|
}) => {
|
||
|
(0, _securityUtils.saferShellOpenExternal)(url);
|
||
|
// Even if this opens the url, we still want to deny since we want to open in the user's browser,
|
||
|
// not the electron app
|
||
|
return {
|
||
|
action: 'deny'
|
||
|
};
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Handle events for our new window
|
||
|
*
|
||
|
* NOTE: Wanted to handle 'always-on-top-changed' and send to client but currently
|
||
|
* the event seems to not fire.
|
||
|
* */
|
||
|
popoutWindow.once('closed', () => {
|
||
|
popoutWindow.removeAllListeners();
|
||
|
delete popoutWindows[popoutWindow.windowKey];
|
||
|
});
|
||
|
}
|
||
|
function closePopouts() {
|
||
|
Object.values(popoutWindows).forEach(popoutWindow => popoutWindow.close());
|
||
|
popoutWindows = {};
|
||
|
}
|