aboutsummaryrefslogtreecommitdiffstats
path: root/src/webview/lib
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-07-24 02:23:48 +0200
committerLibravatar GitHub <noreply@github.com>2021-07-24 02:23:48 +0200
commit9c3c441941ad5060ec2db89b805a958a914547f3 (patch)
treea4224d7bd2576797b14a2ffa1d25ba6e167351ae /src/webview/lib
parentUpdate submodules, browserslist data updates and linter fixes [skip ci] (diff)
downloadferdium-app-9c3c441941ad5060ec2db89b805a958a914547f3.tar.gz
ferdium-app-9c3c441941ad5060ec2db89b805a958a914547f3.tar.zst
ferdium-app-9c3c441941ad5060ec2db89b805a958a914547f3.zip
Recipe context isolation (#1456)
* Enable service contextIsolation * Enable contextIsolation on the service webviews * Expose a new API window.ferdi in the service main world to allow calling back into the service isolated world * Expose a new IPC message inject-js-unsafe from the service isolated world to execute Javascript in the service main world (i.e., run code without context isolation). While the name contains the "unsafe" suffix to show the lack of context isolation, this should mostly be safe, as no nodejs APIs are available in the injected code. * Refactor the Notifications shim into a part in the isolated world that handles displaying and modifying notifications, and a shim in the main world for the Notifications class. The two communicate via the window.ferdi endpoint and a Promise object can be used to detect notification clicks. * Refactor the screen sharing shim into a part in the isolated world that enumerated shareable screens and windows and a shim in the main world that displays the media selector and completes the media selection promise. * Expose the injectJSUnsafe API to recipes to inject javascript code into the main world without context isolation. * Expose setBadge to the main world The window.ferdi.setBadge API can be used to update the service badge from injected unsafe Javascript * Safer script injection into the service main world Make sure that we don't try to serialize stray objects back from the main world to the isolated world by always surrounding the script to be executed by an anonymous function. * Always read recipe assets as utf8 * Remove window.log from recipes We didn't use it anywhere and its behavior was confusing in production mode. * Inject multiple unsafe scripts at the same time * Find in page without remote module Remove the @electron/remote dependency from the find in page (Ctrl+F) functionality. The remote webContents is replaced with Electron IPC. Synchronous IPC messages are handled in the main Electron process, because the renderer process cannot reply to IPC messages synchronously. * Update to latest contextIsolation recipes * Fixing issue with missing 'fs' functions. Co-authored-by: Vijay A <avijayr@protonmail.com>
Diffstat (limited to 'src/webview/lib')
-rw-r--r--src/webview/lib/RecipeWebview.js48
1 files changed, 23 insertions, 25 deletions
diff --git a/src/webview/lib/RecipeWebview.js b/src/webview/lib/RecipeWebview.js
index b8fe7dc52..3bb9352f6 100644
--- a/src/webview/lib/RecipeWebview.js
+++ b/src/webview/lib/RecipeWebview.js
@@ -1,14 +1,12 @@
1import { ipcRenderer } from 'electron'; 1import { ipcRenderer } from 'electron';
2import { pathExistsSync, readFile } from 'fs-extra'; 2import { exists, pathExistsSync, readFile } from 'fs-extra';
3 3
4const debug = require('debug')('Ferdi:Plugin:RecipeWebview'); 4const debug = require('debug')('Ferdi:Plugin:RecipeWebview');
5 5
6class RecipeWebview { 6class RecipeWebview {
7 constructor() { 7 constructor(badgeHandler, notificationsHandler) {
8 this.countCache = { 8 this.badgeHandler = badgeHandler;
9 direct: 0, 9 this.notificationsHandler = notificationsHandler;
10 indirect: 0,
11 };
12 10
13 ipcRenderer.on('poll', () => { 11 ipcRenderer.on('poll', () => {
14 this.loopFunc(); 12 this.loopFunc();
@@ -45,24 +43,7 @@ class RecipeWebview {
45 * me directly to me eg. in a channel 43 * me directly to me eg. in a channel
46 */ 44 */
47 setBadge(direct = 0, indirect = 0) { 45 setBadge(direct = 0, indirect = 0) {
48 if (this.countCache.direct === direct 46 this.badgeHandler.setBadge(direct, indirect);
49 && this.countCache.indirect === indirect) return;
50
51 // Parse number to integer
52 // This will correct errors that recipes may introduce, e.g.
53 // by sending a String instead of an integer
54 const directInt = parseInt(direct, 10);
55 const indirectInt = parseInt(indirect, 10);
56
57 const count = {
58 direct: Math.max(directInt, 0),
59 indirect: Math.max(indirectInt, 0),
60 };
61
62 ipcRenderer.sendToHost('message-counts', count);
63 Object.assign(this.countCache, count);
64
65 debug('Sending badge count to host', count);
66 } 47 }
67 48
68 /** 49 /**
@@ -85,6 +66,23 @@ class RecipeWebview {
85 }); 66 });
86 } 67 }
87 68
69 injectJSUnsafe(...files) {
70 Promise.all(files.map(async (file) => {
71 if (await exists(file)) {
72 const data = await readFile(file, 'utf8');
73 return data;
74 }
75 debug('Script not found', file);
76 return null;
77 })).then(async (scripts) => {
78 const scriptsFound = scripts.filter(script => script !== null);
79 if (scriptsFound.length > 0) {
80 debug('Inject scripts to main world', scriptsFound);
81 ipcRenderer.sendToHost('inject-js-unsafe', ...scriptsFound);
82 }
83 });
84 }
85
88 /** 86 /**
89 * Set a custom handler for turning on and off dark mode 87 * Set a custom handler for turning on and off dark mode
90 * 88 *
@@ -96,7 +94,7 @@ class RecipeWebview {
96 94
97 onNotify(fn) { 95 onNotify(fn) {
98 if (typeof fn === 'function') { 96 if (typeof fn === 'function') {
99 window.Notification.prototype.onNotify = fn; 97 this.notificationsHandler.onNotify = fn;
100 } 98 }
101 } 99 }
102 100