diff --git a/src/routes.mjs b/src/routes.mjs index dfffa06d..61b46d28 100644 --- a/src/routes.mjs +++ b/src/routes.mjs @@ -32,7 +32,7 @@ const pages = { 'rh': 'pages/proxnav/rammerhead.html', 'w': 'pages/proxnav/womginx.html', /* Proxy Presets */ - 'sx': 'pages/proxnav/preset/searx.html', + 'sx': 'pages/proxnav/preset/spotify.html', 'y': 'pages/proxnav/preset/youtube.html', 'd': 'pages/proxnav/preset/discord.html', 'r': 'pages/proxnav/preset/reddit.html', diff --git a/src/server.mjs b/src/server.mjs index 2aa46eed..a2b8e988 100644 --- a/src/server.mjs +++ b/src/server.mjs @@ -7,7 +7,6 @@ import pkg from './routes.mjs'; import { paintSource, tryReadFile } from './randomization.mjs'; const bare = new Server('/bare/', ''); - const config = JSON.parse(await readFile(new URL('./config.json', import.meta.url))); const { pages, text404 } = pkg; @@ -18,12 +17,10 @@ const router = express.Router(); const server = http.createServer(app); router.get('/', async(req, res) => res.send(paintSource(tryReadFile(path.normalize(__dirname + '/views/' + (['/', '/?'].includes(req.url) ? pages.index : pages[Object.keys(req.query)[0]])))))); - -app.use('/', (req, res, next) => { - if (!bare.route_request(req, res)) return next() -});  - app.use(router); +app.use('/', (req, res, next) => { + if (!bare.route_request(req, res)) return next() +});  app.use(express.static(path.normalize(__dirname + '/views'))); app.disable('x-powered-by'); app.use((req, res) => { @@ -31,4 +28,4 @@ app.use((req, res) => { }); server.listen(port); -console.log('Holy Unblocker is listening on port ' + port + '.'); +console.log('Holy Unblocker is listening on port ' + port + '.'); \ No newline at end of file diff --git a/views/assets/js/header-1644738239.js b/views/assets/js/header-1644738239.js index e7d8bbc3..d7f2b1df 100644 --- a/views/assets/js/header-1644738239.js +++ b/views/assets/js/header-1644738239.js @@ -1 +1 @@ -document.getElementById("header").innerHTML = decodeURIComponent(atob("JTNDYSUyMGhyZWYlM0QlMjIlMkYlMjIlMjBjbGFzcyUzRCUyMmJyYW5kJTIyJTNFSCUyNiUyMzE3MyUzQm8lMjYlMjMxNzMlM0JseSUyMFUlMjYlMjMxNzMlM0JuJTI2JTIzMTczJTNCYiUyNiUyMzE3MyUzQmxvJTI2JTIzMTczJTNCYyUyNiUyMzE3MyUzQmslMjYlMjMxNzMlM0JlciUzQyUyRmElM0UlM0NpbnB1dCUyMGlkJTNEJTIybW5hdmVjYiUyMiUyMHR5cGUlM0QlMjJjaGVja2JveCUyMiUzRSUzQ2xhYmVsJTIwZm9yJTNEJTIybW5hdmVjYiUyMiUyMGNsYXNzJTNEJTIybW5hdmUlMjIlM0UlM0NzcGFuJTIwY2xhc3MlM0QlMjJtbmF2ZWJ1dHRvbiUyMiUzRSUzQyUyRnNwYW4lM0UlM0MlMkZsYWJlbCUzRSUzQ3VsJTIwY2xhc3MlM0QlMjJuYXZiYXIlMjIlM0UlMjAlM0NsaSUzRSUzQ2ElMjBocmVmJTNEJTIyJTJGJTNGeiUyMiUzRVclMjYlMjMxNzMlM0JlYiUyMFByJTI2JTIzMTczJTNCb3glMjYlMjMxNzMlM0JpZSUyNiUyMzE3MyUzQnMlM0MlMkZhJTNFJTNDJTJGbGklM0UlM0NsaSUzRSUzQ2ElMjBocmVmJTNEJTIyJTJGJTNGc3glMjIlM0VTZWFyeCUzQyUyRmElM0UlM0MlMkZsaSUzRSUzQ2xpJTNFJTNDYSUyMGhyZWYlM0QlMjIlMkYlM0ZnJTIyJTNFRyUyNiUyMzE3MyUzQmElMjYlMjMxNzMlM0JtZSUyNiUyMzE3MyUzQnMlM0MlMkZhJTNFJTNDJTJGbGklM0UlM0NsaSUzRSUzQ2ElMjBocmVmJTNEJTIyJTJGJTNGeSUyMiUzRVlvJTI2JTIzMTczJTNCdVQlMjYlMjMxNzMlM0J1JTI2JTIzMTczJTNCYmUlM0MlMkZhJTNFJTNDJTJGbGklM0UlM0NsaSUzRSUzQ2ElMjBocmVmJTNEJTIyJTJGJTNGZCUyMiUzRUQlMjYlMjMxNzMlM0Jpc2MlMjYlMjMxNzMlM0JvciUyNiUyMzE3MyUzQmQlM0MlMkZhJTNFJTNDJTJGbGklM0UlM0NsaSUzRSUzQ2ElMjBocmVmJTNEJTIyJTJGJTNGciUyMiUzRVJlJTI2JTIzMTczJTNCZGQlMjYlMjMxNzMlM0JpdCUzQyUyRmElM0UlM0MlMkZsaSUzRSUzQ2xpJTIwY2xhc3MlM0QlMjJkcm9wZG93bi1wYXJlbnQlMjIlM0UlM0NhJTIwaHJlZiUzRCUyMiUyMyUyMiUzRU0lMjYlMjMxNzMlM0JvciUyNiUyMzE3MyUzQmUlMjAlM0NpJTIwY2xhc3MlM0QlMjJmYXMlMjBmYS1lbGxpcHNpcy12JTIyJTNFJTNDJTJGaSUzRSUzQyUyRmElM0UlMjAlM0NkaXYlMjBjbGFzcyUzRCUyMmRyb3Bkb3duLWNoaWxkJTIyJTIwdGFiaW5kZXglM0QlMjIwJTIyJTNFJTIwJTNDdWwlMjBjbGFzcyUzRCUyMnN1Ym5hdmJhciUyMiUzRSUyMCUzQ2klMjBjbGFzcyUzRCUyMmZhcyUyMGZhLWJhcnMlMjIlM0UlM0MlMkZpJTNFJTIwJTNDbGklM0UlM0NhJTIwaHJlZiUzRCUyMiUyRiUzRnglMjIlM0VCb29rJTI2JTIzMTczJTNCbWFyayUyNiUyMzE3MyUzQmxldHMlM0MlMkZhJTNFJTNDJTJGbGklM0UlM0NsaSUzRSUzQ2ElMjBocmVmJTNEJTIyJTJGJTNGaW4lMjIlM0VEb2NzJTNDJTJGYSUzRSUzQyUyRmxpJTNFJTNDbGklM0UlM0NhJTIwaHJlZiUzRCUyMiUyRiUzRmZhcSUyMiUzRUZBJTI2JTIzMTczJTNCUSUzQyUyRmElM0UlM0MlMkZsaSUzRSUzQ2xpJTNFJTNDYSUyMGhyZWYlM0QlMjIlMkYlM0ZjJTIyJTNFQ3JlZGl0JTI2JTIzMTczJTNCcyUzQyUyRmElM0UlM0MlMkZsaSUzRSUzQ2xpJTNFJTNDYSUyMGhyZWYlM0QlMjIlMkYlM0Z0JTIyJTNFVE8lMjYlMjMxNzMlM0JTJTNDJTJGYSUzRSUzQyUyRmxpJTNFJTNDJTJGdWwlM0UlMjAlM0MlMkZkaXYlM0UlM0MlMkZsaSUzRSUzQ2xpJTIwY2xhc3MlM0QlMjJkcm9wZG93bi1wYXJlbnQlMjIlM0UlM0NhJTIwaHJlZiUzRCUyMiUyMyUyMiUzRVNldHRpbmdzJTIwJTNDaSUyMGNsYXNzJTNEJTIyZmFzJTIwZmEtY29nJTIyJTNFJTNDJTJGaSUzRSUzQyUyRmElM0UlMjAlM0NkaXYlMjBjbGFzcyUzRCUyMmRyb3Bkb3duLWNoaWxkJTIyJTIwdGFiaW5kZXglM0QlMjIwJTIyJTNFJTIwJTNDZGl2JTIwaWQlM0QlMjJjc2VsJTIyJTNFJTNDJTJGZGl2JTNFJTNDJTJGZGl2JTNFJTNDJTJGbGklM0UlM0MlMkZ1bCUzRQ==")); \ No newline at end of file +document.getElementById("header").innerHTML = decodeURIComponent(atob("JTNDYSUyMGhyZWYlM0QlMjIlMkYlMjIlMjBjbGFzcyUzRCUyMmJyYW5kJTIyJTNFSCUyNiUyMzE3MyUzQm8lMjYlMjMxNzMlM0JseSUyMFUlMjYlMjMxNzMlM0JuJTI2JTIzMTczJTNCYiUyNiUyMzE3MyUzQmxvJTI2JTIzMTczJTNCYyUyNiUyMzE3MyUzQmslMjYlMjMxNzMlM0JlciUzQyUyRmElM0UlMEElM0NpbnB1dCUyMGlkJTNEJTIybW5hdmVjYiUyMiUyMHR5cGUlM0QlMjJjaGVja2JveCUyMiUzRSUwQSUzQ2xhYmVsJTIwZm9yJTNEJTIybW5hdmVjYiUyMiUyMGNsYXNzJTNEJTIybW5hdmUlMjIlM0UlM0NzcGFuJTIwY2xhc3MlM0QlMjJtbmF2ZWJ1dHRvbiUyMiUzRSUzQyUyRnNwYW4lM0UlM0MlMkZsYWJlbCUzRSUwQSUzQ3VsJTIwY2xhc3MlM0QlMjJuYXZiYXIlMjIlM0UlMEElMjAlMjAlMjAlMjAlM0NsaSUzRSUzQ2ElMjBocmVmJTNEJTIyJTJGJTNGeiUyMiUzRVclMjYlMjMxNzMlM0JlYiUyMFByJTI2JTIzMTczJTNCb3glMjYlMjMxNzMlM0JpZSUyNiUyMzE3MyUzQnMlM0MlMkZhJTNFJTNDJTJGbGklM0UlMEElMjAlMjAlMjAlMjAlM0NsaSUzRSUzQ2ElMjBocmVmJTNEJTIyJTJGJTNGZyUyMiUzRUclMjYlMjMxNzMlM0JhJTI2JTIzMTczJTNCbWUlMjYlMjMxNzMlM0JzJTNDJTJGYSUzRSUzQyUyRmxpJTNFJTBBJTIwJTIwJTIwJTIwJTNDbGklM0UlM0NhJTIwaHJlZiUzRCUyMiUyRiUzRnN4JTIyJTNFUyUyNiUyMzE3MyUzQnBvdGklMjYlMjMxNzMlM0JmeSUzQyUyRmElM0UlM0MlMkZsaSUzRSUwQSUyMCUyMCUyMCUyMCUzQ2xpJTNFJTNDYSUyMGhyZWYlM0QlMjIlMkYlM0Z5JTIyJTNFWW8lMjYlMjMxNzMlM0J1VCUyNiUyMzE3MyUzQnUlMjYlMjMxNzMlM0JiZSUzQyUyRmElM0UlM0MlMkZsaSUzRSUwQSUyMCUyMCUyMCUyMCUzQ2xpJTNFJTNDYSUyMGhyZWYlM0QlMjIlMkYlM0ZkJTIyJTNFRCUyNiUyMzE3MyUzQmlzYyUyNiUyMzE3MyUzQm9yJTI2JTIzMTczJTNCZCUzQyUyRmElM0UlM0MlMkZsaSUzRSUwQSUyMCUyMCUyMCUyMCUzQ2xpJTNFJTNDYSUyMGhyZWYlM0QlMjIlMkYlM0ZyJTIyJTNFUmUlMjYlMjMxNzMlM0JkZCUyNiUyMzE3MyUzQml0JTNDJTJGYSUzRSUzQyUyRmxpJTNFJTBBJTIwJTIwJTIwJTIwJTNDIS0tJTIwdGFiaW5kZXglM0QlMjIwJTIyJTIwaXMlMjByZXF1aXJlZCUyMG9uJTIwdGhlJTIwZHJvcGRvd25zJTIwLS0lM0UlMEElMjAlMjAlMjAlMjAlM0NsaSUyMGNsYXNzJTNEJTIyZHJvcGRvd24tcGFyZW50JTIyJTNFJTNDYSUyMGhyZWYlM0QlMjIlMjMlMjIlM0VNJTI2JTIzMTczJTNCb3IlMjYlMjMxNzMlM0JlJTIwJTNDaSUyMGNsYXNzJTNEJTIyZmFzJTIwZmEtZWxsaXBzaXMtdiUyMiUzRSUzQyUyRmklM0UlM0MlMkZhJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDZGl2JTIwY2xhc3MlM0QlMjJkcm9wZG93bi1jaGlsZCUyMiUyMHRhYmluZGV4JTNEJTIyMCUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ3VsJTIwY2xhc3MlM0QlMjJzdWJuYXZiYXIlMjIlM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NpJTIwY2xhc3MlM0QlMjJmYXMlMjBmYS1iYXJzJTIyJTNFJTNDJTJGaSUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ2xpJTNFJTNDYSUyMGhyZWYlM0QlMjIlMkYlM0Z4JTIyJTNFQm9vayUyNiUyMzE3MyUzQm1hcmslMjYlMjMxNzMlM0JsZXRzJTNDJTJGYSUzRSUzQyUyRmxpJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDbGklM0UlM0NhJTIwaHJlZiUzRCUyMiUyRiUzRmluJTIyJTNFRG9jcyUzQyUyRmElM0UlM0MlMkZsaSUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ2xpJTNFJTNDYSUyMGhyZWYlM0QlMjIlMkYlM0ZmYXElMjIlM0VGQSUyNiUyMzE3MyUzQlElM0MlMkZhJTNFJTNDJTJGbGklM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NsaSUzRSUzQ2ElMjBocmVmJTNEJTIyJTJGJTNGYyUyMiUzRUNyZWRpdCUyNiUyMzE3MyUzQnMlM0MlMkZhJTNFJTNDJTJGbGklM0UlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlM0NsaSUzRSUzQ2ElMjBocmVmJTNEJTIyJTJGJTNGdCUyMiUzRVRPJTI2JTIzMTczJTNCUyUzQyUyRmElM0UlM0MlMkZsaSUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQyUyRnVsJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDJTJGZGl2JTNFJTBBJTIwJTIwJTIwJTIwJTNDJTJGbGklM0UlMEElMjAlMjAlMjAlMjAlM0NsaSUyMGNsYXNzJTNEJTIyZHJvcGRvd24tcGFyZW50JTIyJTNFJTNDYSUyMGhyZWYlM0QlMjIlMjMlMjIlM0VTZXR0aW5ncyUyMCUzQ2klMjBjbGFzcyUzRCUyMmZhcyUyMGZhLWNvZyUyMiUzRSUzQyUyRmklM0UlM0MlMkZhJTNFJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTNDZGl2JTIwY2xhc3MlM0QlMjJkcm9wZG93bi1jaGlsZCUyMiUyMHRhYmluZGV4JTNEJTIyMCUyMiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQ2RpdiUyMGlkJTNEJTIyY3NlbCUyMiUzRSUzQyUyRmRpdiUzRSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUzQyUyRmRpdiUzRSUwQSUyMCUyMCUyMCUyMCUzQyUyRmxpJTNFJTBBJTNDJTJGdWwlM0U=")); \ No newline at end of file diff --git a/views/pages/frame.html b/views/pages/frame.html index 41cb910a..1de5786a 100644 --- a/views/pages/frame.html +++ b/views/pages/frame.html @@ -39,6 +39,13 @@ + diff --git a/views/pages/proxnav/preset/discord.html b/views/pages/proxnav/preset/discord.html index 7ce677cf..7fa0f60f 100644 --- a/views/pages/proxnav/preset/discord.html +++ b/views/pages/proxnav/preset/discord.html @@ -60,6 +60,13 @@ + diff --git a/views/pages/proxnav/preset/reddit.html b/views/pages/proxnav/preset/reddit.html index 84e3b8ab..782568dc 100644 --- a/views/pages/proxnav/preset/reddit.html +++ b/views/pages/proxnav/preset/reddit.html @@ -46,6 +46,13 @@ + diff --git a/views/pages/proxnav/preset/searx.html b/views/pages/proxnav/preset/spotify.html similarity index 70% rename from views/pages/proxnav/preset/searx.html rename to views/pages/proxnav/preset/spotify.html index 3986c1be..3e8c4a58 100644 --- a/views/pages/proxnav/preset/searx.html +++ b/views/pages/proxnav/preset/spotify.html @@ -24,10 +24,11 @@
-

Searx

+

Spotify

- Sea­rx is a free inte­rnet me­tasearch en­gine which ag­gregates r­esults from mo­re than 7­0 sea­rch ser­vices. User­s are neit­her tra­cked nor pr­ofil­ed. -
Add­itiona­lly, se­arx c­an be u­sed ov­er To­r for onli­ne anonym­ity. + Sp­oti­fy is a digi­tal mus­ic, podc­ast, and v­ideo servi­ce th­at giv­es yo­u acc­ess to mill­ions of so­ngs and oth­er cont­ent f­rom creat­ors all o­ver the + wor­ld. +
Using Ult­rav­iolet, Spo­tify wo­rks wh­ile b­eing su­per fa­st. Enj­oy unblo­cked mu­sic strea­ming!

Having Issues?

@@ -35,12 +36,22 @@
Sometimes the pro­xie­s are under high load so things may be s­low, sorry. In that case simply wait for the pa­ge to load.

-

Down for now.

+ Classic + Stealth
+ + + diff --git a/views/pages/proxnav/preset/youtube.html b/views/pages/proxnav/preset/youtube.html index c90eee42..1f6888a5 100644 --- a/views/pages/proxnav/preset/youtube.html +++ b/views/pages/proxnav/preset/youtube.html @@ -45,6 +45,13 @@ + diff --git a/views/pages/proxnav/ultraviolet.html b/views/pages/proxnav/ultraviolet.html index 5ad08887..addcb13e 100644 --- a/views/pages/proxnav/ultraviolet.html +++ b/views/pages/proxnav/ultraviolet.html @@ -62,6 +62,13 @@ + diff --git a/views/sw.js b/views/sw.js new file mode 100644 index 00000000..ffe57d1f --- /dev/null +++ b/views/sw.js @@ -0,0 +1,9 @@ +importScripts('./uv.sw.js'); + +const sw = new UVServiceWorker(); + +self.addEventListener('fetch', event => + event.respondWith( + sw.fetch(event) + ) +); \ No newline at end of file diff --git a/views/uv.bundle.js b/views/uv.bundle.js index e1b50164..ee061a0e 100644 --- a/views/uv.bundle.js +++ b/views/uv.bundle.js @@ -37985,6 +37985,10 @@ class WebSocketApi extends _events_js__WEBPACK_IMPORTED_MODULE_0__["default"] { this.protocol = ctx.nativeMethods.getOwnPropertyDescriptor(this.wsProto, 'protocol'); this.send = this.wsProto.send; this.close = this.wsProto.close; + this.CONNECTING = 0; + this.OPEN = 1; + this.CLOSING = 2; + this.CLOSED = 3; }; overrideWebSocket() { this.ctx.override(this.window, 'WebSocket', (target, that, args) => { @@ -37998,6 +38002,11 @@ class WebSocketApi extends _events_js__WEBPACK_IMPORTED_MODULE_0__["default"] { if (event.intercepted) return event.returnValue; return new event.target(event.data.url, event.data.protocols); }, true); + + this.window.WebSocket.CONNECTING = this.CONNECTING; + this.window.WebSocket.OPEN = this.OPEN; + this.window.WebSocket.CLOSING = this.CLOSING; + this.window.WebSocket.CLOSED = this.CLOSED; }; overrideUrl() { this.ctx.overrideDescriptor(this.wsProto, 'url', { @@ -39050,9 +39059,9 @@ class Ultraviolet { this.meta = options.meta || {}; this.meta.base ||= undefined; this.meta.origin ||= ''; - this.bundleScript = options.bundleScript || '/uv.bundle.js'; - this.handlerScript = options.handlerScript || '/uv.handler.js'; - this.configScript = options.handlerScript || '/uv.config.js'; + this.bundleScript = options.bundle || '/uv.bundle.js'; + this.handlerScript = options.handler || '/uv.handler.js'; + this.configScript = options.config || '/uv.config.js'; this.meta.url ||= this.meta.base || ''; this.codec = Ultraviolet.codec; this.html = new _html_js__WEBPACK_IMPORTED_MODULE_0__["default"](this); diff --git a/views/uv.config.js b/views/uv.config.js index 8715bff5..a737665b 100644 --- a/views/uv.config.js +++ b/views/uv.config.js @@ -1,10 +1,10 @@ self.__uv$config = { - prefix: '/sw/', - bare: '/bare/', + prefix: '/service/', + bare: 'https://cdn.' + location.hostname + '/bare/', encodeUrl: Ultraviolet.codec.xor.encode, decodeUrl: Ultraviolet.codec.xor.decode, handler: '/uv.handler.js', bundle: '/uv.bundle.js', config: '/uv.config.js', sw: '/uv.sw.js', -}; +}; \ No newline at end of file diff --git a/views/uv.handler.js b/views/uv.handler.js index bf06acc0..e4b5c332 100644 --- a/views/uv.handler.js +++ b/views/uv.handler.js @@ -17,6 +17,10 @@ async function __uvHook(window, config = {}, bare = '/bare/') { window, }); + if (typeof config.construct === 'function') { + config.construct(__uv, worker ? 'worker' : 'window'); + }; + const { client } = __uv; const { HTMLMediaElement, diff --git a/views/uv.sw.js b/views/uv.sw.js index 1ba34a48..8a72003b 100644 --- a/views/uv.sw.js +++ b/views/uv.sw.js @@ -1,196 +1,135 @@ importScripts('./uv.bundle.js'); -importScripts('./uv.config.js') - -const csp = [ - 'cross-origin-embedder-policy', - 'cross-origin-opener-policy', - 'cross-origin-resource-policy', - 'content-security-policy', - 'content-security-policy-report-only', - 'expect-ct', - 'feature-policy', - 'origin-isolation', - 'strict-transport-security', - 'upgrade-insecure-requests', - 'x-content-type-options', - 'x-download-options', - 'x-frame-options', - 'x-permitted-cross-domain-policies', - 'x-powered-by', - 'x-xss-protection', -]; - -const headers = { - csp: [ - 'cross-origin-embedder-policy', - 'cross-origin-opener-policy', - 'cross-origin-resource-policy', - 'content-security-policy', - 'content-security-policy-report-only', - 'expect-ct', - 'feature-policy', - 'origin-isolation', - 'strict-transport-security', - 'upgrade-insecure-requests', - 'x-content-type-options', - 'x-download-options', - 'x-frame-options', - 'x-permitted-cross-domain-policies', - 'x-powered-by', - 'x-xss-protection', - ], - forward: [ - 'accept-encoding', - 'connection', - 'content-length', - 'content-type', - 'user-agent', - ], -}; - -const scripts = { - package: '/uv.bundle.js', - handler: '/uv.handler.js', -}; - -const method = { - empty: ['GET', 'HEAD'] -}; - -const statusCode = { - empty: [ - 204, - 304, - ], -}; - -const handler = UVServiceWorker(__uv$config.bare, __uv$config); - -addEventListener('fetch', async event => - event.respondWith(handler(event)) -); - -addEventListener('install', () => { - self.skipWaiting(); -}); - - -function UVServiceWorker(_bare = '/bare/', options) { - try { - return async function handler(event) { - const { request } = event; - const bare = new URL(_bare, location.href); +importScripts('./uv.config.js'); +class UVServiceWorker extends EventEmitter { + constructor(config = __uv$config) { + super(); + if (!config.bare) config.bare = '/bare/'; + this.addresses = typeof config.bare === 'string' ? [ new URL(config.bare, location) ] : config.bare.map(str => new URL(str, location)); + this.headers = { + csp: [ + 'cross-origin-embedder-policy', + 'cross-origin-opener-policy', + 'cross-origin-resource-policy', + 'content-security-policy', + 'content-security-policy-report-only', + 'expect-ct', + 'feature-policy', + 'origin-isolation', + 'strict-transport-security', + 'upgrade-insecure-requests', + 'x-content-type-options', + 'x-download-options', + 'x-frame-options', + 'x-permitted-cross-domain-policies', + 'x-powered-by', + 'x-xss-protection', + ], + forward: [ + 'accept-encoding', + 'connection', + 'content-length', + 'content-type', + 'user-agent', + ], + }; + this.method = { + empty: [ + 'GET', + 'HEAD' + ] + }; + this.statusCode = { + empty: [ + 204, + 304, + ], + }; + this.config = config; + }; + async fetch({ request }) { + if (!request.url.startsWith(location.origin + (this.config.prefix || '/service/'))) { + return fetch(request); + }; try { - if (!request.url.startsWith(location.origin + (options.prefix || '/service/'))) { - return fetch(request); + + const ultraviolet = new Ultraviolet(this.config); + + if (typeof this.config.construct === 'function') { + this.config.construct(ultraviolet, 'service'); }; - const requestCtx = { - url: bare.href + 'v1/', - referrer: false, - headers: {}, - forward: headers.forward, - method: request.method, - body: !method.empty.includes(request.method.toUpperCase()) ? await request.blob() : null, - redirect: request.redirect, - credentials: 'omit', - mode: request.mode === 'cors' ? request.mode : 'same-origin', - blob: false, - }; + const db = await ultraviolet.cookie.db(); - const uv = new Ultraviolet(options); - const db = await uv.cookie.db(); - - uv.meta.origin = location.origin; - uv.meta.base = uv.meta.url = new URL(uv.sourceUrl(request.url)); + ultraviolet.meta.origin = location.origin; + ultraviolet.meta.base = ultraviolet.meta.url = new URL(ultraviolet.sourceUrl(request.url)); - if (uv.meta.url.protocol === 'blob:') { + const requestCtx = new RequestContext( + request, + this, + ultraviolet, + !this.method.empty.includes(request.method.toUpperCase()) ? await request.blob() : null + ); + + if (ultraviolet.meta.url.protocol === 'blob:') { requestCtx.blob = true; - uv.meta.base = uv.meta.url = new URL(uv.meta.url.pathname); - requestCtx.url = 'blob:' + location.origin + uv.meta.url.pathname; + requestCtx.base = requestCtx.url = new URL(requestCtx.url.pathname); }; - requestCtx.headers = Object.fromEntries([...request.headers.entries()]); - - requestCtx.host = uv.meta.url.host; - if (request.referrer && request.referrer.startsWith(location.origin)) { - const referer = new URL(uv.sourceUrl(request.referrer)); + const referer = new URL(ultraviolet.sourceUrl(request.referrer)); - if (uv.meta.url.origin !== referer.origin && request.mode === 'cors') { + if (ultraviolet.meta.url.origin !== referer.origin && request.mode === 'cors') { requestCtx.headers.origin = referer.origin; }; requestCtx.headers.referer = referer.href; }; - const cookies = await uv.cookie.getCookies(db) || []; - const cookieStr = uv.cookie.serialize(cookies, uv.meta, false); + const cookies = await ultraviolet.cookie.getCookies(db) || []; + const cookieStr = ultraviolet.cookie.serialize(cookies, ultraviolet.meta, false); const browser = Ultraviolet.Bowser.getParser(self.navigator.userAgent).getBrowserName(); - const forward = [...headers.forward]; if (browser === 'Firefox' && !(request.destination === 'iframe' || request.destination === 'document')) { - forward.shift(); + requestCtx.forward.shift(); }; if (cookieStr) requestCtx.headers.cookie = cookieStr; - const bareHeaders = { - 'x-bare-protocol': uv.meta.url.protocol, - 'x-bare-host': uv.meta.url.hostname, - 'x-bare-path': uv.meta.url.pathname + uv.meta.url.search, - 'x-bare-port': uv.meta.url.port || (uv.meta.url.protocol === 'https:' ? '443' : '80'), - 'x-bare-headers': JSON.stringify(requestCtx.headers), - 'x-bare-forward-headers': JSON.stringify(forward), - }; - const fetchOptions = { - method: requestCtx.method, - headers: !requestCtx.blob ? bareHeaders : requestCtx.headers, - redirect: requestCtx.redirect, - credentials: requestCtx.credentials, - mode: location.origin !== bare.origin ? 'cors' : requestCtx.mode, - }; - if (requestCtx.body) fetchOptions.body = requestCtx.body; + const reqEvent = new HookEvent(requestCtx, null, null); + this.emit('request', reqEvent); + if (reqEvent.intercepted) return reqEvent.returnValue; + + const response = await fetch(requestCtx.send); - const response = await fetch(requestCtx.url, fetchOptions); - if (response.status === 500) { - return Promise.reject('Err'); + return Promise.reject(''); }; - - const sendData = !requestCtx.blob ? getBarerResponse(response) : { - status: response.status, - statusText: response.statusText, - headers: Object.fromEntries([...response.headers.entries()]), - body: response.body, - }; - const responseCtx = { - headers: sendData.headers, - status: sendData.status, - statusText: sendData.statusText, - body: !statusCode.empty.includes(sendData.status) ? sendData.body : null, - }; - - for (const name of headers.csp) { + const responseCtx = new ResponseContext(requestCtx, response, this); + const resEvent = new HookEvent(responseCtx, null, null); + + this.emit('beforemod', resEvent); + if (resEvent.intercepted) return resEvent.returnValue; + + for (const name of this.headers.csp) { if (responseCtx.headers[name]) delete responseCtx.headers[name]; }; if (responseCtx.headers.location) { - responseCtx.headers.location = uv.rewriteUrl(responseCtx.headers.location); + responseCtx.headers.location = ultraviolet.rewriteUrl(responseCtx.headers.location); }; if (responseCtx.headers['set-cookie']) { - Promise.resolve(uv.cookie.setCookies(responseCtx.headers['set-cookie'], db, uv.meta)).then(() => { + Promise.resolve(ultraviolet.cookie.setCookies(responseCtx.headers['set-cookie'], db, ultraviolet.meta)).then(() => { self.clients.matchAll().then(function (clients){ clients.forEach(function(client){ client.postMessage({ msg: 'updateCookies', - url: uv.meta.url.href, + url: ultraviolet.meta.url.href, }); }); }); @@ -203,27 +142,27 @@ function UVServiceWorker(_bare = '/bare/', options) { case 'script': case 'worker': responseCtx.body = `if (!self.__uv && self.importScripts) importScripts('${__uv$config.bundle}', '${__uv$config.config}', '${__uv$config.handler}');\n`; - responseCtx.body += uv.js.rewrite( + responseCtx.body += ultraviolet.js.rewrite( await response.text() ); break; case 'style': - responseCtx.body = uv.rewriteCSS( + responseCtx.body = ultraviolet.rewriteCSS( await response.text() ); break; case 'iframe': case 'document': - if (isHtml(uv.meta.url, (sendData.headers['content-type'] || ''))) { - responseCtx.body = uv.rewriteHtml( + if (isHtml(ultraviolet.meta.url, (responseCtx.headers['content-type'] || ''))) { + responseCtx.body = ultraviolet.rewriteHtml( await response.text(), { document: true , - injectHead: uv.createHtmlInject( - options.handler, - options.bundle, - options.config, - uv.cookie.serialize(cookies, uv.meta, true), + injectHead: ultraviolet.createHtmlInject( + this.config.handler, + this.config.bundle, + this.config.config, + ultraviolet.cookie.serialize(cookies, ultraviolet.meta, true), request.referrer ) } @@ -233,46 +172,615 @@ function UVServiceWorker(_bare = '/bare/', options) { }; if (requestCtx.headers.accept === 'text/event-stream') { - requestCtx.headers['content-type'] = 'text/event-stream'; + responseCtx.headers['content-type'] = 'text/event-stream'; }; + this.emit('response', resEvent); + if (resEvent.intercepted) return resEvent.returnValue; + return new Response(responseCtx.body, { headers: responseCtx.headers, status: responseCtx.status, statusText: responseCtx.statusText, }); - } catch(e) { - return new Response(e.toString(), { + + } catch(err) { + return new Response(err.toString(), { status: 500, }); }; }; -} catch(e) { - return (event) => { - event.respondWith(new Response(e.toString(), { - status: 500, - })) + getBarerResponse(response) { + const headers = {}; + const raw = JSON.parse(response.headers.get('x-bare-headers')); + + for (const key in raw) { + headers[key.toLowerCase()] = raw[key]; + }; + + return { + headers, + status: +response.headers.get('x-bare-status'), + statusText: response.headers.get('x-bare-status-text'), + body: !this.statusCode.empty.includes(+response.headers.get('x-bare-status')) ? response.body : null, + }; }; -}; + get address() { + return this.addresses[Math.floor(Math.random() * this.addresses.length)]; + }; + static Ultraviolet = Ultraviolet; }; -function getBarerResponse(response) { +self.UVServiceWorker = UVServiceWorker; - const headers = {}; - const raw = JSON.parse(response.headers.get('x-bare-headers')); - for (const key in raw) { - headers[key.toLowerCase()] = raw[key]; +class ResponseContext { + constructor(request, response, worker) { + const { headers, status, statusText, body } = !request.blob ? worker.getBarerResponse(response) : { + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries([...response.headers.entries()]), + body: response.body, + }; + this.request = request; + this.raw = response; + this.ultraviolet = request.ultraviolet; + this.headers = headers; + this.status = status; + this.statusText = statusText; + this.body = body; }; - - return { - headers, - status: +response.headers.get('x-bare-status'), - statusText: response.headers.get('x-bare-status-text'), - body: response.body, + get url() { + return this.request.url; + } + get base() { + return this.request.base; + }; + set base(val) { + this.request.base = val; }; }; +class RequestContext { + constructor(request, worker, ultraviolet, body = null) { + this.ultraviolet = ultraviolet; + this.request = request; + this.headers = Object.fromEntries([...request.headers.entries()]); + this.method = request.method; + this.forward = [...worker.headers.forward]; + this.address = worker.address; + this.body = body || null; + this.redirect = request.redirect; + this.credentials = 'omit'; + this.mode = request.mode === 'cors' ? request.mode : 'same-origin'; + this.blob = false; + }; + get send() { + return new Request((!this.blob ? this.address.href + 'v1/' : 'blob:' + location.origin + this.url.pathname), { + method: this.method, + headers: { + 'x-bare-protocol': this.url.protocol, + 'x-bare-host': this.url.hostname, + 'x-bare-path': this.url.pathname + this.url.search, + 'x-bare-port': this.url.port || (this.url.protocol === 'https:' ? '443' : '80'), + 'x-bare-headers': JSON.stringify(this.headers), + 'x-bare-forward-headers': JSON.stringify(this.forward), + }, + redirect: this.redirect, + credentials: this.credentials, + mode: location.origin !== this.address.origin ? 'cors' : this.mode, + body: this.body + }); + }; + get url() { + return this.ultraviolet.meta.url; + }; + set url(val) { + this.ultraviolet.meta.url = val; + }; + get base() { + return this.ultraviolet.meta.base; + }; + set base(val) { + this.ultraviolet.meta.base = val; + }; +} + function isHtml(url, contentType = '') { return (Ultraviolet.mime.contentType((contentType || url.pathname)) || 'text/html').split(';')[0] === 'text/html'; -}; \ No newline at end of file +}; + +class HookEvent { + #intercepted; + #returnValue; + constructor(data = {}, target = null, that = null) { + this.#intercepted = false; + this.#returnValue = null; + this.data = data; + this.target = target; + this.that = that; + }; + get intercepted() { + return this.#intercepted; + }; + get returnValue() { + return this.#returnValue; + }; + respondWith(input) { + this.#returnValue = input; + this.#intercepted = true; + }; +}; + +var R = typeof Reflect === 'object' ? Reflect : null +var ReflectApply = R && typeof R.apply === 'function' + ? R.apply + : function ReflectApply(target, receiver, args) { + return Function.prototype.apply.call(target, receiver, args); + } + +var ReflectOwnKeys +if (R && typeof R.ownKeys === 'function') { + ReflectOwnKeys = R.ownKeys +} else if (Object.getOwnPropertySymbols) { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target) + .concat(Object.getOwnPropertySymbols(target)); + }; +} else { + ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target); + }; +} + +function ProcessEmitWarning(warning) { + if (console && console.warn) console.warn(warning); +} + +var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) { + return value !== value; +} + +function EventEmitter() { + EventEmitter.init.call(this); +} + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._eventsCount = 0; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +var defaultMaxListeners = 10; + +function checkListener(listener) { + if (typeof listener !== 'function') { + throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener); + } +} + +Object.defineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { + throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.'); + } + defaultMaxListeners = arg; + } +}); + +EventEmitter.init = function() { + + if (this._events === undefined || + this._events === Object.getPrototypeOf(this)._events) { + this._events = Object.create(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; +}; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { + throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.'); + } + this._maxListeners = n; + return this; +}; + +function _getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return _getMaxListeners(this); +}; + +EventEmitter.prototype.emit = function emit(type) { + var args = []; + for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); + var doError = (type === 'error'); + + var events = this._events; + if (events !== undefined) + doError = (doError && events.error === undefined); + else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + var er; + if (args.length > 0) + er = args[0]; + if (er instanceof Error) { + // Note: The comments on the `throw` lines are intentional, they show + // up in Node's output if this results in an unhandled exception. + throw er; // Unhandled 'error' event + } + // At least give some kind of context to the user + var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : '')); + err.context = er; + throw err; // Unhandled 'error' event + } + + var handler = events[type]; + + if (handler === undefined) + return false; + + if (typeof handler === 'function') { + ReflectApply(handler, this, args); + } else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + ReflectApply(listeners[i], this, args); + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + var m; + var events; + var existing; + + checkListener(listener); + + events = target._events; + if (events === undefined) { + events = target._events = Object.create(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener !== undefined) { + target.emit('newListener', type, + listener.listener ? listener.listener : listener); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (existing === undefined) { + // Optimize the case of one listener. Don't need the extra array object. + existing = events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + // If we've already got an array, just append. + } else if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + + // Check for listener leak + m = _getMaxListeners(target); + if (m > 0 && existing.length > m && !existing.warned) { + existing.warned = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + var w = new Error('Possible EventEmitter memory leak detected. ' + + existing.length + ' ' + String(type) + ' listeners ' + + 'added. Use emitter.setMaxListeners() to ' + + 'increase limit'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + ProcessEmitWarning(w); + } + } + + return target; +} + +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + if (arguments.length === 0) + return this.listener.call(this.target); + return this.listener.apply(this.target, arguments); + } +} + +function _onceWrap(target, type, listener) { + var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; + var wrapped = onceWrapper.bind(state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +EventEmitter.prototype.once = function once(type, listener) { + checkListener(listener); + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + checkListener(listener); + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + +// Emits a 'removeListener' event if and only if the listener was removed. +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + var list, events, position, i, originalListener; + + checkListener(listener); + + events = this._events; + if (events === undefined) + return this; + + list = events[type]; + if (list === undefined) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + position = -1; + + for (i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + originalListener = list[i].listener; + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else { + spliceOne(list, position); + } + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener !== undefined) + this.emit('removeListener', type, originalListener || listener); + } + + return this; + }; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + var listeners, events, i; + + events = this._events; + if (events === undefined) + return this; + + // not listening for removeListener, no need to emit + if (events.removeListener === undefined) { + if (arguments.length === 0) { + this._events = Object.create(null); + this._eventsCount = 0; + } else if (events[type] !== undefined) { + if (--this._eventsCount === 0) + this._events = Object.create(null); + else + delete events[type]; + } + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + var keys = Object.keys(events); + var key; + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = Object.create(null); + this._eventsCount = 0; + return this; + } + + listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners !== undefined) { + // LIFO order + for (i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + var events = target._events; + + if (events === undefined) + return []; + + var evlistener = events[type]; + if (evlistener === undefined) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? + unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); +} + +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } else { + return listenerCount.call(emitter, type); + } +}; + +EventEmitter.prototype.listenerCount = listenerCount; +function listenerCount(type) { + var events = this._events; + + if (events !== undefined) { + var evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; + } + } + + return 0; +} + +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; +}; + +function arrayClone(arr, n) { + var copy = new Array(n); + for (var i = 0; i < n; ++i) + copy[i] = arr[i]; + return copy; +} + +function spliceOne(list, index) { + for (; index + 1 < list.length; index++) + list[index] = list[index + 1]; + list.pop(); +} + +function unwrapListeners(arr) { + var ret = new Array(arr.length); + for (var i = 0; i < ret.length; ++i) { + ret[i] = arr[i].listener || arr[i]; + } + return ret; +} + +function once(emitter, name) { + return new Promise(function (resolve, reject) { + function errorListener(err) { + emitter.removeListener(name, resolver); + reject(err); + } + + function resolver() { + if (typeof emitter.removeListener === 'function') { + emitter.removeListener('error', errorListener); + } + resolve([].slice.call(arguments)); + }; + + eventTargetAgnosticAddListener(emitter, name, resolver, { once: true }); + if (name !== 'error') { + addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true }); + } + }); +} + +function addErrorHandlerIfEventEmitter(emitter, handler, flags) { + if (typeof emitter.on === 'function') { + eventTargetAgnosticAddListener(emitter, 'error', handler, flags); + } +} + +function eventTargetAgnosticAddListener(emitter, name, listener, flags) { + if (typeof emitter.on === 'function') { + if (flags.once) { + emitter.once(name, listener); + } else { + emitter.on(name, listener); + } + } else if (typeof emitter.addEventListener === 'function') { + // EventTarget does not have `error` event semantics like Node + // EventEmitters, we do not listen for `error` events here. + emitter.addEventListener(name, function wrapListener(arg) { + // IE does not have builtin `{ once: true }` support so we + // have to do it manually. + if (flags.once) { + emitter.removeEventListener(name, wrapListener); + } + listener(arg); + }); + } else { + throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter); + } +} \ No newline at end of file