)]}' {"version": 3, "sources": ["/web/static/src/legacy/js/promise_extension.js", "/web/static/src/boot.js", "/bus/static/src/workers/websocket_worker.js", "/bus/static/src/workers/websocket_worker_script.js", "/bus/static/src/workers/websocket_worker_utils.js"], "mappings": "AAAA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACvBA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5bA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvbA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA", "sourcesContent": ["/**\n * This file adds a 'guardedCatch' function to the Promise API. This function\n * has to be used when we don't want to swallow real errors (crashes), like\n * 'catch' does (i.e. basically all the time in Odoo). We only execute the\n * 'onRejected' handler if the rejection's reason is not an Error, and we always\n * return a rejected Promise to let the rejection bubble up (and trigger the\n * 'unhandledrejection' event).\n */\n\n(function () {\n var _catch = Promise.prototype.catch;\n Promise.prototype.guardedCatch = function (onRejected) {\n return _catch.call(this, function (reason) {\n const error = (reason instanceof Error && \"cause\" in reason) ? reason.cause : reason;\n if (!error || !(error instanceof Error)) {\n if (onRejected) {\n onRejected.call(this, reason);\n }\n }\n return Promise.reject(reason);\n });\n };\n})();\n", "/**\n *------------------------------------------------------------------------------\n * Odoo Web Boostrap Code\n *------------------------------------------------------------------------------\n *\n * Each module can return a promise. In that case, the module is marked as loaded\n * only when the promise is resolved, and its value is equal to the resolved value.\n * The module can be rejected (unloaded). This will be logged in the console as info.\n *\n * logs:\n * Missing dependencies:\n * These modules do not appear in the page. It is possible that the\n * JavaScript file is not in the page or that the module name is wrong\n * Failed modules:\n * A javascript error is detected\n * Rejected modules:\n * The module returns a rejected promise. It (and its dependent modules)\n * is not loaded.\n * Rejected linked modules:\n * Modules who depend on a rejected module\n * Non loaded modules:\n * Modules who depend on a missing or a failed module\n * Debug:\n * Non loaded or failed module informations for debugging\n */\n(function () {\n \"use strict\";\n\n var jobUID = Date.now();\n\n var jobs = [];\n var factories = Object.create(null);\n var jobDeps = [];\n var jobPromises = [];\n\n var services = Object.create({});\n\n var commentRegExp = /(\\/\\*([\\s\\S]*?)\\*\\/|([^:]|^)\\/\\/(.*)$)/gm;\n var cjsRequireRegExp = /[^.]\\s*require\\s*\\(\\s*[\"']([^'\"\\s]+)[\"']\\s*\\)/g;\n if (!globalThis.odoo) {\n globalThis.odoo = {};\n }\n var odoo = globalThis.odoo;\n var debug = odoo.debug;\n\n var didLogInfoResolve;\n var didLogInfoPromise = new Promise(function (resolve) {\n didLogInfoResolve = resolve;\n });\n\n odoo.remainingJobs = jobs;\n odoo.__DEBUG__ = {\n didLogInfo: didLogInfoPromise,\n getDependencies: function (name, transitive) {\n var deps = name instanceof Array ? name : [name];\n var changed;\n do {\n changed = false;\n jobDeps.forEach(function (dep) {\n if (deps.indexOf(dep.to) >= 0 && deps.indexOf(dep.from) < 0) {\n deps.push(dep.from);\n changed = true;\n }\n });\n } while (changed && transitive);\n return deps;\n },\n getDependents: function (name) {\n return jobDeps\n .filter(function (dep) {\n return dep.from === name;\n })\n .map(function (dep) {\n return dep.to;\n });\n },\n getWaitedJobs: function () {\n return jobs\n .map(function (job) {\n return job.name;\n })\n .filter(function (item, index, self) {\n // uniq\n return self.indexOf(item) === index;\n });\n },\n getMissingJobs: function () {\n var self = this;\n var waited = this.getWaitedJobs();\n var missing = [];\n waited.forEach(function (job) {\n self.getDependencies(job).forEach(function (job) {\n if (!(job in self.services)) {\n missing.push(job);\n }\n });\n });\n return missing\n .filter(function (item, index, self) {\n return self.indexOf(item) === index;\n })\n .filter(function (item) {\n return waited.indexOf(item) < 0;\n })\n .filter(function (job) {\n return !job.error;\n });\n },\n getFailedJobs: function () {\n return jobs.filter(function (job) {\n return !!job.error;\n });\n },\n processJobs: function () {\n var job;\n\n function processJob(job) {\n var require = makeRequire(job);\n\n var jobExec;\n function onError(e) {\n job.error = e;\n console.error(`Error while loading ${job.name}: ${e.message}`, e);\n Promise.reject(e);\n }\n var def = new Promise(function (resolve) {\n try {\n jobExec = job.factory.call(null, require);\n jobs.splice(jobs.indexOf(job), 1);\n } catch (e) {\n onError(e);\n }\n if (!job.error) {\n Promise.resolve(jobExec)\n .then(function (data) {\n services[job.name] = data;\n resolve();\n odoo.__DEBUG__.processJobs();\n })\n .guardedCatch(function (e) {\n job.rejected = e || true;\n jobs.push(job);\n })\n .catch(function (e) {\n if (e instanceof Error) {\n onError(e);\n }\n resolve();\n });\n } else {\n resolve();\n }\n });\n jobPromises.push(def);\n def.then(job.resolve);\n }\n\n function isReady(job) {\n return (\n !job.error &&\n !job.rejected &&\n job.factory.deps.every(function (name) {\n return name in services;\n })\n );\n }\n\n function makeRequire(job) {\n var deps = {};\n Object.keys(services)\n .filter(function (item) {\n return job.deps.indexOf(item) >= 0;\n })\n .forEach(function (key) {\n deps[key] = services[key];\n });\n\n return function require(name) {\n if (!(name in deps)) {\n console.error(\"Undefined dependency: \", name);\n }\n return deps[name];\n };\n }\n\n while (jobs.length) {\n job = undefined;\n for (var i = 0; i < jobs.length; i++) {\n if (isReady(jobs[i])) {\n job = jobs[i];\n break;\n }\n }\n if (!job) {\n break;\n }\n processJob(job);\n }\n\n return services;\n },\n factories: factories,\n services: services,\n };\n odoo.define = function () {\n var args = Array.prototype.slice.call(arguments);\n var name = typeof args[0] === \"string\" ? args.shift() : \"__odoo_job\" + jobUID++;\n var factory = args[args.length - 1];\n var deps;\n if (args[0] instanceof Array) {\n deps = args[0];\n } else {\n deps = [];\n factory\n .toString()\n .replace(commentRegExp, \"\")\n .replace(cjsRequireRegExp, function (match, dep) {\n deps.push(dep);\n });\n }\n\n if (!(deps instanceof Array)) {\n throw new Error(\"Dependencies should be defined by an array\", deps);\n }\n if (typeof factory !== \"function\") {\n throw new Error(\"Factory should be defined by a function\", factory);\n }\n if (typeof name !== \"string\") {\n throw new Error(\"Invalid name definition (should be a string\", name);\n }\n if (name in factories) {\n throw new Error(\"Service \" + name + \" already defined\");\n }\n\n factory.deps = deps;\n factories[name] = factory;\n\n let promiseResolve;\n const promise = new Promise((resolve) => {\n promiseResolve = resolve;\n });\n jobs.push({\n name: name,\n factory: factory,\n deps: deps,\n resolve: promiseResolve,\n promise: promise,\n });\n\n deps.forEach(function (dep) {\n jobDeps.push({ from: dep, to: name });\n });\n\n odoo.__DEBUG__.processJobs();\n };\n odoo.log = function () {\n var missing = [];\n var failed = [];\n var cycle = null;\n\n if (jobs.length) {\n var debugJobs = {};\n var rejected = [];\n var rejectedLinked = [];\n var job;\n var jobdep;\n\n for (var k = 0; k < jobs.length; k++) {\n debugJobs[jobs[k].name] = job = {\n dependencies: jobs[k].deps,\n dependents: odoo.__DEBUG__.getDependents(jobs[k].name),\n name: jobs[k].name,\n };\n if (jobs[k].error) {\n job.error = jobs[k].error;\n }\n if (jobs[k].rejected) {\n job.rejected = jobs[k].rejected;\n rejected.push(job.name);\n }\n var deps = odoo.__DEBUG__.getDependencies(job.name);\n for (var i = 0; i < deps.length; i++) {\n if (job.name !== deps[i] && !(deps[i] in services)) {\n jobdep = debugJobs[deps[i]];\n if (!jobdep && deps[i] in factories) {\n for (var j = 0; j < jobs.length; j++) {\n if (jobs[j].name === deps[i]) {\n jobdep = jobs[j];\n break;\n }\n }\n }\n if (jobdep && jobdep.rejected) {\n if (!job.rejected) {\n job.rejected = [];\n rejectedLinked.push(job.name);\n }\n job.rejected.push(deps[i]);\n } else {\n if (!job.missing) {\n job.missing = [];\n }\n job.missing.push(deps[i]);\n }\n }\n }\n }\n missing = odoo.__DEBUG__.getMissingJobs();\n failed = odoo.__DEBUG__.getFailedJobs();\n var unloaded = Object.keys(debugJobs) // Object.values is not supported\n .map(function (key) {\n return debugJobs[key];\n })\n .filter(function (job) {\n return job.missing;\n });\n\n if (debug || failed.length || unloaded.length) {\n var log = globalThis.console[\n !failed.length || !unloaded.length ? \"info\" : \"error\"\n ].bind(globalThis.console);\n log(\n (failed.length ? \"error\" : unloaded.length ? \"warning\" : \"info\") +\n \": Some modules could not be started\"\n );\n if (missing.length) {\n log(\"Missing dependencies: \", missing);\n }\n if (failed.length) {\n log(\n \"Failed modules: \",\n failed.map(function (fail) {\n return fail.name;\n })\n );\n }\n if (rejected.length) {\n log(\"Rejected modules: \", rejected);\n }\n if (rejectedLinked.length) {\n log(\"Rejected linked modules: \", rejectedLinked);\n }\n if (unloaded.length) {\n cycle = findCycle(unloaded);\n if (cycle) {\n console.error(\"Cyclic dependencies: \" + cycle);\n }\n log(\n \"Non loaded modules: \",\n unloaded.map(function (unload) {\n return unload.name;\n })\n );\n }\n if (debug && Object.keys(debugJobs).length) {\n log(\"Debug: \", debugJobs);\n }\n }\n }\n odoo.__DEBUG__.jsModules = {\n missing: missing,\n failed: failed.map((mod) => mod.name),\n unloaded: unloaded ? unloaded.map((mod) => mod.name) : [],\n cycle,\n };\n didLogInfoResolve(true);\n };\n /**\n * Returns a resolved promise when the targeted services are loaded.\n * If no service is found the promise is used directly.\n *\n * @param {string|RegExp} serviceName name of the service to expect\n * or regular expression matching the service.\n * @returns {Promise} resolved when the services ares\n * loaded. The value is equal to the number of services found.\n */\n odoo.ready = async function (serviceName) {\n function match(name) {\n return typeof serviceName === \"string\" ? name === serviceName : serviceName.test(name);\n }\n await Promise.all(jobs.filter((job) => match(job.name)).map((job) => job.promise));\n return Object.keys(factories).filter(match).length;\n };\n\n odoo.runtimeImport = function (moduleName) {\n if (!(moduleName in services)) {\n throw new Error(`Service \"${moduleName} is not defined or isn't finished loading.\"`);\n }\n return services[moduleName];\n };\n\n // Automatically log errors detected when loading modules\n globalThis.addEventListener(\"load\", function logWhenLoaded() {\n const len = jobPromises.length;\n Promise.all(jobPromises).then(function () {\n if (len === jobPromises.length) {\n odoo.log();\n } else {\n logWhenLoaded();\n }\n });\n });\n\n /**\n * Visit the list of jobs, and return the first found cycle, if any\n *\n * @param {any[]} jobs\n * @returns {null | string} either a string describing a cycle, or null\n */\n function findCycle(jobs) {\n // build dependency graph\n const dependencyGraph = new Map();\n for (const job of jobs) {\n dependencyGraph.set(job.name, job.dependencies);\n }\n\n // helpers\n function visitJobs(jobs, visited = new Set()) {\n for (const job of jobs) {\n const result = visitJob(job, visited);\n if (result) {\n return result;\n }\n }\n return null;\n }\n\n function visitJob(job, visited) {\n if (visited.has(job)) {\n const jobs = Array.from(visited).concat([job]);\n const index = jobs.indexOf(job);\n return jobs\n .slice(index)\n .map((j) => `\"${j}\"`)\n .join(\" => \");\n }\n const deps = dependencyGraph.get(job);\n return deps ? visitJobs(deps, new Set(visited).add(job)) : null;\n }\n\n // visit each root to find cycles\n return visitJobs(jobs.map((j) => j.name));\n }\n})();\n", "/** @odoo-module **/\n\nimport { debounce } from '@bus/workers/websocket_worker_utils';\n\n/**\n * Type of events that can be sent from the worker to its clients.\n *\n * @typedef { 'connect' | 'reconnect' | 'disconnect' | 'reconnecting' | 'notification' | 'initialized' } WorkerEvent\n */\n\n/**\n * Type of action that can be sent from the client to the worker.\n *\n * @typedef {'add_channel' | 'delete_channel' | 'force_update_channels' | 'initialize_connection' | 'send' | 'leave' | 'stop' | 'start' } WorkerAction\n */\n\nexport const WEBSOCKET_CLOSE_CODES = Object.freeze({\n CLEAN: 1000,\n GOING_AWAY: 1001,\n PROTOCOL_ERROR: 1002,\n INCORRECT_DATA: 1003,\n ABNORMAL_CLOSURE: 1006,\n INCONSISTENT_DATA: 1007,\n MESSAGE_VIOLATING_POLICY: 1008,\n MESSAGE_TOO_BIG: 1009,\n EXTENSION_NEGOTIATION_FAILED: 1010,\n SERVER_ERROR: 1011,\n RESTART: 1012,\n TRY_LATER: 1013,\n BAD_GATEWAY: 1014,\n SESSION_EXPIRED: 4001,\n KEEP_ALIVE_TIMEOUT: 4002,\n RECONNECTING: 4003,\n});\n// Should be incremented on every worker update in order to force\n// update of the worker in browser cache.\nexport const WORKER_VERSION = '1.0.5';\nconst INITIAL_RECONNECT_DELAY = 1000;\nconst MAXIMUM_RECONNECT_DELAY = 60000;\n\n/**\n * This class regroups the logic necessary in order for the\n * SharedWorker/Worker to work. Indeed, Safari and some minor browsers\n * do not support SharedWorker. In order to solve this issue, a Worker\n * is used in this case. The logic is almost the same than the one used\n * for SharedWorker and this class implements it.\n */\nexport class WebsocketWorker {\n constructor() {\n // Timestamp of start of most recent bus service sender\n this.newestStartTs = undefined;\n this.websocketURL = \"\";\n this.currentUID = null;\n this.isWaitingForNewUID = true;\n this.channelsByClient = new Map();\n this.connectRetryDelay = INITIAL_RECONNECT_DELAY;\n this.connectTimeout = null;\n this.debugModeByClient = new Map();\n this.isDebug = false;\n this.isReconnecting = false;\n this.lastChannelSubscription = null;\n this.lastNotificationId = 0;\n this.messageWaitQueue = [];\n this._forceUpdateChannels = debounce(this._forceUpdateChannels, 300, true);\n\n this._onWebsocketClose = this._onWebsocketClose.bind(this);\n this._onWebsocketError = this._onWebsocketError.bind(this);\n this._onWebsocketMessage = this._onWebsocketMessage.bind(this);\n this._onWebsocketOpen = this._onWebsocketOpen.bind(this);\n }\n\n //--------------------------------------------------------------------------\n // Public\n //--------------------------------------------------------------------------\n\n /**\n * Send the message to all the clients that are connected to the\n * worker.\n *\n * @param {WorkerEvent} type Event to broadcast to connected\n * clients.\n * @param {Object} data\n */\n broadcast(type, data) {\n for (const client of this.channelsByClient.keys()) {\n client.postMessage({ type, data });\n }\n }\n\n /**\n * Register a client handled by this worker.\n *\n * @param {MessagePort} messagePort\n */\n registerClient(messagePort) {\n messagePort.onmessage = ev => {\n this._onClientMessage(messagePort, ev.data);\n };\n this.channelsByClient.set(messagePort, []);\n }\n\n /**\n * Send message to the given client.\n *\n * @param {number} client\n * @param {WorkerEvent} type\n * @param {Object} data\n */\n sendToClient(client, type, data) {\n client.postMessage({ type, data });\n }\n\n //--------------------------------------------------------------------------\n // PRIVATE\n //--------------------------------------------------------------------------\n\n /**\n * Called when a message is posted to the worker by a client (i.e. a\n * MessagePort connected to this worker).\n *\n * @param {MessagePort} client\n * @param {Object} message\n * @param {WorkerAction} [message.action]\n * Action to execute.\n * @param {Object|undefined} [message.data] Data required by the\n * action.\n */\n _onClientMessage(client, { action, data }) {\n switch (action) {\n case 'send':\n return this._sendToServer(data);\n case 'start':\n return this._start();\n case 'stop':\n return this._stop();\n case 'leave':\n return this._unregisterClient(client);\n case 'add_channel':\n return this._addChannel(client, data);\n case 'delete_channel':\n return this._deleteChannel(client, data);\n case 'force_update_channels':\n return this._forceUpdateChannels();\n case 'initialize_connection':\n return this._initializeConnection(client, data);\n }\n }\n\n /**\n * Add a channel for the given client. If this channel is not yet\n * known, update the subscription on the server.\n *\n * @param {MessagePort} client\n * @param {string} channel\n */\n _addChannel(client, channel) {\n const clientChannels = this.channelsByClient.get(client);\n if (!clientChannels.includes(channel)) {\n clientChannels.push(channel);\n this.channelsByClient.set(client, clientChannels);\n this._updateChannels();\n }\n }\n\n /**\n * Remove a channel for the given client. If this channel is not\n * used anymore, update the subscription on the server.\n *\n * @param {MessagePort} client\n * @param {string} channel\n */\n _deleteChannel(client, channel) {\n const clientChannels = this.channelsByClient.get(client);\n if (!clientChannels) {\n return;\n }\n const channelIndex = clientChannels.indexOf(channel);\n if (channelIndex !== -1) {\n clientChannels.splice(channelIndex, 1);\n this._updateChannels();\n }\n }\n\n /**\n * Update the channels on the server side even if the channels on\n * the client side are the same than the last time we subscribed.\n */\n _forceUpdateChannels() {\n this._updateChannels({ force: true });\n }\n\n /**\n * Remove the given client from this worker client list as well as\n * its channels. If some of its channels are not used anymore,\n * update the subscription on the server.\n *\n * @param {MessagePort} client\n */\n _unregisterClient(client) {\n this.channelsByClient.delete(client);\n this.debugModeByClient.delete(client);\n this.isDebug = Object.values(this.debugModeByClient).some(debugValue => debugValue !== '');\n this._updateChannels();\n }\n\n /**\n * Initialize a client connection to this worker.\n *\n * @param {Object} param0\n * @param {String} [param0.debug] Current debugging mode for the\n * given client.\n * @param {Number} [param0.lastNotificationId] Last notification id\n * known by the client.\n * @param {String} [param0.websocketURL] URL of the websocket endpoint.\n * @param {Number|false|undefined} [param0.uid] Current user id\n * - Number: user is logged whether on the frontend/backend.\n * - false: user is not logged.\n * - undefined: not available (e.g. livechat support page)\n * @param {Number} param0.startTs Timestamp of start of bus service sender.\n */\n _initializeConnection(client, { debug, lastNotificationId, uid, websocketURL, startTs }) {\n if (this.newestStartTs && this.newestStartTs > startTs) {\n this.debugModeByClient[client] = debug;\n this.isDebug = Object.values(this.debugModeByClient).some(debugValue => debugValue !== '');\n this.sendToClient(client, \"initialized\");\n return;\n }\n this.newestStartTs = startTs;\n this.websocketURL = websocketURL;\n this.lastNotificationId = lastNotificationId;\n this.debugModeByClient[client] = debug;\n this.isDebug = Object.values(this.debugModeByClient).some(debugValue => debugValue !== '');\n const isCurrentUserKnown = uid !== undefined;\n if (this.isWaitingForNewUID && isCurrentUserKnown) {\n this.isWaitingForNewUID = false;\n this.currentUID = uid;\n }\n if (this.currentUID !== uid && isCurrentUserKnown) {\n this.currentUID = uid;\n if (this.websocket) {\n this.websocket.close(WEBSOCKET_CLOSE_CODES.CLEAN);\n }\n this.channelsByClient.forEach((_, key) => this.channelsByClient.set(key, []));\n }\n this.sendToClient(client, 'initialized');\n }\n\n /**\n * Determine whether or not the websocket associated to this worker\n * is connected.\n *\n * @returns {boolean}\n */\n _isWebsocketConnected() {\n return this.websocket && this.websocket.readyState === 1;\n }\n\n /**\n * Determine whether or not the websocket associated to this worker\n * is connecting.\n *\n * @returns {boolean}\n */\n _isWebsocketConnecting() {\n return this.websocket && this.websocket.readyState === 0;\n }\n\n /**\n * Determine whether or not the websocket associated to this worker\n * is in the closing state.\n *\n * @returns {boolean}\n */\n _isWebsocketClosing() {\n return this.websocket && this.websocket.readyState === 2;\n }\n\n /**\n * Triggered when a connection is closed. If closure was not clean ,\n * try to reconnect after indicating to the clients that the\n * connection was closed.\n *\n * @param {CloseEvent} ev\n * @param {number} code close code indicating why the connection\n * was closed.\n * @param {string} reason reason indicating why the connection was\n * closed.\n */\n _onWebsocketClose({ code, reason }) {\n if (this.isDebug) {\n console.debug(`%c${new Date().toLocaleString()} - [onClose]`, 'color: #c6e; font-weight: bold;', code, reason);\n }\n this.lastChannelSubscription = null;\n if (this.isReconnecting) {\n // Connection was not established but the close event was\n // triggered anyway. Let the onWebsocketError method handle\n // this case.\n return;\n }\n this.broadcast('disconnect', { code, reason });\n if (code === WEBSOCKET_CLOSE_CODES.CLEAN) {\n // WebSocket was closed on purpose, do not try to reconnect.\n return;\n }\n // WebSocket was not closed cleanly, let's try to reconnect.\n this.broadcast('reconnecting', { closeCode: code });\n this.isReconnecting = true;\n if (code === WEBSOCKET_CLOSE_CODES.KEEP_ALIVE_TIMEOUT) {\n // Don't wait to reconnect on keep alive timeout.\n this.connectRetryDelay = 0;\n }\n if (code === WEBSOCKET_CLOSE_CODES.SESSION_EXPIRED) {\n this.isWaitingForNewUID = true;\n }\n this._retryConnectionWithDelay();\n }\n\n /**\n * Triggered when a connection failed or failed to established.\n */\n _onWebsocketError() {\n if (this.isDebug) {\n console.debug(`%c${new Date().toLocaleString()} - [onError]`, 'color: #c6e; font-weight: bold;');\n }\n this._retryConnectionWithDelay();\n }\n\n /**\n * Handle data received from the bus.\n *\n * @param {MessageEvent} messageEv\n */\n _onWebsocketMessage(messageEv) {\n const notifications = JSON.parse(messageEv.data);\n if (this.isDebug) {\n console.debug(`%c${new Date().toLocaleString()} - [onMessage]`, 'color: #c6e; font-weight: bold;', notifications);\n }\n this.lastNotificationId = notifications[notifications.length - 1].id;\n this.broadcast('notification', notifications);\n }\n\n /**\n * Triggered on websocket open. Send message that were waiting for\n * the connection to open.\n */\n _onWebsocketOpen() {\n if (this.isDebug) {\n console.debug(`%c${new Date().toLocaleString()} - [onOpen]`, 'color: #c6e; font-weight: bold;');\n }\n this._updateChannels();\n this.messageWaitQueue.forEach(msg => this.websocket.send(msg));\n this.messageWaitQueue = [];\n this.broadcast(this.isReconnecting ? 'reconnect' : 'connect');\n this.connectRetryDelay = INITIAL_RECONNECT_DELAY;\n this.connectTimeout = null;\n this.isReconnecting = false;\n }\n\n /**\n * Try to reconnect to the server, an exponential back off is\n * applied to the reconnect attempts.\n */\n _retryConnectionWithDelay() {\n this.connectRetryDelay = Math.min(this.connectRetryDelay * 1.5, MAXIMUM_RECONNECT_DELAY) + 1000 * Math.random();\n this.connectTimeout = setTimeout(this._start.bind(this), this.connectRetryDelay);\n }\n\n /**\n * Send a message to the server through the websocket connection.\n * If the websocket is not open, enqueue the message and send it\n * upon the next reconnection.\n *\n * @param {any} message Message to send to the server.\n */\n _sendToServer(message) {\n const payload = JSON.stringify(message);\n if (!this._isWebsocketConnected()) {\n this.messageWaitQueue.push(payload);\n } else {\n this.websocket.send(payload);\n }\n }\n\n /**\n * Start the worker by opening a websocket connection.\n */\n _start() {\n if (this._isWebsocketConnected() || this._isWebsocketConnecting()) {\n return;\n }\n if (this.websocket) {\n this.websocket.removeEventListener('open', this._onWebsocketOpen);\n this.websocket.removeEventListener('message', this._onWebsocketMessage);\n this.websocket.removeEventListener('error', this._onWebsocketError);\n this.websocket.removeEventListener('close', this._onWebsocketClose);\n }\n if (this._isWebsocketClosing()) {\n // close event was not triggered and will never be, broadcast the\n // disconnect event for consistency sake.\n this.lastChannelSubscription = null;\n this.broadcast(\"disconnect\", { code: WEBSOCKET_CLOSE_CODES.ABNORMAL_CLOSURE });\n }\n this.websocket = new WebSocket(this.websocketURL);\n this.websocket.addEventListener('open', this._onWebsocketOpen);\n this.websocket.addEventListener('error', this._onWebsocketError);\n this.websocket.addEventListener('message', this._onWebsocketMessage);\n this.websocket.addEventListener('close', this._onWebsocketClose);\n }\n\n /**\n * Stop the worker.\n */\n _stop() {\n clearTimeout(this.connectTimeout);\n this.connectRetryDelay = INITIAL_RECONNECT_DELAY;\n this.isReconnecting = false;\n this.lastChannelSubscription = null;\n if (this.websocket) {\n this.websocket.close();\n }\n }\n\n /**\n * Update the channel subscription on the server. Ignore if the channels\n * did not change since the last subscription.\n *\n * @param {boolean} force Whether or not we should update the subscription\n * event if the channels haven't change since last subscription.\n */\n _updateChannels({ force = false } = {}) {\n const allTabsChannels = [...new Set([].concat.apply([], [...this.channelsByClient.values()]))].sort();\n const allTabsChannelsString = JSON.stringify(allTabsChannels);\n const shouldUpdateChannelSubscription = allTabsChannelsString !== this.lastChannelSubscription;\n if (force || shouldUpdateChannelSubscription) {\n this.lastChannelSubscription = allTabsChannelsString;\n this._sendToServer({ event_name: 'subscribe', data: { channels: allTabsChannels, last: this.lastNotificationId } });\n }\n }\n}\n", "/** @odoo-module **/\n/* eslint-env worker */\n/* eslint-disable no-restricted-globals */\n\nimport { WebsocketWorker } from \"./websocket_worker\";\n\n(function () {\n const websocketWorker = new WebsocketWorker();\n\n if (self.name.includes('shared')) {\n // The script is running in a shared worker: let's register every\n // tab connection to the worker in order to relay notifications\n // coming from the websocket.\n onconnect = function (ev) {\n const currentClient = ev.ports[0];\n websocketWorker.registerClient(currentClient);\n };\n } else {\n // The script is running in a simple web worker.\n websocketWorker.registerClient(self);\n }\n})();\n\n", "/** @odoo-module **/\n\n/**\n * Returns a function, that, as long as it continues to be invoked, will not\n * be triggered. The function will be called after it stops being called for\n * N milliseconds. If `immediate` is passed, trigger the function on the\n * leading edge, instead of the trailing.\n *\n * Inspired by https://davidwalsh.name/javascript-debounce-function\n */\n export function debounce(func, wait, immediate) {\n let timeout;\n return function () {\n const context = this;\n const args = arguments;\n function later() {\n timeout = null;\n if (!immediate) {\n func.apply(context, args);\n }\n }\n const callNow = immediate && !timeout;\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n if (callNow) {\n func.apply(context, args);\n }\n };\n}\n"], "file": "/web/assets/3551799-c9895bf/bus.websocket_worker_assets.js", "sourceRoot": "../../../"}