diff options
author | Kristóf Marussy <kristof@marussy.com> | 2021-12-25 00:01:18 +0100 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2021-12-25 00:01:18 +0100 |
commit | e321534fbea9f09b139d440584f6b84ad0afb80f (patch) | |
tree | c4b4df109589475dd5f40a0d31f47a6aa9e43195 /packages/main/src | |
parent | feat: Shim userAgentData in all frames and workers (diff) | |
download | sophie-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.ts | 84 |
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'; |
27 | import { readFile, readFileSync } from 'fs'; | 27 | import { readFile, readFileSync } from 'fs'; |
28 | import { autorun } from 'mobx'; | 28 | import { autorun } from 'mobx'; |
@@ -31,6 +31,7 @@ import { join } from 'path'; | |||
31 | import { | 31 | import { |
32 | ServiceToMainIpcMessage, | 32 | ServiceToMainIpcMessage, |
33 | unreadCount, | 33 | unreadCount, |
34 | WebSource, | ||
34 | } from '@sophie/service-shared'; | 35 | } from '@sophie/service-shared'; |
35 | import { | 36 | import { |
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. |
74 | const originalUserAgent = app.userAgentFallback; | 75 | const originalUserAgent = app.userAgentFallback; |
75 | const userAgent = originalUserAgent.replaceAll(/\s(sophie|Electron)\/\S+/g, ''); | 76 | const userAgent = originalUserAgent.replaceAll(/\s(sophie|Electron)\/\S+/g, ''); |
76 | const platformInUa = userAgent.match(/\((Win|Mac|X11; L)/); | 77 | const chromelessUserAgent = userAgent.replace(/ Chrome\/\S+/, ''); |
77 | let platform = 'Unknown'; | ||
78 | if (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 | } | ||
91 | const 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. |
93 | if (!isDevelopment) { | 79 | if (!isDevelopment) { |
94 | app.userAgentFallback = userAgent; | 80 | app.userAgentFallback = userAgent; |
95 | } | 81 | } |
96 | 82 | ||
83 | function getResourcePath(relativePath: string): string { | ||
84 | return join(__dirname, relativePath); | ||
85 | } | ||
86 | |||
87 | function getResourceUrl(relativePath: string): string { | ||
88 | return new URL(relativePath, `file://${__dirname}`).toString(); | ||
89 | } | ||
90 | |||
97 | let serviceInjectRelativePath = '../../service-inject/dist/index.cjs'; | 91 | let serviceInjectRelativePath = '../../service-inject/dist/index.cjs'; |
98 | let serviceInjectPath = join(__dirname, serviceInjectRelativePath); | 92 | let serviceInjectPath = getResourcePath(serviceInjectRelativePath); |
99 | let serviceInject: string = readFileSync(serviceInjectPath, 'utf8'); | 93 | let serviceInject: WebSource = { |
94 | code: readFileSync(serviceInjectPath, 'utf8'), | ||
95 | url: getResourceUrl(serviceInjectRelativePath), | ||
96 | }; | ||
100 | 97 | ||
101 | if (isDevelopment) { | 98 | if (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 | ||