aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-06-03 19:01:01 +0200
committerLibravatar GitHub <noreply@github.com>2021-06-03 19:01:01 +0200
commit2ad39ffb1cb0d0e5f79d6948f798ca79ed73c76c (patch)
treedcb679119cf4963126a3520b7c62ae4b032e0225
parentUpgraded electron to '13.1.0'. (diff)
downloadferdi-2ad39ffb1cb0d0e5f79d6948f798ca79ed73c76c.tar.gz
ferdi-2ad39ffb1cb0d0e5f79d6948f798ca79ed73c76c.tar.zst
ferdi-2ad39ffb1cb0d0e5f79d6948f798ca79ed73c76c.zip
Expose Chrome version to todos webview (fix #1211) (#1478)
* Expose Chrome version to todos webview (fix #1211) The TickTick todo service fails to load if the Chrome version number does not appear in the User-Agent string. However, login to Google Tasks is prevented by the same. We adopt the "chromeless" User-Agent logic from the service webview, which selectively exposes the Chrome version everywhere except the Google login screen. The common logic was moved into the userAgent-helpers module. * Refactor user agent switching * "Chromeless" user agent switching is extracted into a separate model * Both the service and the todos webview uses the same model
-rw-r--r--src/features/todos/components/TodosWebview.js10
-rw-r--r--src/features/todos/containers/TodosScreen.js1
-rw-r--r--src/features/todos/store.js13
-rw-r--r--src/helpers/userAgent-helpers.js4
-rw-r--r--src/i18n/locales/defaultMessages.json12
-rw-r--r--src/i18n/messages/src/features/todos/components/TodosWebview.json12
-rw-r--r--src/models/Service.js39
-rw-r--r--src/models/UserAgent.js80
8 files changed, 120 insertions, 51 deletions
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.js
index 18a900e8..634ec4ca 100644
--- a/src/features/todos/components/TodosWebview.js
+++ b/src/features/todos/components/TodosWebview.js
@@ -14,8 +14,6 @@ import Appear from '../../../components/ui/effects/Appear';
14import UpgradeButton from '../../../components/ui/UpgradeButton'; 14import UpgradeButton from '../../../components/ui/UpgradeButton';
15import { TODOS_PARTITION_ID } from '..'; 15import { TODOS_PARTITION_ID } from '..';
16 16
17import userAgent from '../../../helpers/userAgent-helpers';
18
19// NOTE: https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url 17// NOTE: https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url
20function validURL(str) { 18function validURL(str) {
21 let url; 19 let url;
@@ -112,6 +110,7 @@ class TodosWebview extends Component {
112 resize: PropTypes.func.isRequired, 110 resize: PropTypes.func.isRequired,
113 width: PropTypes.number.isRequired, 111 width: PropTypes.number.isRequired,
114 minWidth: PropTypes.number.isRequired, 112 minWidth: PropTypes.number.isRequired,
113 userAgent: PropTypes.string.isRequired,
115 isTodosIncludedInCurrentPlan: PropTypes.bool.isRequired, 114 isTodosIncludedInCurrentPlan: PropTypes.bool.isRequired,
116 stores: PropTypes.shape({ 115 stores: PropTypes.shape({
117 settings: PropTypes.instanceOf(SettingsStore).isRequired, 116 settings: PropTypes.instanceOf(SettingsStore).isRequired,
@@ -139,11 +138,6 @@ class TodosWebview extends Component {
139 this.node.addEventListener('mousemove', this.resizePanel.bind(this)); 138 this.node.addEventListener('mousemove', this.resizePanel.bind(this));
140 this.node.addEventListener('mouseup', this.stopResize.bind(this)); 139 this.node.addEventListener('mouseup', this.stopResize.bind(this));
141 this.node.addEventListener('mouseleave', this.stopResize.bind(this)); 140 this.node.addEventListener('mouseleave', this.stopResize.bind(this));
142
143 const webViewInstance = this;
144 this.webview.addEventListener('dom-ready', () => {
145 webViewInstance.webview.setUserAgent(userAgent(true));
146 });
147 } 141 }
148 142
149 startResize = (event) => { 143 startResize = (event) => {
@@ -214,6 +208,7 @@ class TodosWebview extends Component {
214 classes, 208 classes,
215 isTodosServiceActive, 209 isTodosServiceActive,
216 isVisible, 210 isVisible,
211 userAgent,
217 isTodosIncludedInCurrentPlan, 212 isTodosIncludedInCurrentPlan,
218 stores, 213 stores,
219 } = this.props; 214 } = this.props;
@@ -275,6 +270,7 @@ class TodosWebview extends Component {
275 partition={TODOS_PARTITION_ID} 270 partition={TODOS_PARTITION_ID}
276 preload="./features/todos/preload.js" 271 preload="./features/todos/preload.js"
277 ref={(webview) => { this.webview = webview ? webview.view : null; }} 272 ref={(webview) => { this.webview = webview ? webview.view : null; }}
273 useragent={userAgent}
278 src={todoUrl} 274 src={todoUrl}
279 /> 275 />
280 ) 276 )
diff --git a/src/features/todos/containers/TodosScreen.js b/src/features/todos/containers/TodosScreen.js
index 884925be..631893f9 100644
--- a/src/features/todos/containers/TodosScreen.js
+++ b/src/features/todos/containers/TodosScreen.js
@@ -27,6 +27,7 @@ class TodosScreen extends Component {
27 width={todosStore.width} 27 width={todosStore.width}
28 minWidth={TODOS_MIN_WIDTH} 28 minWidth={TODOS_MIN_WIDTH}
29 resize={width => todoActions.resize({ width })} 29 resize={width => todoActions.resize({ width })}
30 userAgent={todosStore.userAgent}
30 isTodosIncludedInCurrentPlan 31 isTodosIncludedInCurrentPlan
31 /> 32 />
32 </ErrorBoundary> 33 </ErrorBoundary>
diff --git a/src/features/todos/store.js b/src/features/todos/store.js
index c1d6a904..af8519d9 100644
--- a/src/features/todos/store.js
+++ b/src/features/todos/store.js
@@ -16,6 +16,8 @@ import {
16import { IPC } from './constants'; 16import { IPC } from './constants';
17import { state as delayAppState } from '../delayApp'; 17import { state as delayAppState } from '../delayApp';
18 18
19import UserAgent from '../../models/UserAgent';
20
19const debug = require('debug')('Ferdi:feature:todos:store'); 21const debug = require('debug')('Ferdi:feature:todos:store');
20 22
21export default class TodoStore extends FeatureStore { 23export default class TodoStore extends FeatureStore {
@@ -25,6 +27,8 @@ export default class TodoStore extends FeatureStore {
25 27
26 @observable webview = null; 28 @observable webview = null;
27 29
30 @observable userAgentModel = new UserAgent();
31
28 isInitialized = false; 32 isInitialized = false;
29 33
30 @computed get width() { 34 @computed get width() {
@@ -51,6 +55,10 @@ export default class TodoStore extends FeatureStore {
51 return localStorage.getItem('todos') || {}; 55 return localStorage.getItem('todos') || {};
52 } 56 }
53 57
58 @computed get userAgent() {
59 return this.userAgentModel.userAgent;
60 }
61
54 // ========== PUBLIC API ========= // 62 // ========== PUBLIC API ========= //
55 63
56 @action start(stores, actions) { 64 @action start(stores, actions) {
@@ -123,7 +131,10 @@ export default class TodoStore extends FeatureStore {
123 131
124 @action _setTodosWebview = ({ webview }) => { 132 @action _setTodosWebview = ({ webview }) => {
125 debug('_setTodosWebview', webview); 133 debug('_setTodosWebview', webview);
126 this.webview = webview; 134 if (this.webview !== webview) {
135 this.webview = webview;
136 this.userAgentModel.setWebviewReference(webview);
137 }
127 }; 138 };
128 139
129 @action _handleHostMessage = (message) => { 140 @action _handleHostMessage = (message) => {
diff --git a/src/helpers/userAgent-helpers.js b/src/helpers/userAgent-helpers.js
index c5eee008..73c45430 100644
--- a/src/helpers/userAgent-helpers.js
+++ b/src/helpers/userAgent-helpers.js
@@ -19,6 +19,10 @@ function linux() {
19 return 'X11; Ubuntu; Linux x86_64'; 19 return 'X11; Ubuntu; Linux x86_64';
20} 20}
21 21
22export function isChromeless(url) {
23 return url.startsWith('https://accounts.google.com');
24}
25
22export default function userAgent(removeChromeVersion = false, addFerdiVersion = false) { 26export default function userAgent(removeChromeVersion = false, addFerdiVersion = false) {
23 let platformString = ''; 27 let platformString = '';
24 28
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json
index ef25aa3d..c4435739 100644
--- a/src/i18n/locales/defaultMessages.json
+++ b/src/i18n/locales/defaultMessages.json
@@ -6412,39 +6412,39 @@
6412 "defaultMessage": "!!!Franz Todos are available to premium users now!", 6412 "defaultMessage": "!!!Franz Todos are available to premium users now!",
6413 "end": { 6413 "end": {
6414 "column": 3, 6414 "column": 3,
6415 "line": 36 6415 "line": 34
6416 }, 6416 },
6417 "file": "src/features/todos/components/TodosWebview.js", 6417 "file": "src/features/todos/components/TodosWebview.js",
6418 "id": "feature.todos.premium.info", 6418 "id": "feature.todos.premium.info",
6419 "start": { 6419 "start": {
6420 "column": 15, 6420 "column": 15,
6421 "line": 33 6421 "line": 31
6422 } 6422 }
6423 }, 6423 },
6424 { 6424 {
6425 "defaultMessage": "!!!Upgrade Account", 6425 "defaultMessage": "!!!Upgrade Account",
6426 "end": { 6426 "end": {
6427 "column": 3, 6427 "column": 3,
6428 "line": 40 6428 "line": 38
6429 }, 6429 },
6430 "file": "src/features/todos/components/TodosWebview.js", 6430 "file": "src/features/todos/components/TodosWebview.js",
6431 "id": "feature.todos.premium.upgrade", 6431 "id": "feature.todos.premium.upgrade",
6432 "start": { 6432 "start": {
6433 "column": 14, 6433 "column": 14,
6434 "line": 37 6434 "line": 35
6435 } 6435 }
6436 }, 6436 },
6437 { 6437 {
6438 "defaultMessage": "!!!Everyone else will have to wait a little longer.", 6438 "defaultMessage": "!!!Everyone else will have to wait a little longer.",
6439 "end": { 6439 "end": {
6440 "column": 3, 6440 "column": 3,
6441 "line": 44 6441 "line": 42
6442 }, 6442 },
6443 "file": "src/features/todos/components/TodosWebview.js", 6443 "file": "src/features/todos/components/TodosWebview.js",
6444 "id": "feature.todos.premium.rollout", 6444 "id": "feature.todos.premium.rollout",
6445 "start": { 6445 "start": {
6446 "column": 15, 6446 "column": 15,
6447 "line": 41 6447 "line": 39
6448 } 6448 }
6449 } 6449 }
6450 ], 6450 ],
diff --git a/src/i18n/messages/src/features/todos/components/TodosWebview.json b/src/i18n/messages/src/features/todos/components/TodosWebview.json
index 0652b0a3..ff6e037f 100644
--- a/src/i18n/messages/src/features/todos/components/TodosWebview.json
+++ b/src/i18n/messages/src/features/todos/components/TodosWebview.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Franz Todos are available to premium users now!", 4 "defaultMessage": "!!!Franz Todos are available to premium users now!",
5 "file": "src/features/todos/components/TodosWebview.js", 5 "file": "src/features/todos/components/TodosWebview.js",
6 "start": { 6 "start": {
7 "line": 33, 7 "line": 31,
8 "column": 15 8 "column": 15
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 36, 11 "line": 34,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Upgrade Account", 17 "defaultMessage": "!!!Upgrade Account",
18 "file": "src/features/todos/components/TodosWebview.js", 18 "file": "src/features/todos/components/TodosWebview.js",
19 "start": { 19 "start": {
20 "line": 37, 20 "line": 35,
21 "column": 14 21 "column": 14
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 40, 24 "line": 38,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Everyone else will have to wait a little longer.", 30 "defaultMessage": "!!!Everyone else will have to wait a little longer.",
31 "file": "src/features/todos/components/TodosWebview.js", 31 "file": "src/features/todos/components/TodosWebview.js",
32 "start": { 32 "start": {
33 "line": 41, 33 "line": 39,
34 "column": 15 34 "column": 15
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 44, 37 "line": 42,
38 "column": 3 38 "column": 3
39 } 39 }
40 } 40 }
diff --git a/src/models/Service.js b/src/models/Service.js
index b01881be..0d1dff43 100644
--- a/src/models/Service.js
+++ b/src/models/Service.js
@@ -4,9 +4,9 @@ import { webContents } from '@electron/remote';
4import normalizeUrl from 'normalize-url'; 4import normalizeUrl from 'normalize-url';
5import path from 'path'; 5import path from 'path';
6 6
7import userAgent from '../helpers/userAgent-helpers';
8import { TODOS_RECIPE_ID, todosStore } from '../features/todos'; 7import { TODOS_RECIPE_ID, todosStore } from '../features/todos';
9import { isValidExternalURL } from '../helpers/url-helpers'; 8import { isValidExternalURL } from '../helpers/url-helpers';
9import UserAgent from './UserAgent';
10 10
11const debug = require('debug')('Ferdi:Service'); 11const debug = require('debug')('Ferdi:Service');
12 12
@@ -94,7 +94,7 @@ export default class Service {
94 94
95 @observable lostRecipeReloadAttempt = 0; 95 @observable lostRecipeReloadAttempt = 0;
96 96
97 @observable chromelessUserAgent = false; 97 @observable userAgentModel = null;
98 98
99 constructor(data, recipe) { 99 constructor(data, recipe) {
100 if (!data) { 100 if (!data) {
@@ -156,6 +156,8 @@ export default class Service {
156 this.isHibernating = true; 156 this.isHibernating = true;
157 } 157 }
158 158
159 this.userAgentModel = new UserAgent(recipe.overrideUserAgent);
160
159 autorun(() => { 161 autorun(() => {
160 if (!this.isEnabled) { 162 if (!this.isEnabled) {
161 this.webview = null; 163 this.webview = null;
@@ -234,12 +236,7 @@ export default class Service {
234 } 236 }
235 237
236 @computed get userAgent() { 238 @computed get userAgent() {
237 let ua = userAgent(this.chromelessUserAgent); 239 return this.userAgentModel.userAgent;
238 if (typeof this.recipe.overrideUserAgent === 'function') {
239 ua = this.recipe.overrideUserAgent();
240 }
241
242 return ua;
243 } 240 }
244 241
245 @computed get partition() { 242 @computed get partition() {
@@ -250,6 +247,8 @@ export default class Service {
250 initializeWebViewEvents({ handleIPCMessage, openWindow, stores }) { 247 initializeWebViewEvents({ handleIPCMessage, openWindow, stores }) {
251 const webviewWebContents = webContents.fromId(this.webview.getWebContentsId()); 248 const webviewWebContents = webContents.fromId(this.webview.getWebContentsId());
252 249
250 this.userAgentModel.setWebviewReference(this.webview);
251
253 // If the recipe has implemented modifyRequestHeaders, 252 // If the recipe has implemented modifyRequestHeaders,
254 // Send those headers to ipcMain so that it can be set in session 253 // Send those headers to ipcMain so that it can be set in session
255 if (typeof this.recipe.modifyRequestHeaders === 'function') { 254 if (typeof this.recipe.modifyRequestHeaders === 'function') {
@@ -263,23 +262,6 @@ export default class Service {
263 debug(this.name, 'modifyRequestHeaders is not defined in the recipe'); 262 debug(this.name, 'modifyRequestHeaders is not defined in the recipe');
264 } 263 }
265 264
266 const handleUserAgent = (url, forwardingHack = false) => {
267 if (url.startsWith('https://accounts.google.com')) {
268 if (!this.chromelessUserAgent) {
269 debug('Setting user agent to chromeless for url', url);
270 this.webview.setUserAgent(userAgent(true));
271 if (forwardingHack) {
272 this.webview.loadURL(url);
273 }
274 this.chromelessUserAgent = true;
275 }
276 } else if (this.chromelessUserAgent) {
277 debug('Setting user agent to contain chrome');
278 this.webview.setUserAgent(this.userAgent);
279 this.chromelessUserAgent = false;
280 }
281 };
282
283 this.webview.addEventListener('ipc-message', e => handleIPCMessage({ 265 this.webview.addEventListener('ipc-message', e => handleIPCMessage({
284 serviceId: this.id, 266 serviceId: this.id,
285 channel: e.channel, 267 channel: e.channel,
@@ -306,8 +288,6 @@ export default class Service {
306 } 288 }
307 }); 289 });
308 290
309 this.webview.addEventListener('will-navigate', event => handleUserAgent(event.url, true));
310
311 this.webview.addEventListener('did-start-loading', (event) => { 291 this.webview.addEventListener('did-start-loading', (event) => {
312 debug('Did start load', this.name, event); 292 debug('Did start load', this.name, event);
313 293
@@ -325,10 +305,7 @@ export default class Service {
325 }; 305 };
326 306
327 this.webview.addEventListener('did-frame-finish-load', didLoad.bind(this)); 307 this.webview.addEventListener('did-frame-finish-load', didLoad.bind(this));
328 this.webview.addEventListener('did-navigate', (event) => { 308 this.webview.addEventListener('did-navigate', didLoad.bind(this));
329 handleUserAgent(event.url);
330 didLoad();
331 });
332 309
333 this.webview.addEventListener('did-fail-load', (event) => { 310 this.webview.addEventListener('did-fail-load', (event) => {
334 debug('Service failed to load', this.name, event); 311 debug('Service failed to load', this.name, event);
diff --git a/src/models/UserAgent.js b/src/models/UserAgent.js
new file mode 100644
index 00000000..f51f2e5a
--- /dev/null
+++ b/src/models/UserAgent.js
@@ -0,0 +1,80 @@
1import {
2 action,
3 computed,
4 observe,
5 observable,
6} from 'mobx';
7
8import defaultUserAgent, { isChromeless } from '../helpers/userAgent-helpers';
9
10const debug = require('debug')('Ferdi:UserAgent');
11
12export default class UserAgent {
13 _willNavigateListener = null;
14
15 _didNavigateListener = null;
16
17 @observable.ref webview = null;
18
19 @observable chromelessUserAgent = false;
20
21 @observable getUserAgent = defaultUserAgent;
22
23 constructor(overrideUserAgent = null) {
24 if (typeof overrideUserAgent === 'function') {
25 this.getUserAgent = overrideUserAgent;
26 }
27
28 observe(this, 'webview', (change) => {
29 const { oldValue, newValue } = change;
30 if (oldValue !== null) {
31 this._removeWebviewEvents(oldValue);
32 }
33 if (newValue !== null) {
34 this._addWebviewEvents(newValue);
35 }
36 });
37 }
38
39 @computed get userAgent() {
40 return this.chromelessUserAgent ? defaultUserAgent(true) : this.getUserAgent();
41 }
42
43 @action setWebviewReference(webview) {
44 this.webview = webview;
45 }
46
47 @action _handleNavigate(url, forwardingHack = false) {
48 if (isChromeless(url)) {
49 if (!this.chromelessUserAgent) {
50 debug('Setting user agent to chromeless for url', url);
51 this.chromelessUserAgent = true;
52 this.webview.userAgent = this.userAgent;
53 if (forwardingHack) {
54 this.webview.loadURL(url);
55 }
56 }
57 } else if (this.chromelessUserAgent) {
58 debug('Setting user agent to contain chrome for url', url);
59 this.chromelessUserAgent = false;
60 this.webview.userAgent = this.userAgent;
61 }
62 }
63
64 _addWebviewEvents(webview) {
65 debug('Adding event handlers');
66
67 this._willNavigateListener = event => this._handleNavigate(event.url, true);
68 webview.addEventListener('will-navigate', this._willNavigateListener);
69
70 this._didNavigateListener = event => this._handleNavigate(event.url);
71 webview.addEventListener('did-navigate', this._didNavigateListener);
72 }
73
74 _removeWebviewEvents(webview) {
75 debug('Removing event handlers');
76
77 webview.removeEventListener('will-navigate', this._willNavigateListener);
78 webview.removeEventListener('did-navigate', this._didNavigateListener);
79 }
80}