Since @KevinGhadyani answer (or blob techniques) require to lessen your CSPs (by adding a worker-src data:
or blob:
directive, for example), there is a little example of how you can take advantage of importScripts
inside a worker to load another worker script hosted on another domain, without lessening your CSPs.
It may help you to load a worker from any CDN allowed by your CSPs.
As far as I know, it works on Opera, Firefox, Chrome, Edge and all browsers that support workers.
/**
* This worker allow us to import a script from our CDN as a worker
* avoiding to have to reduce security policy.
*/
/**
* Send a formated response to the main thread. Can handle regular errors.
* @param {('imported'|'error')} resp
* @param {*} data
*/
function respond(resp, data = undefined){
const msg = { resp };
if(data !== undefined){
if(data && typeof data === 'object'){
msg.data = {};
if(data instanceof Error){
msg.error = true;
msg.data.code = data.code;
msg.data.name = data.name;
msg.data.stack = data.stack.toString();
msg.data.message = data.message;
} else {
Object.assign(msg.data, data);
}
} else msg.data = data;
}
self.postMessage(msg);
}
function handleMessage(event){
if(typeof event.data === 'string' && event.data.match(/^@worker-importer/)){
const [
action = null,
data = null
] = event.data.replace('@worker-importer.','').split('|');
switch(action){
case 'import' :
if(data){
try{
importScripts(data);
respond('imported', { url : data });
//The work is done, we can just unregister the handler
//and let the imported worker do it's work without us.
self.removeEventListener('message', handleMessage);
}catch(e){
respond('error', e);
}
} else respond('error', new Error(`No url specified.`));
break;
default : respond('error', new Error(`Unknown action ${action}`));
}
}
}
self.addEventListener('message', handleMessage);
How to use it ?
Obviously, your CSPs must allow the CDN domain, but you don't need more CSP rule.
Let's say that you domain is my-domain.com
, and your cdn is statics.your-cdn.com
.
The worker we want to import is hosted at https://statics.your-cdn.com/super-worker.js
and will contain :
self.addEventListener('message', event => {
if(event.data === 'who are you ?') {
self.postMessage("It's me ! I'm useless, but I'm alive !");
} else self.postMessage("I don't understand.");
});
Assuming that you host a file with the code of the worker importer on your domain (NOT your CDN) under the path https://my-domain.com/worker-importer.js
, and that you try to start your worker inside a script tag at https://my-domain.com/
, this is how it works :
<script>
window.addEventListener('load', async () => {
function importWorker(url){
return new Promise((resolve, reject) => {
//The worker importer
const workerImporter = new Worker('/worker-importer.js');
//Will only be used to import our worker
function handleImporterMessage(event){
const { resp = null, data = null } = event.data;
if(resp === 'imported') {
console.log(`Worker at ${data.url} successfully imported !`);
workerImporter.removeEventListener('message', handleImporterMessage);
// Now, we can work with our worker. It's ready !
resolve(workerImporter);
} else if(resp === 'error'){
reject(data);
}
}
workerImporter.addEventListener('message', handleImporterMessage);
workerImporter.postMessage(`@worker-importer.import|${url}`);
});
}
const worker = await importWorker("https://statics.your-cdn.com/super-worker.js");
worker.addEventListener('message', event => {
console.log('worker message : ', event.data);
});
worker.postMessage('who are you ?');
});
</script>
This will print :
Worker at https://statics.your-cdn.com/super-worker.js successfully imported !
worker message : It's me ! I'm useless, but I'm alive !
Note that the code above can even work if it's written in a file hosted on the CDN too.
This is especially usefull when you have several worker scripts on your CDN, or if you build a library that must be hosted on a CDN and you want your users to be able to call your workers without having to host all workers on their domain.