Skip to content

Instantly share code, notes, and snippets.

@bogdanRada
Forked from willywongi/1-echo.js
Created April 6, 2022 20:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bogdanRada/e9411f357d81f595e81848abcf8549cb to your computer and use it in GitHub Desktop.
Save bogdanRada/e9411f357d81f595e81848abcf8549cb to your computer and use it in GitHub Desktop.
Load a Worker from a different domain (CORS + tricks)
/*
You want to use webworkers, but you host all of your js files on a different domain than where
your app lives (es. app.example.com and js.example.com). Given that the static file served from
js.example.com are CORS ready (in that the response header includes Accept-origin: *), I thought
I could load a Worker from other domains. I was almost wrong, but at last I found a solution:
XHRing the worker source and create an inline worker. This is tested only on Firefox (latest).
*/
// This is an example webworker that is on js.example.com. It just echoes messages it receive.
self.onmessage = function(e) {
self.postMessage(e.data);
}
<!DOCTYPE html>
<!--
This page is on app.example.com; we try to use a worker that is on js.example.com.
-->
<head>
<!-- Loading an external script through a <script> node doesn't work -->
<script src="http://js.example.com/get_worker_echo.js"></script>
<!--
This inline worker sources are parsed later on; each of these workers loads another worker
when it receives a message like this: {url: 'http://some.url/to/worker.js'} and
responds with the same URL when it managed to load it.
Disclosure: this is not working; only loading through XHR the worker source works.
-->
<script id="static-load-worker" type="text/webworker">
self.onmessage = function(e) {
if (e.data.url) {
try {
self.subworker = new Worker(e.data.url);
} catch (e) {
self.postMessage({'error': 'cannot loaded subworker from url '+e.data.url})
}
if (self.subworker) {
self.subworker.onmessage = function(e) {
self.postMessage(e)
}
self.postMessage(e.data.url);
}
} else {
if (! self.subworker) {
self.postMessage({'error': 'subworker not loaded'});
} else {
self.subworker.postMessage(e)
}
}
}
</script>
<script id="import-worker" type="text/webworker">
self.onmessage = function(e) {
if (e.data.url) {
self.workerURL = e.data.url;
importScripts('http://js.example.it/loader.js');
}
}
</script>
<script>
// This simply won't work
var w = new Worker('http://js.example.it/echo.js');
function loadWorker(url, ready, scope) {
/* This creates a inline-worker that spans a subworker loading script from the desired URL.
inlineworker > subworker
This won't work because the subworker shares the origin with the parent (inline) worker,
and its origin is the same as the containing page (the URL you're visiting).
*/
var worker = new Worker(window.URL.createObjectURL(new Blob([document.getElementById('static-load-worker').innerHTML]))),
h = worker.addEventListener('message', function(e) {
if (e.data === url) {
ready.call(scope, worker);
}
});
worker.postMessage({'url': url});
return worker;
}
function importWorker(url, ready, scope) {
/* This creates an inline worker with static-load-worker (see before), which in
turns loads a script through importScripts; the imported script spans a new
subworker located at the desired URL.
inline worker > importScripts > loader.js > subworker
This won't work because the spanned subworked shares the same origin with the
parent worker, which in turn shares the origin with the worker that loaded it
even if it's imported through importScripts.
*/
var worker = new Worker(window.URL.createObjectURL(new Blob([document.getElementById('import-worker').innerHTML]))),
h = worker.addEventListener('message', function(e) {
if (e.data === url) {
ready.call(scope, worker);
}
});
worker.postMessage({'url': url});
return worker;
}
function XHRWorker(url, ready, scope) {
/* This loads the source of the worker through a XHR call. This is possible since the server
from which we pull the worker source serves files with CORS (Access-Control-Allow-Origin: *).
From the source (responseText) we build an inline worker.
This works but we need to delegeate using the worker when the resource is loaded (XHR call finishes)
*/
var oReq = new XMLHttpRequest();
oReq.addEventListener('load', function() {
var worker = new Worker(window.URL.createObjectURL(new Blob([this.responseText])));
if (ready) {
ready.call(scope, worker);
}
}, oReq);
oReq.open("get", url, true);
oReq.send();
}
function WorkerEcho() {
XHRWorker("http://js.example.it/echo.js", function(worker) {
this.worker = worker;
worker.onmessage = function(e) {
console.log(e.data);
}
}, this);
}
WorkerEcho.prototype = {
say: function(what) {
this.worker.postMessage(what);
}
}
</script>
</head>
/* Here you have: a HTML page on app.example.com with a script tag that points
to http://js.example.com/get_worker_echo.js; this script loads a worker on this
same domain, but it fails because at the end the worker has a different origin.
*/
window.EchoWorker = new Worker('http://js.example.com/echo.js');
/* This piece of code will be imported through importScripts, and will
behave exactly as if it were in place of the importScript call. */
var subworker = new Worker(self.workerURL);
subworker.onmessage = function(e) {
self.postMessage(e.data);
}
self.onmessage = function(e) {
subworker.postMessage(e.data);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment