├── package.json ├── LICENSE ├── mini-coi.js ├── mini-coi-fd.js ├── minicoi.py ├── mini-cli.js └── README.md /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-coi", 3 | "version": "0.4.3", 4 | "description": "A minimalistic version of coi-serviceworker", 5 | "main": "mini-coi.js", 6 | "bin": { 7 | "mini-coi": "mini-cli.js" 8 | }, 9 | "keywords": [ 10 | "mini", 11 | "coi", 12 | "coi-serviceworker" 13 | ], 14 | "author": "Andrea Giammarchi", 15 | "license": "MIT", 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/WebReflection/mini-coi.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/WebReflection/mini-coi/issues" 22 | }, 23 | "homepage": "https://github.com/WebReflection/mini-coi#readme", 24 | "dependencies": { 25 | "static-handler": "^0.5.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Andrea Giammarchi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /mini-coi.js: -------------------------------------------------------------------------------- 1 | /*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */ 2 | /*! mini-coi - Andrea Giammarchi and contributors, licensed under MIT */ 3 | (({ document: d, navigator: { serviceWorker: s } }) => { 4 | if (d) { 5 | const { currentScript: c } = d; 6 | s.register(c.src, { scope: c.getAttribute('scope') || '.' }).then(r => { 7 | r.addEventListener('updatefound', () => location.reload()); 8 | if (r.active && !s.controller) location.reload(); 9 | }); 10 | } 11 | else { 12 | addEventListener('install', () => skipWaiting()); 13 | addEventListener('activate', e => e.waitUntil(clients.claim())); 14 | addEventListener('fetch', e => { 15 | const { request: r } = e; 16 | if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') return; 17 | e.respondWith(fetch(r).then(r => { 18 | const { body, status, statusText } = r; 19 | if (!status || status > 399) return r; 20 | const h = new Headers(r.headers); 21 | h.set('Cross-Origin-Opener-Policy', 'same-origin'); 22 | h.set('Cross-Origin-Embedder-Policy', 'require-corp'); 23 | h.set('Cross-Origin-Resource-Policy', 'cross-origin'); 24 | return new Response(status == 204 ? null : body, { status, statusText, headers: h }); 25 | })); 26 | }); 27 | } 28 | })(self); 29 | -------------------------------------------------------------------------------- /mini-coi-fd.js: -------------------------------------------------------------------------------- 1 | /*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */ 2 | /*! mini-coi - Andrea Giammarchi and contributors, licensed under MIT */ 3 | /** FEATURE DETECTION VERSION - COMPATIBLE WITH SERVERS THAT DO NOT SUPPORT COI */ 4 | (({ document: d, navigator: { serviceWorker: s } }) => { 5 | if (d) { 6 | try { new SharedArrayBuffer(4, { maxByteLength: 8 }) } 7 | catch (_) { 8 | const { currentScript: c } = d; 9 | s.register(c.src, { scope: c.getAttribute('scope') || '.' }).then(r => { 10 | r.addEventListener('updatefound', () => location.reload()); 11 | if (r.active && !s.controller) location.reload(); 12 | }); 13 | } 14 | } 15 | else { 16 | addEventListener('install', () => skipWaiting()); 17 | addEventListener('activate', e => e.waitUntil(clients.claim())); 18 | addEventListener('fetch', e => { 19 | const { request: r } = e; 20 | if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') return; 21 | e.respondWith(fetch(r).then(r => { 22 | const { body, status, statusText } = r; 23 | if (!status || status > 399) return r; 24 | const h = new Headers(r.headers); 25 | h.set('Cross-Origin-Opener-Policy', 'same-origin'); 26 | h.set('Cross-Origin-Embedder-Policy', 'require-corp'); 27 | h.set('Cross-Origin-Resource-Policy', 'cross-origin'); 28 | return new Response(status == 204 ? null : body, { status, statusText, headers: h }); 29 | })); 30 | }); 31 | } 32 | })(self); 33 | -------------------------------------------------------------------------------- /minicoi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from http.server import SimpleHTTPRequestHandler as NativeSimpleHTTPRequestHandler 4 | 5 | 6 | class SimpleHTTPRequestHandler(NativeSimpleHTTPRequestHandler): 7 | 8 | def end_headers(self): 9 | self.send_header('Cross-Origin-Opener-Policy', 'same-origin') 10 | self.send_header('Cross-Origin-Embedder-Policy', 'require-corp') 11 | self.send_header('Cross-Origin-Resource-Policy', 'cross-origin') 12 | super().end_headers() 13 | 14 | 15 | # also usable directly as CLI program 16 | if __name__ == '__main__': 17 | import socketserver 18 | import sys 19 | 20 | PORT = '8080' 21 | 22 | for i in range(0, len(sys.argv)): 23 | arg = sys.argv[i] 24 | if arg in ('-h', '--help'): 25 | print(""" 26 | \x1b[7m\x1b[1m minicoi \x1b[0m 27 | 28 | A SimpleHTTPRequestHandler with COOP, COEP, and CORP headers 29 | 30 | \x1b[1moptions\x1b[0m 31 | 32 | -h | --help \x1b[2mthis help\x1b[0m 33 | -p | --port \x1b[2mchange default 8080 port:\x1b[0m 34 | \x1b[2m$ ./minicoi.py -p 3000\x1b[0m 35 | \x1b[2m$ ./minicoi.py --port 8000\x1b[0m 36 | """) 37 | sys.exit(0) 38 | elif arg in ('-p', '--port'): 39 | PORT = sys.argv[i + 1] 40 | elif arg.startswith('port='): 41 | _, PORT, = arg.split('=') 42 | 43 | 44 | print(f'Starting: http://localhost:{PORT}') 45 | 46 | try: 47 | socketserver.TCPServer.allow_reuse_address = True 48 | mini_coi = socketserver.TCPServer(('', int(PORT)), SimpleHTTPRequestHandler) 49 | mini_coi.serve_forever() 50 | except KeyboardInterrupt: 51 | print('Stoped by "Ctrl+C"') 52 | finally: 53 | print('Closing') 54 | mini_coi.server_close() 55 | -------------------------------------------------------------------------------- /mini-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { spawn } = require('child_process'); 4 | const { join, resolve } = require('path'); 5 | const { writeFileSync, readFileSync, statSync } = require('fs'); 6 | 7 | const { argv } = process; 8 | const args = argv.slice(2); 9 | const coi = ['--coi']; 10 | 11 | const cwd = process.cwd(); 12 | 13 | for (let i = 0; i < args.length; i++) { 14 | if (args[i].startsWith('-')) { 15 | switch (args[i]) { 16 | case '--cors': 17 | case '--coop': 18 | case '--coep': 19 | case '--corp': 20 | coi.pop(); 21 | break; 22 | case '-h': 23 | case '--help': 24 | i = args.length; 25 | console.log(` 26 | \x1b[7m\x1b[1m mini-coi \x1b[0m 27 | 28 | \x1b[1moptions\x1b[0m 29 | 30 | --service-worker | -sw \x1b[2msave mini-coi.js script into a specific path:\x1b[0m 31 | \x1b[2mnpx mini-coi -sw ./public/\x1b[0m 32 | \x1b[2mbunx mini-coi -sw ./public/\x1b[0m 33 | 34 | The \x1b[1m--coi\x1b[0m option is passed if none of these options are present: 35 | 36 | --cors 37 | --coop 38 | --coep 39 | --corp 40 | 41 | ↓ all other options are forwarded to ↓`); 42 | break; 43 | case '-sw': 44 | case '--service-worker': 45 | if (++i < args.length) { 46 | let file = resolve(cwd, args[i]); 47 | const stats = statSync(file, {throwIfNoEntry: false}); 48 | if (stats && stats.isDirectory()) 49 | file = resolve(file, 'mini-coi.js'); 50 | writeFileSync(file, readFileSync(join(__dirname, 'mini-coi.js'))); 51 | console.log(`Created mini-coi as \x1b[1m${file}\x1b[0m`); 52 | process.exit(0); 53 | } 54 | console.error(`Unknown path for \x1b[1m${args[--i]}\x1b[0m`); 55 | process.exit(1); 56 | } 57 | } 58 | } 59 | 60 | spawn( 61 | resolve(__dirname, '..', 'static-handler', 'static-handler.cjs'), 62 | [...coi, ...args], 63 | { 64 | cwd, 65 | stdio: 'inherit', 66 | } 67 | ); 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mini-coi 2 | 3 | This project ensures your browser's 4 | [complicated Cross Origin Isolation (COI) settings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements) 5 | "just work"™ for web workers. This is especially useful when you have no 6 | control over the HTTP headers returned by the server. 7 | 8 | The simplest way to use mini-coi is to **place the `mini-coi.js` file in the 9 | root of your website (i.e. `/`), and reference it as the first child tag in the 10 | `
` of your HTML documents**: 11 | 12 | ```html 13 | 14 | ``` 15 | 16 | For more complete technical details, read on... 17 | 18 | ## What is mini-coi? 19 | 20 | A minimalistic *CLI* utility or a simplified version of [coi-serviceworker](https://github.com/gzuidhof/coi-serviceworker), with an optional `scope` attribute to define where Cross Origin Isolation should happen. 21 | 22 | ## CLI 23 | 24 | Bootstrap a local server with all headers enforced: 25 | 26 | ```sh 27 | npx mini-coi . 28 | 29 | # the third argument is a path so ... 30 | # npx mini-coi ./public/ 31 | # npx mini-coi ./test/ 32 | # ... 33 | ``` 34 | 35 | The *CLI* brings in what's possible already to do with [static-handler](https://github.com/WebReflection/static-handler/tree/main) by passing `--coi` by default. 36 | 37 | 38 | ## Service Worker 39 | 40 | Allow headers in places like GitHub pages or any other server where you cannot change current headers: 41 | 42 | ```html 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ``` 52 | 53 | #### What's different from coi-serviceworker? 54 | 55 | * no options at all: "*it just works*" ™ 56 | * always *require-corp* to have this working on Safari as well as Chrome or Firefox 57 | * errors are just thrown in devtools 58 | 59 | - - - 60 | 61 | ### How to use mini-coi as Service Worker 62 | 63 | * the file **must be a local file**, you can't use any CDN or raw GitHub URL, you need to copy the file content locally [^1] 64 | * the script *must not be a module*, it has to be exactly a `` at the top of your `` tag in your page (or in general before any other `script` or `link` or `style` is used, it can be after `meta` and `title` though) 65 | 66 | [^1]: You can either use the *CLI* utility: 67 | 68 | ```sh 69 | npx mini-coi -sw public/mini-coi.js 70 | 71 | // or ... 72 | npx mini-coi --service-worker public/mini-coi.js 73 | ``` 74 | 75 | Or you can grab the file from a CDN and save it locally: 76 | 77 | ```sh 78 | # grab mini-coi.js and save it locally as mini-coi.js 79 | curl -LO https://raw.githubusercontent.com/WebReflection/mini-coi/main/mini-coi.js 80 | ``` 81 | 82 | --------------------------------------------------------------------------------