From 3288b37be796908b5023f6367e6208d521ccdcf6 Mon Sep 17 00:00:00 2001 From: Cohen Erickson Date: Sat, 4 Feb 2023 16:34:33 +0000 Subject: [PATCH] Remove bare switcher & update devcontainer files --- .devcontainer/Dockerfile | 20 +- .devcontainer/base.Dockerfile | 55 - .devcontainer/devcontainer.json | 33 +- static/options/index.html | 13 - static/resources/appModules/BareSwitcher.js | 103 -- .../resources/appModules/database-manager.js | 24 - static/resources/nebulamain.js | 20 - static/uv/uv.config.js | 2 +- static/uv/uv.sw.js | 1233 ++++++++--------- 9 files changed, 621 insertions(+), 882 deletions(-) delete mode 100644 .devcontainer/base.Dockerfile delete mode 100644 static/resources/appModules/BareSwitcher.js delete mode 100644 static/resources/appModules/database-manager.js diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 2b286811..be84a64d 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,14 +1,12 @@ -# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster -ARG VARIANT=16-bullseye -FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} +FROM node:18 -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends +# Install basic development tools +RUN apt update && apt install -y less man-db sudo -# [Optional] Uncomment if you want to install an additional version of node using nvm -# ARG EXTRA_NODE_VERSION=10 -# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" +# Ensure default `node` user has access to `sudo` +ARG USERNAME=node +RUN echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ + && chmod 0440 /etc/sudoers.d/$USERNAME -# [Optional] Uncomment if you want to install more global node modules -# RUN su node -c "npm install -g " +# Set `DEVCONTAINER` environment variable to help with orientation +ENV DEVCONTAINER=true diff --git a/.devcontainer/base.Dockerfile b/.devcontainer/base.Dockerfile deleted file mode 100644 index a3e066fd..00000000 --- a/.devcontainer/base.Dockerfile +++ /dev/null @@ -1,55 +0,0 @@ -# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster -ARG VARIANT=16-bullseye -FROM node:${VARIANT} - -# [Option] Install zsh -ARG INSTALL_ZSH="true" -# [Option] Upgrade OS packages to their latest versions -ARG UPGRADE_PACKAGES="true" - -# Install needed packages, yarn, nvm and setup non-root user. Use a separate RUN statement to add your own dependencies. -ARG USERNAME=node -ARG USER_UID=1000 -ARG USER_GID=$USER_UID -ARG NPM_GLOBAL=/usr/local/share/npm-global -ENV NVM_DIR=/usr/local/share/nvm -ENV NVM_SYMLINK_CURRENT=true \ - PATH=${NPM_GLOBAL}/bin:${NVM_DIR}/current/bin:${PATH} -COPY library-scripts/*.sh library-scripts/*.env /tmp/library-scripts/ -RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ - # Remove imagemagick due to https://security-tracker.debian.org/tracker/CVE-2019-10131 - && apt-get purge -y imagemagick imagemagick-6-common \ - # Install common packages, non-root user, update yarn and install nvm - && bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" "true" "true" \ - # Install yarn, nvm - && rm -rf /opt/yarn-* /usr/local/bin/yarn /usr/local/bin/yarnpkg \ - && bash /tmp/library-scripts/node-debian.sh "${NVM_DIR}" "none" "${USERNAME}" \ - # Configure global npm install location, use group to adapt to UID/GID changes - && if ! cat /etc/group | grep -e "^npm:" > /dev/null 2>&1; then groupadd -r npm; fi \ - && usermod -a -G npm ${USERNAME} \ - && umask 0002 \ - && mkdir -p ${NPM_GLOBAL} \ - && touch /usr/local/etc/npmrc \ - && chown ${USERNAME}:npm ${NPM_GLOBAL} /usr/local/etc/npmrc \ - && chmod g+s ${NPM_GLOBAL} \ - && npm config -g set prefix ${NPM_GLOBAL} \ - && sudo -u ${USERNAME} npm config -g set prefix ${NPM_GLOBAL} \ - # Install eslint - && su ${USERNAME} -c "umask 0002 && npm install -g eslint" \ - && npm cache clean --force > /dev/null 2>&1 \ - # Install python-is-python3 on bullseye to prevent node-gyp regressions - && . /etc/os-release \ - && if [ "${VERSION_CODENAME}" = "bullseye" ]; then apt-get -y install --no-install-recommends python-is-python3; fi \ - # Clean up - && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /root/.gnupg /tmp/library-scripts - -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends - -# [Optional] Uncomment if you want to install an additional version of node using nvm -# ARG EXTRA_NODE_VERSION=10 -# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" - -# [Optional] Uncomment if you want to install more global node modules -# RUN su node -c "npm install -g "" \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0e29ebe1..17aceafa 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,32 +1,9 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/javascript-node +// See https://containers.dev/implementors/json_reference/ for configuration reference { - "name": "Node.js", + "name": "Nebula", "build": { - "dockerfile": "Dockerfile", - // Update 'VARIANT' to pick a Node version: 18, 16, 14. - // Append -bullseye or -buster to pin to an OS version. - // Use -bullseye variants on local arm64/Apple Silicon. - "args": { "VARIANT": "16-bullseye" } + "dockerfile": "Dockerfile" }, - - // Configure tool-specific properties. - "customizations": { - // Configure properties specific to VS Code. - "vscode": { - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "dbaeumer.vscode-eslint" - ] - } - }, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "npm install", - - // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "node" + "remoteUser": "node", + "postCreateCommand": "npm install" } diff --git a/static/options/index.html b/static/options/index.html index 09e75694..d69fcd7c 100644 --- a/static/options/index.html +++ b/static/options/index.html @@ -12,7 +12,6 @@ - @@ -141,13 +140,6 @@

