[FIX] bus: websocket worker from different origin

Before this commit, the bus service would fail to initialize its worker
when loaded from a different origin than the worker script URL.
Moreover, the websocket URL was assumed to be on the same domain that
the bus service.

This PR fixes both issues:
- When the bus service is loaded from a different origin than the worker
script URL, create a data URL and use "importScripts" inside the worker
since it is not restricted to same origin scripts.
- Pass the websocketURL to the worker during its initialization.

opw-3118394

closes odoo/odoo#109701

X-original-commit: c1a480bb9ec133b9f94a5bb505380216a7f9c4ce
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
tsm-odoo
2023-01-12 01:27:47 +01:00
committed by Alexandre Kühn
parent 75c98684fe
commit cface47e4e
5 changed files with 42 additions and 11 deletions
+12 -1
View File
@@ -6,6 +6,7 @@ import { registry } from '@web/core/registry';
import { session } from '@web/session';
import { isIosApp } from '@web/core/browser/feature_detection';
import { WORKER_VERSION } from "@bus/workers/websocket_worker";
import legacySession from "web.session";
const { EventBus } = owl;
@@ -29,8 +30,17 @@ export const busService = {
multiTab.removeSharedValue('last_notification_id');
}
const bus = new EventBus();
let workerURL = `${legacySession.prefix}/bus/websocket_worker_bundle?v=${WORKER_VERSION}`;
if (legacySession.prefix !== window.origin) {
// Bus service is loaded from a different origin than the bundle
// URL. The Worker expects an URL from this origin, give it a base64
// URL that will then load the bundle via "importScripts" which
// allows cross origin.
const source = `importScripts("${workerURL}");`;
workerURL = 'data:application/javascript;base64,' + window.btoa(source);
}
const workerClass = 'SharedWorker' in window && !isIosApp() ? browser.SharedWorker : browser.Worker;
const worker = new workerClass(`/bus/websocket_worker_bundle?v=${WORKER_VERSION}`, {
const worker = new workerClass(workerURL, {
name: 'SharedWorker' in window && !isIosApp() ? 'odoo:websocket_shared_worker' : 'odoo:websocket_worker',
});
const connectionInitializedDeferred = new Deferred();
@@ -95,6 +105,7 @@ export const busService = {
uid = false;
}
send('initialize_connection', {
websocketURL: `${legacySession.prefix.replace("http", "ws")}/websocket`,
debug: odoo.debug,
lastNotificationId: multiTab.getSharedValue('last_notification_id', 0),
uid,
@@ -34,7 +34,7 @@ export const WEBSOCKET_CLOSE_CODES = Object.freeze({
});
// Should be incremented on every worker update in order to force
// update of the worker in browser cache.
export const WORKER_VERSION = "1.0.0";
export const WORKER_VERSION = "1.0.1";
/**
* This class regroups the logic necessary in order for the
@@ -44,8 +44,8 @@ export const WORKER_VERSION = "1.0.0";
* for SharedWorker and this class implements it.
*/
export class WebsocketWorker {
constructor(websocketURL) {
this.websocketURL = websocketURL;
constructor() {
this.websocketURL = "";
this.currentUID = null;
this.isWaitingForNewUID = true;
this.channelsByClient = new Map();
@@ -200,12 +200,14 @@ export class WebsocketWorker {
* given client.
* @param {Number} [param0.lastNotificationId] Last notification id
* known by the client.
* @param {String} [param0.websocketURL] URL of the websocket endpoint.
* @param {Number|false|undefined} [param0.uid] Current user id
* - Number: user is logged whether on the frontend/backend.
* - false: user is not logged.
* - undefined: not available (e.g. livechat support page)
*/
_initializeConnection(client, { debug, lastNotificationId, uid }) {
_initializeConnection(client, { debug, lastNotificationId, uid, websocketURL }) {
this.websocketURL = websocketURL;
this.lastNotificationId = lastNotificationId;
this.debugModeByClient[client] = debug;
this.isDebug = Object.values(this.debugModeByClient).some(debugValue => debugValue !== '');
@@ -5,9 +5,7 @@
import { WebsocketWorker } from "./websocket_worker";
(function () {
const websocketWorker = new WebsocketWorker(
`${self.location.protocol === 'https:' ? 'wss' : 'ws'}://${self.location.host}/websocket`
);
const websocketWorker = new WebsocketWorker();
if (self.name.includes('shared')) {
// The script is running in a shared worker: let's register every
+21
View File
@@ -13,6 +13,7 @@ const { registry } = require("@web/core/registry");
const { session } = require('@web/session');
const { makeDeferred, nextTick, patchWithCleanup } = require("@web/../tests/helpers/utils");
const { makeTestEnv } = require('@web/../tests/helpers/mock_env');
const legacySession = require('web.session');
QUnit.module('Bus', {
beforeEach: function () {
@@ -461,6 +462,26 @@ QUnit.module('Bus', {
'connect',
]);
});
QUnit.test("WebSocket connects with URL corresponding to session prefix", async function (assert) {
patchWebsocketWorkerWithCleanup();
const origin = "http://random-website.com";
patchWithCleanup(legacySession, {
prefix: origin,
});
const websocketCreatedDeferred = makeDeferred();
patchWithCleanup(window, {
WebSocket: function (url) {
assert.step(url);
websocketCreatedDeferred.resolve();
return new EventTarget();
},
}, { pure: true });
const env = await makeTestEnv();
env.services["bus_service"].start();
await websocketCreatedDeferred;
assert.verifySteps([`${origin.replace("http", "ws")}/websocket`]);
});
});
});
@@ -6,10 +6,9 @@ import { patchWithCleanup } from "@web/../tests/helpers/utils";
import { registerCleanup } from "@web/../tests/helpers/cleanup";
class WebSocketMock extends EventTarget {
constructor(url) {
constructor() {
super();
this.readyState = 0;
this.url = url;
queueMicrotask(() => {
this.readyState = 1;
@@ -71,7 +70,7 @@ export function patchWebsocketWorkerWithCleanup(params = {}) {
},
}, { pure: true });
patchWithCleanup(websocketWorker || WebsocketWorker.prototype, params);
websocketWorker = websocketWorker || new WebsocketWorker('wss://odoo.com/websocket');
websocketWorker = websocketWorker || new WebsocketWorker();
patchWithCleanup(browser, {
SharedWorker: function () {
const sharedWorker = new SharedWorkerMock(websocketWorker);