diff --git a/src/uv.config.js b/src/uv.config.js
new file mode 100644
index 0000000..16f08dd
--- /dev/null
+++ b/src/uv.config.js
@@ -0,0 +1,10 @@
+self.__uv$config = {
+ prefix: '/service/',
+ bare: 'https://incog.dev/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/src/uv.handler.js b/src/uv.handler.js
new file mode 100644
index 0000000..d67aca6
--- /dev/null
+++ b/src/uv.handler.js
@@ -0,0 +1,1129 @@
+if (!self.__uv) {
+ __uvHook(self, self.__uv$config, self.__uv$config.bare);
+};
+
+async function __uvHook(window, config = {}, bare = '/bare/') {
+ if ('__uv' in window && window.__uv instanceof Ultraviolet) return false;
+
+ if (window.document && !!window.window) {
+ window.document.querySelectorAll("script[__uv-script]").forEach(node => node.remove())
+ };
+
+ const worker = !window.window;
+ const master = '__uv';
+ const methodPrefix = '__uv$';
+ const __uv = new Ultraviolet({
+ ...config,
+ window,
+ });
+
+ if (typeof config.construct === 'function') {
+ config.construct(__uv, worker ? 'worker' : 'window');
+ };
+
+ const { client } = __uv;
+ const {
+ HTMLMediaElement,
+ HTMLScriptElement,
+ HTMLAudioElement,
+ HTMLVideoElement,
+ HTMLInputElement,
+ HTMLEmbedElement,
+ HTMLTrackElement,
+ HTMLAnchorElement,
+ HTMLIFrameElement,
+ HTMLAreaElement,
+ HTMLLinkElement,
+ HTMLBaseElement,
+ HTMLFormElement,
+ HTMLImageElement,
+ HTMLSourceElement,
+ } = window;
+
+ client.nativeMethods.defineProperty(window, '__uv', {
+ value: __uv,
+ enumerable: false,
+ });
+
+
+ __uv.meta.origin = location.origin;
+ __uv.location = client.location.emulate(
+ (href) => {
+ if (href === 'about:srcdoc') return new URL(href);
+ if (href.startsWith('blob:')) href = href.slice('blob:'.length);
+ return new URL(__uv.sourceUrl(href));
+ },
+ (href) => {
+ return __uv.rewriteUrl(href);
+ },
+ );
+
+ __uv.cookieStr = window.__uv$cookies || '';
+ __uv.meta.url = __uv.location;
+ __uv.domain = __uv.meta.url.host;
+ __uv.blobUrls = new window.Map();
+ __uv.referrer = '';
+ __uv.cookies = [];
+ __uv.localStorageObj = {};
+ __uv.sessionStorageObj = {};
+
+ try {
+ __uv.bare = new URL(bare, window.location.href);
+ } catch(e) {
+ __uv.bare = window.parent.__uv.bare;
+ };
+
+ if (__uv.location.href === 'about:srcdoc') {
+ __uv.meta = window.parent.__uv.meta;
+ };
+
+ if (window.EventTarget) {
+ __uv.addEventListener = window.EventTarget.prototype.addEventListener;
+ __uv.removeListener = window.EventTarget.prototype.removeListener;
+ __uv.dispatchEvent = window.EventTarget.prototype.dispatchEvent;
+ };
+
+ // Storage wrappers
+ client.nativeMethods.defineProperty(client.storage.storeProto, '__uv$storageObj', {
+ get() {
+ if (this === client.storage.sessionStorage) return __uv.sessionStorageObj;
+ if (this === client.storage.localStorage) return __uv.localStorageObj;
+ },
+ enumerable: false,
+ });
+
+ if (window.localStorage) {
+ for (const key in window.localStorage) {
+ if (key.startsWith(methodPrefix + __uv.location.origin + '@')) {
+ __uv.localStorageObj[key.slice((methodPrefix + __uv.location.origin + '@').length)] = window.localStorage.getItem(key);
+ };
+ };
+
+ __uv.lsWrap = client.storage.emulate(client.storage.localStorage, __uv.localStorageObj);
+ };
+
+ if (window.sessionStorage) {
+ for (const key in window.sessionStorage) {
+ if (key.startsWith(methodPrefix + __uv.location.origin + '@')) {
+ __uv.sessionStorageObj[key.slice((methodPrefix + __uv.location.origin + '@').length)] = window.sessionStorage.getItem(key);
+ };
+ };
+
+ __uv.ssWrap = client.storage.emulate(client.storage.sessionStorage, __uv.sessionStorageObj);
+ };
+
+
+
+ let rawBase = window.document ? client.node.baseURI.get.call(window.document) : window.location.href;
+ let base = __uv.sourceUrl(rawBase);
+
+ client.nativeMethods.defineProperty(__uv.meta, 'base', {
+ get() {
+ if (!window.document) return __uv.meta.url.href;
+
+ if (client.node.baseURI.get.call(window.document) !== rawBase) {
+ rawBase = client.node.baseURI.get.call(window.document);
+ base = __uv.sourceUrl(rawBase);
+ };
+
+ return base;
+ },
+ });
+
+
+ __uv.methods = {
+ setSource: methodPrefix + 'setSource',
+ source: methodPrefix + 'source',
+ location: methodPrefix + 'location',
+ function: methodPrefix + 'function',
+ string: methodPrefix + 'string',
+ eval: methodPrefix + 'eval',
+ parent: methodPrefix + 'parent',
+ top: methodPrefix + 'top',
+ };
+
+ __uv.filterKeys = [
+ master,
+ __uv.methods.setSource,
+ __uv.methods.source,
+ __uv.methods.location,
+ __uv.methods.function,
+ __uv.methods.string,
+ __uv.methods.eval,
+ __uv.methods.parent,
+ __uv.methods.top,
+ methodPrefix + 'protocol',
+ methodPrefix + 'storageObj',
+ methodPrefix + 'url',
+ methodPrefix + 'modifiedStyle',
+ methodPrefix + 'config',
+ methodPrefix + 'dispatched',
+ 'Ultraviolet',
+ '__uvHook',
+ ];
+
+
+ client.on('wrap', (target, wrapped) => {
+ client.nativeMethods.defineProperty(wrapped, 'name', client.nativeMethods.getOwnPropertyDescriptor(target, 'name'));
+ client.nativeMethods.defineProperty(wrapped, 'length', client.nativeMethods.getOwnPropertyDescriptor(target, 'length'));
+
+ client.nativeMethods.defineProperty(wrapped, __uv.methods.string, {
+ enumerable: false,
+ value: client.nativeMethods.fnToString.call(target),
+ });
+
+ client.nativeMethods.defineProperty(wrapped, __uv.methods.function, {
+ enumerable: false,
+ value: target,
+ });
+ });
+
+ client.fetch.on('request', event => {
+ event.data.input = __uv.rewriteUrl(event.data.input);
+ });
+
+ client.fetch.on('requestUrl', event => {
+ event.data.value = __uv.sourceUrl(event.data.value);
+ });
+
+ client.fetch.on('responseUrl', event => {
+ event.data.value = __uv.sourceUrl(event.data.value);
+ });
+
+ // XMLHttpRequest
+ client.xhr.on('open', event => {
+ event.data.input = __uv.rewriteUrl(event.data.input);
+ });
+
+ client.xhr.on('responseUrl', event => {
+ event.data.value = __uv.sourceUrl(event.data.value);
+ });
+
+
+ // Workers
+ client.workers.on('worker', event => {
+ event.data.url = __uv.rewriteUrl(event.data.url);
+ });
+
+ client.workers.on('addModule', event => {
+ event.data.url = __uv.rewriteUrl(event.data.url);
+ });
+
+ client.workers.on('importScripts', event => {
+ for (const i in event.data.scripts) {
+ event.data.scripts[i] = __uv.rewriteUrl(event.data.scripts[i]);
+ };
+ });
+
+ client.workers.on('postMessage', event => {
+ let to = event.data.origin;
+
+ event.data.origin = '*';
+ event.data.message = {
+ __data: event.data.message,
+ __origin: __uv.meta.url.origin,
+ __to: to,
+ };
+ });
+
+ // Navigator
+ client.navigator.on('sendBeacon', event => {
+ event.data.url = __uv.rewriteUrl(event.data.url);
+ });
+
+ // Cookies
+ client.document.on('getCookie', event => {
+ event.data.value = __uv.cookieStr;
+ });
+
+ client.document.on('setCookie', event => {
+ Promise.resolve(__uv.cookie.setCookies(event.data.value, __uv.db, __uv.meta)).then(() => {
+ __uv.cookie.db().then(db => {
+ __uv.cookie.getCookies(db).then(cookies => {
+ __uv.cookieStr = __uv.cookie.serialize(cookies, __uv.meta, true);
+ });
+ });
+ });
+ const cookie = __uv.cookie.setCookie(event.data.value)[0];
+
+ if (!cookie.path) cookie.path = '/';
+ if (!cookie.domain) cookie.domain = __uv.meta.url.hostname;
+
+ if (__uv.cookie.validateCookie(cookie, __uv.meta, true)) {
+ if (__uv.cookieStr.length) __uv.cookieStr += '; ';
+ __uv.cookieStr += `${cookie.name}=${cookie.value}`;
+ };
+
+ event.respondWith(event.data.value);
+ });
+
+ // HTML
+ client.element.on('setInnerHTML', event => {
+ switch (event.that.tagName) {
+ case 'SCRIPT':
+ event.data.value = __uv.js.rewrite(event.data.value);
+ break;
+ case 'STYLE':
+ event.data.value = __uv.rewriteCSS(event.data.value);
+ break;
+ default:
+ event.data.value = __uv.rewriteHtml(event.data.value);
+ };
+ });
+
+ client.element.on('getInnerHTML', event => {
+ switch (event.that.tagName) {
+ case 'SCRIPT':
+ event.data.value = __uv.js.source(event.data.value);
+ break;
+ default:
+ event.data.value = __uv.sourceHtml(event.data.value);
+ };
+ });
+
+ client.element.on('setOuterHTML', event => {
+ event.data.value = __uv.rewriteHtml(event.data.value, { document: event.that.tagName === 'HTML' });
+ });
+
+ client.element.on('getOuterHTML', event => {
+ switch (event.that.tagName) {
+ case 'HEAD':
+ event.data.value = __uv.sourceHtml(
+ event.data.value.replace(/
(.*)<\/head>/s, '$2')
+ ).replace(/(.*)<\/op-head>/s, '$2');
+ break;
+ case 'BODY':
+ event.data.value = __uv.sourceHtml(
+ event.data.value.replace(/(.*)<\/body>/s, '$2')
+ ).replace(/(.*)<\/op-body>/s, '$2');
+ break;
+ default:
+ event.data.value = __uv.sourceHtml(event.data.value, { document: event.that.tagName === 'HTML' });
+ break;
+ };
+
+ //event.data.value = __uv.sourceHtml(event.data.value, { document: event.that.tagName === 'HTML' });
+ });
+
+ client.document.on('write', event => {
+ if (!event.data.html.length) return false;
+ event.data.html = [__uv.rewriteHtml(event.data.html.join(''))];
+ });
+
+ client.document.on('writeln', event => {
+ if (!event.data.html.length) return false;
+ event.data.html = [__uv.rewriteHtml(event.data.html.join(''))];
+ });
+
+ client.element.on('insertAdjacentHTML', event => {
+ event.data.html = __uv.rewriteHtml(event.data.html);
+ });
+
+ // EventSource
+
+ client.eventSource.on('construct', event => {
+ event.data.url = __uv.rewriteUrl(event.data.url);
+ });
+
+
+ client.eventSource.on('url', event => {
+ event.data.url = __uv.rewriteUrl(event.data.url);
+ });
+
+ // History
+ client.history.on('replaceState', event => {
+ if (event.data.url) event.data.url = __uv.rewriteUrl(event.data.url, '__uv' in event.that ? event.that.__uv.meta : __uv.meta);
+ });
+ client.history.on('pushState', event => {
+ if (event.data.url) event.data.url = __uv.rewriteUrl(event.data.url, '__uv' in event.that ? event.that.__uv.meta : __uv.meta);
+ });
+
+ // Element get set attribute methods
+ client.element.on('getAttribute', event => {
+ if (client.element.hasAttribute.call(event.that, __uv.attributePrefix + '-attr-' + event.data.name)) {
+ event.respondWith(
+ event.target.call(event.that, __uv.attributePrefix + '-attr-' + event.data.name)
+ );
+ };
+ });
+
+ // Message
+ client.message.on('postMessage', event => {
+ let to = event.data.origin;
+ let call = __uv.call;
+
+
+ if (event.that) {
+ call = event.that.__uv$source.call;
+ };
+
+ event.data.origin = '*';
+ event.data.message = {
+ __data: event.data.message,
+ __origin: (event.that || event.target).__uv$source.location.origin,
+ __to: to,
+ };
+
+ event.respondWith(
+ worker ?
+ call(event.target, [event.data.message, event.data.transfer], event.that) :
+ call(event.target, [event.data.message, event.data.origin, event.data.transfer], event.that)
+ );
+
+ });
+
+ client.message.on('data', event => {
+ const { value: data } = event.data;
+ if (typeof data === 'object' && '__data' in data && '__origin' in data) {
+ event.respondWith(data.__data);
+ };
+ });
+
+ client.message.on('origin', event => {
+ const data = client.message.messageData.get.call(event.that);
+ if (typeof data === 'object' && data.__data && data.__origin) {
+ event.respondWith(data.__origin);
+ };
+ });
+
+ client.overrideDescriptor(window, 'origin', {
+ get: (target, that) => {
+ return __uv.location.origin;
+ },
+ });
+
+ client.node.on('baseURI', event => {
+ if (event.data.value.startsWith(window.location.origin)) event.data.value = __uv.sourceUrl(event.data.value);
+ });
+
+ client.element.on('setAttribute', event => {
+ if (event.that instanceof HTMLMediaElement && event.data.name === 'src' && event.data.value.startsWith('blob:')) {
+ event.target.call(event.that, __uv.attributePrefix + '-attr-' + event.data.name, event.data.value);
+ event.data.value = __uv.blobUrls.get(event.data.value);
+ return;
+ };
+
+ if (__uv.attrs.isUrl(event.data.name)) {
+ event.target.call(event.that, __uv.attributePrefix + '-attr-' + event.data.name, event.data.value);
+ event.data.value = __uv.rewriteUrl(event.data.value);
+ };
+
+ if (__uv.attrs.isStyle(event.data.name)) {
+ event.target.call(event.that, __uv.attributePrefix + '-attr-' + event.data.name, event.data.value);
+ event.data.value = __uv.rewriteCSS(event.data.value, { context: 'declarationList' });
+ };
+
+ if (__uv.attrs.isHtml(event.data.name)) {
+ event.target.call(event.that, __uv.attributePrefix + '-attr-' + event.data.name, event.data.value);
+ event.data.value = __uv.rewriteHtml(event.data.value, {...__uv.meta, document: true, injectHead:__uv.createHtmlInject(__uv.handlerScript, __uv.bundleScript, __uv.configScript, __uv.cookieStr, window.location.href) });
+ };
+
+ if (__uv.attrs.isSrcset(event.data.name)) {
+ event.target.call(event.that, __uv.attributePrefix + '-attr-' + event.data.name, event.data.value);
+ event.data.value = __uv.html.wrapSrcset(event.data.value);
+ };
+
+ if (__uv.attrs.isForbidden(event.data.name)) {
+ event.data.name = __uv.attributePrefix + '-attr-' + event.data.name;
+ };
+ });
+
+ client.element.on('audio', event => {
+ event.data.url = __uv.rewriteUrl(event.data.url);
+ });
+
+ // Element Property Attributes
+ client.element.hookProperty([HTMLAnchorElement, HTMLAreaElement, HTMLLinkElement, HTMLBaseElement], 'href', {
+ get: (target, that) => {
+ return __uv.sourceUrl(
+ target.call(that)
+ );
+ },
+ set: (target, that, [val]) => {
+ client.element.setAttribute.call(that, __uv.attributePrefix + '-attr-href', val)
+ target.call(that, __uv.rewriteUrl(val));
+ },
+ });
+
+ client.element.hookProperty([HTMLScriptElement, HTMLAudioElement, HTMLVideoElement, HTMLMediaElement, HTMLImageElement, HTMLInputElement, HTMLEmbedElement, HTMLIFrameElement, HTMLTrackElement, HTMLSourceElement], 'src', {
+ get: (target, that) => {
+ return __uv.sourceUrl(
+ target.call(that)
+ );
+ },
+ set: (target, that, [val]) => {
+ if (new String(val).toString().trim().startsWith('blob:') && that instanceof HTMLMediaElement) {
+ client.element.setAttribute.call(that, __uv.attributePrefix + '-attr-src', val)
+ return target.call(that, __uv.blobUrls.get(val) || val);
+ };
+
+ client.element.setAttribute.call(that, __uv.attributePrefix + '-attr-src', val)
+ target.call(that, __uv.rewriteUrl(val));
+ },
+ });
+
+ client.element.hookProperty([HTMLFormElement], 'action', {
+ get: (target, that) => {
+ return __uv.sourceUrl(
+ target.call(that)
+ );
+ },
+ set: (target, that, [val]) => {
+ client.element.setAttribute.call(that, __uv.attributePrefix + '-attr-action', val)
+ target.call(that, __uv.rewriteUrl(val));
+ },
+ });
+
+ client.element.hookProperty([HTMLImageElement], 'srcset', {
+ get: (target, that) => {
+ return client.element.getAttribute.call(that, __uv.attributePrefix + '-attr-srcset') || target.call(that);
+ },
+ set: (target, that, [val]) => {
+ client.element.setAttribute.call(that, __uv.attributePrefix + '-attr-srcset', val)
+ target.call(that, __uv.html.wrapSrcset(val));
+ },
+ });
+
+ client.element.hookProperty(HTMLScriptElement, 'integrity', {
+ get: (target, that) => {
+ return client.element.getAttribute.call(that, __uv.attributePrefix + '-attr-integrity');
+ },
+ set: (target, that, [val]) => {
+ client.element.setAttribute.call(that, __uv.attributePrefix + '-attr-integrity', val);
+ },
+ });
+
+ client.element.hookProperty(HTMLIFrameElement, 'sandbox', {
+ get: (target, that) => {
+ return client.element.getAttribute.call(that, __uv.attributePrefix + '-attr-sandbox') || target.call(that);
+ },
+ set: (target, that, [val]) => {
+ client.element.setAttribute.call(that, __uv.attributePrefix + '-attr-sandbox', val);
+ },
+ });
+
+ client.element.hookProperty(HTMLIFrameElement, 'contentWindow', {
+ get: (target, that) => {
+ const win = target.call(that);
+ try {
+ if (!win.__uv) __uvHook(win, config, bare);
+ return win;
+ } catch (e) {
+ return win;
+ };
+ },
+ });
+
+ client.element.hookProperty(HTMLIFrameElement, 'contentDocument', {
+ get: (target, that) => {
+ const doc = target.call(that);
+ try {
+ const win = doc.defaultView
+ if (!win.__uv) __uvHook(win, config, bare);
+ return doc;
+ } catch (e) {
+ return win;
+ };
+ },
+ });
+
+ client.element.hookProperty(HTMLIFrameElement, 'srcdoc', {
+ get: (target, that) => {
+ return client.element.getAttribute.call(that, __uv.attributePrefix + '-attr-srcdoc') || target.call(that);
+ },
+ set: (target, that, [val]) => {
+ target.call(that, __uv.rewriteHtml(val, {
+ document: true,
+ injectHead: __uv.createHtmlInject(__uv.handlerScript, __uv.bundleScript, __uv.configScript, __uv.cookieStr, window.location.href)
+ }))
+ },
+ });
+
+ client.node.on('getTextContent', event => {
+ if (event.that.tagName === 'SCRIPT') {
+ event.data.value = __uv.js.source(event.data.value);
+ };
+ });
+
+ client.node.on('setTextContent', event => {
+ if (event.that.tagName === 'SCRIPT') {
+ event.data.value = __uv.js.rewrite(event.data.value);
+ };
+ });
+
+ // Until proper rewriting is implemented for service workers.
+ // Not sure atm how to implement it with the already built in service worker
+ if ('serviceWorker' in window.navigator) {
+ delete window.Navigator.prototype.serviceWorker;
+ };
+
+ // Document
+ client.document.on('getDomain', event => {
+ event.data.value = __uv.domain;
+ });
+ client.document.on('setDomain', event => {
+ if (!event.data.value.toString().endsWith(__uv.meta.url.hostname.split('.').slice(-2).join('.'))) return event.respondWith('');
+ event.respondWith(__uv.domain = event.data.value);
+ })
+
+ client.document.on('url', event => {
+ event.data.value = __uv.location.href;
+ });
+
+ client.document.on('documentURI', event => {
+ event.data.value = __uv.location.href;
+ });
+
+ client.document.on('referrer', event => {
+ event.data.value = __uv.referrer || __uv.sourceUrl(event.data.value);
+ });
+
+ client.document.on('parseFromString', event => {
+ if (event.data.type !== 'text/html') return false;
+ event.data.string = __uv.rewriteHtml(event.data.string, {...__uv.meta, document: true, });
+ });
+
+ // Attribute (node.attributes)
+ client.attribute.on('getValue', event => {
+ if (client.element.hasAttribute.call(event.that.ownerElement, __uv.attributePrefix + '-attr-' + event.data.name)) {
+ event.data.value = client.element.getAttribute.call(event.that.ownerElement, __uv.attributePrefix + '-attr-' + event.data.name);
+ };
+ });
+
+ client.attribute.on('setValue', event => {
+ if (__uv.attrs.isUrl(event.data.name)) {
+ client.element.setAttribute.call(event.that.ownerElement, __uv.attributePrefix + '-attr-' + event.data.name, event.data.value);
+ event.data.value = __uv.rewriteUrl(event.data.value);
+ };
+
+ if (__uv.attrs.isStyle(event.data.name)) {
+ client.element.setAttribute.call(event.that.ownerElement, __uv.attributePrefix + '-attr-' + event.data.name, event.data.value);
+ event.data.value = __uv.rewriteCSS(event.data.value, { context: 'declarationList' });
+ };
+
+ if (__uv.attrs.isHtml(event.data.name)) {
+ client.element.setAttribute.call(event.that.ownerElement, __uv.attributePrefix + '-attr-' + event.data.name, event.data.value);
+ event.data.value = __uv.rewriteHtml(event.data.value, {...__uv.meta, document: true, injectHead:__uv.createHtmlInject(__uv.handlerScript, __uv.bundleScript, __uv.configScript, __uv.cookieStr, window.location.href) });
+ };
+
+ if (__uv.attrs.isSrcset(event.data.name)) {
+ client.element.setAttribute.call(event.that.ownerElement, __uv.attributePrefix + '-attr-' + event.data.name, event.data.value);
+ event.data.value = __uv.html.wrapSrcset(event.data.value);
+ };
+
+ });
+
+ // URL
+ client.url.on('createObjectURL', event => {
+ let url = event.target.call(event.that, event.data.object);
+ if (url.startsWith('blob:' + location.origin)) {
+ let newUrl = 'blob:' + (__uv.meta.url.href !== 'about:blank' ? __uv.meta.url.origin : window.parent.__uv.meta.url.origin) + url.slice('blob:'.length + location.origin.length);
+ __uv.blobUrls.set(newUrl, url);
+ event.respondWith(newUrl);
+ } else {
+ event.respondWith(url);
+ };
+ });
+
+ client.url.on('revokeObjectURL', event => {
+ if (__uv.blobUrls.has(event.data.url)) {
+ const old = event.data.url;
+ event.data.url = __uv.blobUrls.get(event.data.url);
+ __uv.blobUrls.delete(old);
+ };
+ });
+
+ client.storage.on('get', event => {
+ event.data.name = methodPrefix + __uv.meta.url.origin + '@' + event.data.name;
+ });
+
+ client.storage.on('set', event => {
+ if (event.that.__uv$storageObj) {
+ event.that.__uv$storageObj[event.data.name] = event.data.value;
+ };
+ event.data.name = methodPrefix + __uv.meta.url.origin + '@' + event.data.name;
+ });
+
+ client.storage.on('delete', event => {
+ if (event.that.__uv$storageObj) {
+ delete event.that.__uv$storageObj[event.data.name];
+ };
+ event.data.name = methodPrefix + __uv.meta.url.origin + '@' + event.data.name;
+ });
+
+ client.storage.on('getItem', event => {
+ event.data.name = methodPrefix + __uv.meta.url.origin + '@' + event.data.name;
+ });
+
+ client.storage.on('setItem', event => {
+ if (event.that.__uv$storageObj) {
+ event.that.__uv$storageObj[event.data.name] = event.data.value;
+ };
+ event.data.name = methodPrefix + __uv.meta.url.origin + '@' + event.data.name;
+ });
+
+ client.storage.on('removeItem', event => {
+ if (event.that.__uv$storageObj) {
+ delete event.that.__uv$storageObj[event.data.name];
+ };
+ event.data.name = methodPrefix + __uv.meta.url.origin + '@' + event.data.name;
+ });
+
+ client.storage.on('clear', event => {
+ if (event.that.__uv$storageObj) {
+ for (const key of client.nativeMethods.keys.call(null, event.that.__uv$storageObj)) {
+ delete event.that.__uv$storageObj[key];
+ client.storage.removeItem.call(event.that, methodPrefix + __uv.meta.url.origin + '@' + key);
+ event.respondWith();
+ };
+ };
+ });
+
+ client.storage.on('length', event => {
+ if (event.that.__uv$storageObj) {
+ event.respondWith(client.nativeMethods.keys.call(null, event.that.__uv$storageObj).length);
+ };
+ });
+
+ client.storage.on('key', event => {
+ if (event.that.__uv$storageObj) {
+ event.respondWith(
+ (client.nativeMethods.keys.call(null, event.that.__uv$storageObj)[event.data.index] || null)
+ );
+ };
+ });
+
+ client.websocket.on('websocket', async event => {
+ let url;
+ try {
+ url = new URL(event.data.url);
+ } catch(e) {
+ return;
+ };
+
+ const headers = {
+ Host: url.host,
+ Origin: __uv.meta.url.origin,
+ Pragma: 'no-cache',
+ 'Cache-Control': 'no-cache',
+ Upgrade: 'websocket',
+ 'User-Agent': window.navigator.userAgent,
+ 'Connection': 'Upgrade',
+ };
+
+ const cookies = __uv.cookie.serialize(__uv.cookies, { url }, false);
+
+ if (cookies) headers.Cookie = cookies;
+ const protocols = [...event.data.protocols];
+
+ const remote = {
+ protocol: url.protocol,
+ host: url.hostname,
+ port: url.port || (url.protocol === 'wss:' ? '443' : '80'),
+ path: url.pathname + url.search,
+ };
+
+ if (protocols.length) headers['Sec-WebSocket-Protocol'] = protocols.join(', ');
+
+ event.data.url = (__uv.bare.protocol === 'https:' ? 'wss://' : 'ws://') + __uv.bare.host + __uv.bare.pathname + 'v1/';
+ event.data.protocols = [
+ 'bare',
+ __uv.encodeProtocol(JSON.stringify({
+ remote,
+ headers,
+ forward_headers: [
+ 'accept',
+ 'accept-encoding',
+ 'accept-language',
+ 'sec-websocket-extensions',
+ 'sec-websocket-key',
+ 'sec-websocket-version',
+ ],
+ })),
+ ];
+
+ const ws = new event.target(event.data.url, event.data.protocols);
+
+ client.nativeMethods.defineProperty(ws, methodPrefix + 'url', {
+ enumerable: false,
+ value: url.href,
+ });
+
+ event.respondWith(
+ ws
+ );
+ });
+
+ client.websocket.on('url', event => {
+ if ('__uv$url' in event.that) {
+ event.data.value = event.that.__uv$url;
+ };
+ });
+
+ client.websocket.on('protocol', event => {
+ if ('__uv$protocol' in event.that) {
+ event.data.value = event.that.__uv$protocol;
+ };
+ });
+
+ client.function.on('function', event => {
+ event.data.script = __uv.rewriteJS(event.data.script);
+ });
+
+ client.function.on('toString', event => {
+ if (__uv.methods.string in event.that) event.respondWith(event.that[__uv.methods.string]);
+ });
+
+ client.object.on('getOwnPropertyNames', event => {
+ event.data.names = event.data.names.filter(element => !(__uv.filterKeys.includes(element)));
+ });
+
+ client.object.on('getOwnPropertyDescriptors', event => {
+ for (const forbidden of __uv.filterKeys) {
+ delete event.data.descriptors[forbidden];
+ };
+
+ });
+
+ client.style.on('setProperty', event => {
+ if (client.style.dashedUrlProps.includes(event.data.property)) {
+ event.data.value = __uv.rewriteCSS(event.data.value, {
+ context: 'value',
+ ...__uv.meta
+ })
+ };
+ });
+
+ client.style.on('getPropertyValue', event => {
+ if (client.style.dashedUrlProps.includes(event.data.property)) {
+ event.respondWith(
+ __uv.sourceCSS(
+ event.target.call(event.that, event.data.property),
+ {
+ context: 'value',
+ ...__uv.meta
+ }
+ )
+ );
+ };
+ });
+
+ if ('CSS2Properties' in window) {
+ for (const key of client.style.urlProps) {
+ client.overrideDescriptor(window.CSS2Properties.prototype, key, {
+ get: (target, that) => {
+ return __uv.sourceCSS(
+ target.call(that),
+ {
+ context: 'value',
+ ...__uv.meta
+ }
+ )
+ },
+ set: (target, that, val) => {
+ target.call(
+ that,
+ __uv.rewriteCSS(val, {
+ context: 'value',
+ ...__uv.meta
+ })
+ );
+ }
+ });
+ };
+ } else if ('HTMLElement' in window) {
+
+ client.overrideDescriptor(
+ window.HTMLElement.prototype,
+ 'style',
+ {
+ get: (target, that) => {
+ const value = target.call(that);
+ if (!value[methodPrefix + 'modifiedStyle']) {
+
+ for (const key of client.style.urlProps) {
+ client.nativeMethods.defineProperty(value, key, {
+ enumerable: true,
+ configurable: true,
+ get() {
+ const value = client.style.getPropertyValue.call(this, key) || '';
+ return __uv.sourceCSS(
+ value,
+ {
+ context: 'value',
+ ...__uv.meta
+ }
+ )
+ },
+ set(val) {
+ client.style.setProperty.call(this,
+ (client.style.propToDashed[key] || key),
+ __uv.rewriteCSS(val, {
+ context: 'value',
+ ...__uv.meta
+ })
+ )
+ }
+ });
+ client.nativeMethods.defineProperty(value, methodPrefix + 'modifiedStyle', {
+ enumerable: false,
+ value: true
+ });
+ };
+ };
+ return value;
+ }
+ }
+ );
+ };
+
+ client.style.on('setCssText', event => {
+ event.data.value = __uv.rewriteCSS(event.data.value, {
+ context: 'declarationList',
+ ...__uv.meta
+ });
+ });
+
+ client.style.on('getCssText', event => {
+ event.data.value = __uv.sourceCSS(event.data.value, {
+ context: 'declarationList',
+ ...__uv.meta
+ });
+ });
+
+ // Proper hash emulation.
+ if (!!window.window) {
+ __uv.addEventListener.call(window, 'hashchange', event => {
+ if (event.__uv$dispatched) return false;
+ event.stopImmediatePropagation();
+ const hash = window.location.hash;
+ client.history.replaceState.call(window.history, '', '', event.oldURL);
+ __uv.location.hash = hash;
+ });
+ };
+
+ client.location.on('hashchange', (oldUrl, newUrl, ctx) => {
+ if (ctx.HashChangeEvent && client.history.replaceState) {
+ client.history.replaceState.call(window.history, '', '', __uv.rewriteUrl(newUrl));
+
+ const event = new ctx.HashChangeEvent('hashchange', { newURL: newUrl, oldURL: oldUrl });
+
+ client.nativeMethods.defineProperty(event, methodPrefix + 'dispatched', {
+ value: true,
+ enumerable: false,
+ });
+
+ __uv.dispatchEvent.call(window, event);
+ };
+ });
+
+ // Hooking functions & descriptors
+ client.fetch.overrideRequest();
+ client.fetch.overrideUrl();
+ client.xhr.overrideOpen();
+ client.xhr.overrideResponseUrl();
+ client.element.overrideHtml();
+ client.element.overrideAttribute();
+ client.element.overrideInsertAdjacentHTML();
+ client.element.overrideAudio();
+ // client.element.overrideQuerySelector();
+ client.node.overrideBaseURI();
+ client.node.overrideTextContent();
+ client.attribute.overrideNameValue();
+ client.document.overrideDomain();
+ client.document.overrideURL();
+ client.document.overrideDocumentURI();
+ client.document.overrideWrite();
+ client.document.overrideReferrer();
+ client.document.overrideParseFromString();
+ client.storage.overrideMethods();
+ client.storage.overrideLength();
+ //client.document.overrideQuerySelector();
+ client.object.overrideGetPropertyNames();
+ client.object.overrideGetOwnPropertyDescriptors();
+ client.history.overridePushState();
+ client.history.overrideReplaceState();
+ client.eventSource.overrideConstruct();
+ client.eventSource.overrideUrl();
+ client.websocket.overrideWebSocket();
+ client.websocket.overrideProtocol();
+ client.websocket.overrideUrl();
+ client.url.overrideObjectURL();
+ client.document.overrideCookie();
+ client.message.overridePostMessage();
+ client.message.overrideMessageOrigin();
+ client.message.overrideMessageData();
+ client.workers.overrideWorker();
+ client.workers.overrideAddModule();
+ client.workers.overrideImportScripts();
+ client.workers.overridePostMessage();
+ client.style.overrideSetGetProperty();
+ client.style.overrideCssText();
+ client.navigator.overrideSendBeacon();
+ client.function.overrideFunction();
+ client.function.overrideToString();
+ client.location.overrideWorkerLocation(
+ (href) => {
+ return new URL(__uv.sourceUrl(href));
+ }
+ );
+
+ client.overrideDescriptor(window, 'localStorage', {
+ get: (target, that) => {
+ return (that || window).__uv.lsWrap;
+ },
+ });
+ client.overrideDescriptor(window, 'sessionStorage', {
+ get: (target, that) => {
+ return (that || window).__uv.ssWrap;
+ },
+ });
+
+
+ client.override(window, 'open', (target, that, args) => {
+ if (!args.length) return target.apply(that, args);
+ let [url] = args;
+
+ url = __uv.rewriteUrl(url);
+
+ return target.call(that, url);
+ });
+
+ __uv.$wrap = function(name) {
+ if (name === 'location') return __uv.methods.location;
+ if (name === 'eval') return __uv.methods.eval;
+ return name;
+ };
+
+
+ __uv.$get = function(that) {
+ if (that === window.location) return __uv.location;
+ if (that === window.eval) return __uv.eval;
+ if (that === window.parent) {
+ return window.__uv$parent;
+ };
+ if (that === window.top) {
+ return window.__uv$top;
+ };
+ return that;
+ };
+
+ __uv.eval = client.wrap(window, 'eval', (target, that, args) => {
+ if (!args.length || typeof args[0] !== 'string') return target.apply(that, args);
+ let [script] = args;
+
+ script = __uv.rewriteJS(script);
+ return target.call(that, script);
+ });
+
+ __uv.call = function(target, args, that) {
+ return that ? target.apply(that, args) : target(...args);
+ };
+
+ __uv.call$ = function(obj, prop, args = []) {
+ return obj[prop].apply(obj, args);
+ };
+
+ client.nativeMethods.defineProperty(window.Object.prototype, master, {
+ get: () => {
+ return __uv;
+ },
+ enumerable: false
+ });
+
+ client.nativeMethods.defineProperty(window.Object.prototype, __uv.methods.setSource, {
+ value: function(source) {
+ if (!client.nativeMethods.isExtensible(this)) return this;
+
+ client.nativeMethods.defineProperty(this, __uv.methods.source, {
+ value: source,
+ writable: true,
+ enumerable: false
+ });
+
+ return this;
+ },
+ enumerable: false,
+ });
+
+ client.nativeMethods.defineProperty(window.Object.prototype, __uv.methods.source, {
+ value: __uv,
+ writable: true,
+ enumerable: false
+ });
+
+ client.nativeMethods.defineProperty(window.Object.prototype, __uv.methods.location, {
+ configurable: true,
+ get() {
+ return (this === window.document || this === window) ? __uv.location : this.location;
+ },
+ set(val) {
+ if (this === window.document || this === window) {
+ __uv.location.href = val;
+ } else {
+ this.location = val;
+ };
+ },
+ });
+
+ client.nativeMethods.defineProperty(window.Object.prototype, __uv.methods.parent, {
+ configurable: true,
+ get() {
+ const val = this.parent;
+
+ if (this === window) {
+ try {
+ return '__uv' in val ? val : this;
+ } catch (e) {
+ return this;
+ };
+ };
+ return val;
+ },
+ set(val) {
+ this.parent = val;
+ },
+ });
+
+ client.nativeMethods.defineProperty(window.Object.prototype, __uv.methods.top, {
+ configurable: true,
+ get() {
+ const val = this.top;
+
+ if (this === window) {
+ if (val === this.parent) return this[__uv.methods.parent];
+ try {
+ if (!('__uv' in val)) {
+ let current = this;
+
+ while (current.parent !== val) {
+ current = current.parent
+ };
+
+ return '__uv' in current ? current : this;
+
+ } else {
+ return val;
+ };
+ } catch (e) {
+ return this;
+ };
+ };
+ return val;
+ },
+ set(val) {
+ this.top = val;
+ },
+ });
+
+
+ client.nativeMethods.defineProperty(window.Object.prototype, __uv.methods.eval, {
+ configurable: true,
+ get() {
+ return this === window ? __uv.eval : this.eval;
+ },
+ set(val) {
+ this.eval = val;
+ },
+ });
+};
\ No newline at end of file
diff --git a/src/uv.sw.js b/src/uv.sw.js
new file mode 100644
index 0000000..0759104
--- /dev/null
+++ b/src/uv.sw.js
@@ -0,0 +1,789 @@
+importScripts('./uv.bundle.js');
+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',
+ ],
+ };
+ 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;
+ };
+};
+
+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';
+};
+
+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);
+ }
+}