{ -document.getElementById("bareLocationInput").value = bareLocation; -}); - diff --git a/static/resources/appModules/BareSwitcher.js b/static/resources/appModules/BareSwitcher.js deleted file mode 100644 index 0ccc6e3c..00000000 --- a/static/resources/appModules/BareSwitcher.js +++ /dev/null @@ -1,103 +0,0 @@ -// NOTE: THIS FILE MUST BE ACCOMPANIED BY THE ULTRAVIOLET BUNDLE BECAUSE IT CONTAINS THE INDEXEDDB LIBRARY - -const dbPromise = Ultraviolet.openDB('keyval-store', 1, { - upgrade (db) { - db.createObjectStore('keyval') - } -}) - -function getBareLocation () { - return dbPromise - .then(db => db.get('keyval', 'bareLocation')) - .then(value => value || '') -} - -self.storage = { - async get (key) { - return (await dbPromise).get('keyval', key) - }, - - async set (key, val) { - return (await dbPromise).put('keyval', val, key) - }, - - async del (key) { - return (await dbPromise).delete('keyval', key) - } -} - -function setBareLocation (location) { - if ( - /^http(s?):\/\//.test(location) || - (location.includes('.') && val.substr(0, 1) !== ' ') || - location.includes('/bare/') - ) { - storage.set('bareLocation', location) - return 'Bare is located at: ' + location - } else { - console.log( - 'Invalid Location provided, please provide a server in the format of http(s)://server.domain.com/' - ) - return 'Invalid Location provided' - } -} - -function bareValidator (bareLocation) { - try { - // open a request to the bare location - var xmlHttp = new XMLHttpRequest() - xmlHttp.open('GET', bareLocation, false) // false for synchronous request - xmlHttp.send(null) - const _response = xmlHttp.responseText - // turn the response text into json - - const response = JSON.parse(_response) - - if (response.project.name === 'bare-server-node') { - console.log('Bare located at: ' + bareLocation + '') - return true - } else { - console.error('Bare not found at: ' + bareLocation) - return false - } - } catch (error) { - console.error( - 'An error occured while attempting to identify the bare server at: ' + - bareLocation - ) - return false - } -} - -window.addEventListener('load', () => { - console.log('Loaded ') - const _loc = document.getElementById('bareLocationInput') - const indicator = document.getElementById('validIndicator') - - // wait 3 seconds - setTimeout(() => { - if (bareValidator(_loc.value) === true) { - indicator.innerText = 'Connected to server: ' + _loc.value - indicator.style.color = '#42f851' - } else if (bareValidator(_loc.value) === false) { - indicator.innerText = 'Could not connect to server: ' + _loc.value - indicator.style.color = '#f45145bd' - } - }, 1000) - - document - .getElementById('bareLocationInput') - .addEventListener('keydown', function (event) { - if (event.key === 'Enter') { - if (bareValidator(_loc.value) === true) { - indicator.innerText = 'Connected to server: ' + _loc.value - indicator.style.color = '#42f851' - setBareLocation(_loc.value) - } else if (bareValidator(_loc.value) === false) { - _loc.value = '' - indicator.innerText = 'Could not connect to server: ' + _loc.value - indicator.style.color = '#f45145bd' - } - } - }) -}) diff --git a/static/resources/appModules/database-manager.js b/static/resources/appModules/database-manager.js deleted file mode 100644 index 847a85be..00000000 --- a/static/resources/appModules/database-manager.js +++ /dev/null @@ -1,24 +0,0 @@ -// NOTE - This file is not used in the current version of the app. -// this is just a snippet of code that I am keeping for future reference. -// NOTE: THIS FILE MUST BE ACCOMPANIED BY THE ULTRAVIOLET BUNDLE BECAUSE IT CONTAINS THE INDEXEDDB LIBRARY - - -const dbPromise = Ultraviolet.openDB('keyval-store', 1, { - upgrade (db) { - db.createObjectStore('keyval') - } -}) - -self.storage = { - async get (key) { - return (await dbPromise).get('keyval', key) - }, - - async set (key, val) { - return (await dbPromise).put('keyval', val, key) - }, - - async del (key) { - return (await dbPromise).delete('keyval', key) - } -} diff --git a/static/resources/nebulamain.js b/static/resources/nebulamain.js index a68f9a8a..5ec42baa 100644 --- a/static/resources/nebulamain.js +++ b/static/resources/nebulamain.js @@ -40,26 +40,6 @@ window.addEventListener('load', () => { } } - function getBareLocation () { - return dbPromise - .then(db => db.get('keyval', 'bareLocation')) - .then(value => value || '') - } - - getBareLocation().then(bareLocation => { - - console.log('Bare Location: ' + bareLocation) - if (bareLocation === '') { - console.log('No bare location found, automatically setting to default') - storage.set('bareLocation', '/bare/') - - } - - // if bare location is not only whitespace - // if (bareLocation.trim() !== '') { - - }) - navigator.serviceWorker.register('./sw.js', { scope: '/service/' }) diff --git a/static/uv/uv.config.js b/static/uv/uv.config.js index 95ab7d83..8731966b 100644 --- a/static/uv/uv.config.js +++ b/static/uv/uv.config.js @@ -1,6 +1,6 @@ self.__uv$config = { prefix: '/service/go/', - bare: '', + bare: '/bare/', encodeUrl: Ultraviolet.codec.xor.encode, decodeUrl: Ultraviolet.codec.xor.decode, handler: '/uv/uv.handler.js', diff --git a/static/uv/uv.sw.js b/static/uv/uv.sw.js index 97d7d1c9..cbe5ddf6 100644 --- a/static/uv/uv.sw.js +++ b/static/uv/uv.sw.js @@ -1,372 +1,351 @@ -importScripts('/uv/uv.bundle.js'); -importScripts('/uv/uv.config.js'); - +importScripts("/uv/uv.bundle.js"); +importScripts("/uv/uv.config.js"); class UVServiceWorker extends EventEmitter { - constructor(config = __uv$config) { - super(); - -const dbPromise = Ultraviolet.openDB('keyval-store', 1, { - upgrade(db) { - db.createObjectStore('keyval'); - }, -}); - -function getBareLocation() { - return dbPromise.then(db => db.get('keyval', 'bareLocation')).then(value => value || ''); -} -getBareLocation().then(bareLocation => { - if (!config.bare) config.bare = bareLocation; - this.addresses = Array.isArray(config.bare) - ? config.bare.map(str => new URL(str, location)) - : typeof config.bare === 'string' - ? [new URL(config.bare, 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', - ], - }; - this.method = { - empty: [ - 'GET', - 'HEAD' - ] - }; - this.statusCode = { - empty: [ - 204, - 304, - ], - }; - this.config = config; - this.browser = Ultraviolet.Bowser.getParser(self.navigator.userAgent).getBrowserName(); - - if (this.browser === 'Firefox') { - this.headers.forward.push('user-agent'); - this.headers.forward.push('content-type'); - }; - }; - async fetch({ request }) { - if (!request.url.startsWith(location.origin + (this.config.prefix || '/service/'))) { - return fetch(request); - }; - try { - - const ultraviolet = new Ultraviolet(this.config); - - if (typeof this.config.construct === 'function') { - this.config.construct(ultraviolet, 'service'); - }; - - const db = await ultraviolet.cookie.db(); - - ultraviolet.meta.origin = location.origin; - ultraviolet.meta.base = ultraviolet.meta.url = new URL(ultraviolet.sourceUrl(request.url)); - - 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; - requestCtx.base = requestCtx.url = new URL(requestCtx.url.pathname); - }; - - if (request.referrer && request.referrer.startsWith(location.origin)) { - const referer = new URL(ultraviolet.sourceUrl(request.referrer)); - - if (requestCtx.headers.origin || ultraviolet.meta.url.origin !== referer.origin && request.mode === 'cors') { - requestCtx.headers.origin = referer.origin; - }; - - requestCtx.headers.referer = referer.href; - }; - - const cookies = await ultraviolet.cookie.getCookies(db) || []; - const cookieStr = ultraviolet.cookie.serialize(cookies, ultraviolet.meta, false); - - if (this.browser === 'Firefox' && !(request.destination === 'iframe' || request.destination === 'document')) { - requestCtx.forward.shift(); - }; - - if (cookieStr) requestCtx.headers.cookie = cookieStr; - requestCtx.headers.Host = requestCtx.url.host; - - - const reqEvent = new HookEvent(requestCtx, null, null); - this.emit('request', reqEvent); - - if (reqEvent.intercepted) return reqEvent.returnValue; - - const response = await fetch(requestCtx.send); - - if (response.status === 500) { - return Promise.reject(''); - }; - - 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 = ultraviolet.rewriteUrl(responseCtx.headers.location); - }; - - if (responseCtx.headers['set-cookie']) { - 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: ultraviolet.meta.url.href, - }); - }); - }); - }); - delete responseCtx.headers['set-cookie']; - }; - - if (responseCtx.body) { - switch(request.destination) { - case 'script': - case 'worker': - responseCtx.body = `if (!self.__uv && self.importScripts) importScripts('${__uv$config.bundle}', '${__uv$config.config}', '${__uv$config.handler}');\n`; - responseCtx.body += ultraviolet.js.rewrite( - await response.text() - ); - break; - case 'style': - responseCtx.body = ultraviolet.rewriteCSS( - await response.text() - ); - break; - case 'iframe': - case 'document': - if (isHtml(ultraviolet.meta.url, (responseCtx.headers['content-type'] || ''))) { - responseCtx.body = ultraviolet.rewriteHtml( - await response.text(), - { - document: true , - injectHead: ultraviolet.createHtmlInject( - this.config.handler, - this.config.bundle, - this.config.config, - ultraviolet.cookie.serialize(cookies, ultraviolet.meta, true), - request.referrer - ) - } - ); - }; - }; - }; - - if (requestCtx.headers.accept === '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(err) { - console.error(err); - return new Response(err.toString(), { - status: 500, - headers: { - 'x-uv-error': err, - } - }); - }; - }; - 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; + 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', + ], + }; + this.method = { + empty: [ + 'GET', + 'HEAD' + ] + }; + this.statusCode = { + empty: [ + 204, + 304, + ], + }; + this.config = config; + this.browser = Ultraviolet.Bowser.getParser(self.navigator.userAgent).getBrowserName(); + + if (this.browser === 'Firefox') { + this.headers.forward.push('user-agent'); + this.headers.forward.push('content-type'); + }; + }; + async fetch({ request }) { + if (!request.url.startsWith(location.origin + (this.config.prefix || '/service/'))) { + return fetch(request); + }; + try { + + const ultraviolet = new Ultraviolet(this.config); + + if (typeof this.config.construct === 'function') { + this.config.construct(ultraviolet, 'service'); + }; + + const db = await ultraviolet.cookie.db(); + + ultraviolet.meta.origin = location.origin; + ultraviolet.meta.base = ultraviolet.meta.url = new URL(ultraviolet.sourceUrl(request.url)); + + 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; + requestCtx.base = requestCtx.url = new URL(requestCtx.url.pathname); + }; + + if (request.referrer && request.referrer.startsWith(location.origin)) { + const referer = new URL(ultraviolet.sourceUrl(request.referrer)); + + if (requestCtx.headers.origin || ultraviolet.meta.url.origin !== referer.origin && request.mode === 'cors') { + requestCtx.headers.origin = referer.origin; + }; + + requestCtx.headers.referer = referer.href; + }; + + const cookies = await ultraviolet.cookie.getCookies(db) || []; + const cookieStr = ultraviolet.cookie.serialize(cookies, ultraviolet.meta, false); + + if (this.browser === 'Firefox' && !(request.destination === 'iframe' || request.destination === 'document')) { + requestCtx.forward.shift(); + }; + + if (cookieStr) requestCtx.headers.cookie = cookieStr; + requestCtx.headers.Host = requestCtx.url.host; + + + const reqEvent = new HookEvent(requestCtx, null, null); + this.emit('request', reqEvent); + + if (reqEvent.intercepted) return reqEvent.returnValue; + + const response = await fetch(requestCtx.send); + + if (response.status === 500) { + return Promise.reject(''); + }; + + 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 = ultraviolet.rewriteUrl(responseCtx.headers.location); + }; + + if (responseCtx.headers['set-cookie']) { + 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: ultraviolet.meta.url.href, + }); + }); + }); + }); + delete responseCtx.headers['set-cookie']; + }; + + if (responseCtx.body) { + switch(request.destination) { + case 'script': + case 'worker': + responseCtx.body = `if (!self.__uv && self.importScripts) importScripts('${__uv$config.bundle}', '${__uv$config.config}', '${__uv$config.handler}');\n`; + responseCtx.body += ultraviolet.js.rewrite( + await response.text() + ); + break; + case 'style': + responseCtx.body = ultraviolet.rewriteCSS( + await response.text() + ); + break; + case 'iframe': + case 'document': + if (isHtml(ultraviolet.meta.url, (responseCtx.headers['content-type'] || ''))) { + responseCtx.body = ultraviolet.rewriteHtml( + await response.text(), + { + document: true , + injectHead: ultraviolet.createHtmlInject( + this.config.handler, + this.config.bundle, + this.config.config, + ultraviolet.cookie.serialize(cookies, ultraviolet.meta, true), + request.referrer + ) + } + ); + }; + }; + }; + + if (requestCtx.headers.accept === '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(err) { + return new Response(err.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; }; self.UVServiceWorker = UVServiceWorker; 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; - }; - get url() { - return this.request.url; - } - get base() { - return this.request.base; - }; - set base(val) { - this.request.base = val; - }; + 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; + }; + 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; - }; + 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'; + return (Ultraviolet.mime.contentType((contentType || url.pathname)) || 'text/html').split(';')[0] === 'text/html'; }; 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; - }; + #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); - } +? 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 +ReflectOwnKeys = R.ownKeys } else if (Object.getOwnPropertySymbols) { - ReflectOwnKeys = function ReflectOwnKeys(target) { - return Object.getOwnPropertyNames(target) - .concat(Object.getOwnPropertySymbols(target)); - }; +ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target) + .concat(Object.getOwnPropertySymbols(target)); +}; } else { - ReflectOwnKeys = function ReflectOwnKeys(target) { - return Object.getOwnPropertyNames(target); - }; +ReflectOwnKeys = function ReflectOwnKeys(target) { + return Object.getOwnPropertyNames(target); +}; } function ProcessEmitWarning(warning) { - if (console && console.warn) console.warn(warning); +if (console && console.warn) console.warn(warning); } var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) { - return value !== value; +return value !== value; } function EventEmitter() { - EventEmitter.init.call(this); +EventEmitter.init.call(this); } // Backwards-compat with node 0.10.x @@ -381,430 +360,430 @@ EventEmitter.prototype._maxListeners = undefined; 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); - } +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; +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; - } +if (this._events === undefined || + this._events === Object.getPrototypeOf(this)._events) { + this._events = Object.create(null); + this._eventsCount = 0; +} - this._maxListeners = this._maxListeners || undefined; +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; +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; +if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; +return that._maxListeners; } EventEmitter.prototype.getMaxListeners = function getMaxListeners() { - return _getMaxListeners(this); +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 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]; +var handler = events[type]; - if (handler === undefined) - return false; +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); - } +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; +return true; }; function _addListener(target, type, listener, prepend) { - var m; - var events; - var existing; +var m; +var events; +var existing; - checkListener(listener); +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]; +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; +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 { - 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); - } + 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); - } + // 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; +return target; } EventEmitter.prototype.addListener = function addListener(type, listener) { - return _addListener(this, type, listener, false); +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 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); - } +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; +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; +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; - }; + 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; + function removeListener(type, listener) { + var list, events, position, i, originalListener; - checkListener(listener); + checkListener(listener); - events = this._events; - if (events === undefined) - return this; + events = this._events; + if (events === undefined) + return this; - list = events[type]; - if (list === 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 (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) + return this; - if (position === 0) - list.shift(); - else { - spliceOne(list, position); - } + if (position === 0) + list.shift(); + else { + spliceOne(list, position); + } - if (list.length === 1) - events[type] = list[0]; + if (list.length === 1) + events[type] = list[0]; - if (events.removeListener !== undefined) - this.emit('removeListener', type, originalListener || listener); - } + if (events.removeListener !== undefined) + this.emit('removeListener', type, originalListener || listener); + } - return this; - }; + 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; + function removeAllListeners(type) { + var listeners, events, i; - // 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; - } + events = this._events; + if (events === undefined) + return this; - // emit removeListener for all listeners on all events + // not listening for removeListener, no need to emit + if (events.removeListener === undefined) { 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; + } 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]; + 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]); - } + 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; - }; + return this; + }; function _listeners(target, type, unwrap) { - var events = target._events; +var events = target._events; - if (events === undefined) - return []; +if (events === undefined) + return []; - var evlistener = events[type]; - if (evlistener === undefined) - return []; +var evlistener = events[type]; +if (evlistener === undefined) + return []; - if (typeof evlistener === 'function') - return unwrap ? [evlistener.listener || evlistener] : [evlistener]; +if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; - return unwrap ? - unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); +return unwrap ? + unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); } EventEmitter.prototype.listeners = function listeners(type) { - return _listeners(this, type, true); +return _listeners(this, type, true); }; EventEmitter.prototype.rawListeners = function rawListeners(type) { - return _listeners(this, type, false); +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); - } +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; +var events = this._events; - if (events !== undefined) { - var evlistener = events[type]; +if (events !== undefined) { + var evlistener = events[type]; - if (typeof evlistener === 'function') { - return 1; - } else if (evlistener !== undefined) { - return evlistener.length; - } + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; } +} - return 0; +return 0; } EventEmitter.prototype.eventNames = function eventNames() { - return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; +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; +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(); +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; +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)); - }; +return new Promise(function (resolve, reject) { + function errorListener(err) { + emitter.removeListener(name, resolver); + reject(err); + } - eventTargetAgnosticAddListener(emitter, name, resolver, { once: true }); - if (name !== 'error') { - addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true }); + 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); - } +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); - }); +if (typeof emitter.on === 'function') { + if (flags.once) { + emitter.once(name, listener); } else { - throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter); + 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); +} }