[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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user