aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-25 00:01:18 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-25 00:01:18 +0100
commite321534fbea9f09b139d440584f6b84ad0afb80f (patch)
treec4b4df109589475dd5f40a0d31f47a6aa9e43195 /packages/main/src
parentfeat: Shim userAgentData in all frames and workers (diff)
downloadsophie-e321534fbea9f09b139d440584f6b84ad0afb80f.tar.gz
sophie-e321534fbea9f09b139d440584f6b84ad0afb80f.tar.zst
sophie-e321534fbea9f09b139d440584f6b84ad0afb80f.zip
refactor: Simplify script injection
Inject CSS and main world scripts synchronously to avoid race conditions with page loading. Don't try to miming userAgentData for now, since it won't bypass google's checks. However, simply omitting chrome from the user agent does bypass them, at least for now.
Diffstat (limited to 'packages/main/src')
-rw-r--r--packages/main/src/index.ts84
1 files changed, 31 insertions, 53 deletions
diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts
index 02d6c97..0c0a585 100644
--- a/packages/main/src/index.ts
+++ b/packages/main/src/index.ts
@@ -22,7 +22,7 @@ import {
22 app, 22 app,
23 BrowserView, 23 BrowserView,
24 BrowserWindow, 24 BrowserWindow,
25 IpcMainEvent, 25 ipcMain,
26} from 'electron'; 26} from 'electron';
27import { readFile, readFileSync } from 'fs'; 27import { readFile, readFileSync } from 'fs';
28import { autorun } from 'mobx'; 28import { autorun } from 'mobx';
@@ -31,6 +31,7 @@ import { join } from 'path';
31import { 31import {
32 ServiceToMainIpcMessage, 32 ServiceToMainIpcMessage,
33 unreadCount, 33 unreadCount,
34 WebSource,
34} from '@sophie/service-shared'; 35} from '@sophie/service-shared';
35import { 36import {
36 browserViewBounds, 37 browserViewBounds,
@@ -73,30 +74,26 @@ app.commandLine.appendSwitch(
73// Remove sophie and electron from the user-agent string to avoid detection. 74// Remove sophie and electron from the user-agent string to avoid detection.
74const originalUserAgent = app.userAgentFallback; 75const originalUserAgent = app.userAgentFallback;
75const userAgent = originalUserAgent.replaceAll(/\s(sophie|Electron)\/\S+/g, ''); 76const userAgent = originalUserAgent.replaceAll(/\s(sophie|Electron)\/\S+/g, '');
76const platformInUa = userAgent.match(/\((Win|Mac|X11; L)/); 77const chromelessUserAgent = userAgent.replace(/ Chrome\/\S+/, '');
77let platform = 'Unknown';
78if (platformInUa !== null) {
79 switch (platformInUa[1]) {
80 case 'Win':
81 platform = 'Windows';
82 break;
83 case 'Mac':
84 platform = 'macOS';
85 break;
86 case 'X11; L':
87 platform = 'Linux';
88 break;
89 }
90}
91const chromiumVersion = process.versions.chrome.split('.')[0];
92// Removing the electron version breaks redux devtools, so we only do this in production. 78// Removing the electron version breaks redux devtools, so we only do this in production.
93if (!isDevelopment) { 79if (!isDevelopment) {
94 app.userAgentFallback = userAgent; 80 app.userAgentFallback = userAgent;
95} 81}
96 82
83function getResourcePath(relativePath: string): string {
84 return join(__dirname, relativePath);
85}
86
87function getResourceUrl(relativePath: string): string {
88 return new URL(relativePath, `file://${__dirname}`).toString();
89}
90
97let serviceInjectRelativePath = '../../service-inject/dist/index.cjs'; 91let serviceInjectRelativePath = '../../service-inject/dist/index.cjs';
98let serviceInjectPath = join(__dirname, serviceInjectRelativePath); 92let serviceInjectPath = getResourcePath(serviceInjectRelativePath);
99let serviceInject: string = readFileSync(serviceInjectPath, 'utf8'); 93let serviceInject: WebSource = {
94 code: readFileSync(serviceInjectPath, 'utf8'),
95 url: getResourceUrl(serviceInjectRelativePath),
96};
100 97
101if (isDevelopment) { 98if (isDevelopment) {
102 installDevToolsExtensions(app); 99 installDevToolsExtensions(app);
@@ -111,12 +108,12 @@ function createWindow(): Promise<unknown> {
111 show: false, 108 show: false,
112 webPreferences: { 109 webPreferences: {
113 sandbox: true, 110 sandbox: true,
114 preload: join(__dirname, '../../preload/dist/index.cjs'), 111 preload: getResourcePath('../../preload/dist/index.cjs'),
115 }, 112 },
116 }); 113 });
117 114
118 if (isDevelopment) { 115 if (isDevelopment) {
119 // openDevToolsWhenReady(mainWindow); 116 openDevToolsWhenReady(mainWindow);
120 } 117 }
121 118
122 mainWindow.on('ready-to-show', () => { 119 mainWindow.on('ready-to-show', () => {
@@ -131,7 +128,7 @@ function createWindow(): Promise<unknown> {
131 webPreferences: { 128 webPreferences: {
132 sandbox: true, 129 sandbox: true,
133 nodeIntegrationInSubFrames: true, 130 nodeIntegrationInSubFrames: true,
134 preload: join(__dirname, '../../service-preload/dist/index.cjs'), 131 preload: getResourcePath('../../service-preload/dist/index.cjs'),
135 partition: 'persist:service', 132 partition: 'persist:service',
136 }, 133 },
137 }); 134 });
@@ -142,17 +139,6 @@ function createWindow(): Promise<unknown> {
142 }); 139 });
143 mainWindow.setBrowserView(browserView); 140 mainWindow.setBrowserView(browserView);
144 141
145 browserView.webContents.on(
146 'did-frame-navigate',
147 (_event, _url, _statusCode, _statusText, isMainFrame, _processId, routingId) => {
148 const { webContents: { mainFrame } } = browserView;
149 const frame = isMainFrame
150 ? mainFrame
151 : mainFrame.framesInSubtree.find((f) => f.routingId === routingId);
152 frame?.executeJavaScript(serviceInject).catch((err) => console.log(err));
153 }
154 );
155
156 webContents.on('ipc-message', (_event, channel, ...args) => { 142 webContents.on('ipc-message', (_event, channel, ...args) => {
157 try { 143 try {
158 switch (channel) { 144 switch (channel) {
@@ -168,7 +154,7 @@ function createWindow(): Promise<unknown> {
168 case RendererToMainIpcMessage.ReloadAllServices: 154 case RendererToMainIpcMessage.ReloadAllServices:
169 readFile(serviceInjectPath, 'utf8', (err, data) => { 155 readFile(serviceInjectPath, 'utf8', (err, data) => {
170 if (err === null) { 156 if (err === null) {
171 serviceInject = data; 157 serviceInject.code = data;
172 } else { 158 } else {
173 console.error('Error while reloading', serviceInjectPath, err); 159 console.error('Error while reloading', serviceInjectPath, err);
174 } 160 }
@@ -188,10 +174,17 @@ function createWindow(): Promise<unknown> {
188 webContents.send(MainToRendererIpcMessage.SharedStorePatch, patch); 174 webContents.send(MainToRendererIpcMessage.SharedStorePatch, patch);
189 }); 175 });
190 176
177 ipcMain.on(ServiceToMainIpcMessage.ApiExposedInMainWorld, (event) => {
178 event.returnValue = event.sender.id == browserView.webContents.id
179 ? serviceInject
180 : null;
181 });
182
191 browserView.webContents.on('ipc-message', (_event, channel, ...args) => { 183 browserView.webContents.on('ipc-message', (_event, channel, ...args) => {
192 try { 184 try {
193 switch (channel) { 185 switch (channel) {
194 case ServiceToMainIpcMessage.ApiExposedInMainWorld: 186 case ServiceToMainIpcMessage.ApiExposedInMainWorld:
187 // Synchronous message must be handled with `ipcMain.on`
195 break; 188 break;
196 case ServiceToMainIpcMessage.SetUnreadCount: 189 case ServiceToMainIpcMessage.SetUnreadCount:
197 console.log('Unread count:', unreadCount.parse(args[0])); 190 console.log('Unread count:', unreadCount.parse(args[0]));
@@ -205,37 +198,22 @@ function createWindow(): Promise<unknown> {
205 } 198 }
206 }); 199 });
207 200
208 // Inject CSS to simulate `browserView.setBackgroundColor`.
209 // This is injected before the page loads, so the styles from the website will overwrite it.
210 browserView.webContents.on('did-navigate', () => {
211 browserView.webContents.insertCSS(
212 'html { background-color: #fff; }',
213 {
214 cssOrigin: 'author',
215 },
216 );
217 });
218
219 browserView.webContents.session.webRequest.onBeforeSendHeaders(({ url, requestHeaders }, callback) => { 201 browserView.webContents.session.webRequest.onBeforeSendHeaders(({ url, requestHeaders }, callback) => {
220 if (url.match(/accounts\.google/)) { 202 if (url.match(/^[^:]+:\/\/accounts\.google\.[^.\/]+\//)) {
221 requestHeaders['User-Agent'] = userAgent.replace(/ Chrome\/\S+/, ''); 203 requestHeaders['User-Agent'] = chromelessUserAgent;
222 } else { 204 } else {
223 requestHeaders['User-Agent'] = userAgent; 205 requestHeaders['User-Agent'] = userAgent;
224 } 206 }
225 requestHeaders['User-Agent'] = userAgent;
226 requestHeaders['Sec-CH-UA'] = `" Not A;Brand";v="99", "Chromium";v="${chromiumVersion}"`;
227 requestHeaders['Sec-CH-UA-Mobile'] = '?0';
228 requestHeaders['Sec-CH-UA-Platform'] = platform;
229 callback({ requestHeaders }); 207 callback({ requestHeaders });
230 }); 208 });
231 209
232 const pageUrl = (isDevelopment && import.meta.env.VITE_DEV_SERVER_URL !== undefined) 210 const pageUrl = (isDevelopment && import.meta.env.VITE_DEV_SERVER_URL !== undefined)
233 ? import.meta.env.VITE_DEV_SERVER_URL 211 ? import.meta.env.VITE_DEV_SERVER_URL
234 : new URL('../renderer/dist/index.html', `file://${__dirname}`).toString(); 212 : getResourceUrl('../renderer/dist/index.html');
235 213
236 return Promise.all([ 214 return Promise.all([
237 mainWindow.loadURL(pageUrl), 215 mainWindow.loadURL(pageUrl),
238 browserView.webContents.loadURL('https://gmail.com').then(() => browserView.webContents.openDevTools()), 216 browserView.webContents.loadURL('https://git.marussy.com/sophie/about'),
239 ]); 217 ]);
240} 218}
241 219