UNPKG

event-source-hook

Version:

Easily intercept, modify, and simulate EventSource server-sent events.

234 lines (233 loc) 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var NativeEventSource = EventSource; /** * Create a mutable message event from an immutable message event with the same property values. * @param messageEvent - Immutable native `MessageEvent`. * @returns `messageEvent` but mutable. */ function ToMutableMessageEvent(messageEvent) { var self = {}; self.bubbles = messageEvent.bubbles || false; self.cancelable = messageEvent.cancelable || false; self.cancelBubble = messageEvent.cancelBubble || false; self.composed = messageEvent.composed || false; self.currentTarget = messageEvent.currentTarget || null; self.data = messageEvent.data || null; self.defaultPrevented = messageEvent.defaultPrevented || false; self.eventPhase = messageEvent.eventPhase || 0; self.isTrusted = messageEvent.isTrusted || false; self.lastEventId = messageEvent.lastEventId || ""; self.origin = messageEvent.origin || ""; self.ports = messageEvent.ports || []; self.returnValue = messageEvent.returnValue || true; self.simulated = messageEvent.simulated || false; self.source = messageEvent.source || null; self.srcElement = messageEvent.srcElement || null; self.target = messageEvent.target || null; self.timeStamp = messageEvent.timeStamp || performance.now(); self.type = messageEvent.type || "message"; Object.setPrototypeOf(self, Object.getPrototypeOf(messageEvent)); return self; } /** * Create a spoofed `EventSource` object for hook. Swapped with the native `EventSource`. * @constructor */ function HookedEventSource(url, eventSourceInitDict) { var _a, _b; // Call the URL hook function to eventually get a new URL. url = ((_a = ESHook.urlHook) === null || _a === void 0 ? void 0 : _a.call(ESHook, String(url))) || url; var es = new NativeEventSource(url, eventSourceInitDict); es._mapListenerProxy = new WeakMap(); es._nativeAddEventListener = es.addEventListener; es._nativeRemoveEventListener = es.removeEventListener; /* ----------------------- Spoof `addEventListener()` ----------------------- */ es._createEventProxy = function (listener) { return function (event) { var mutableEvent = ToMutableMessageEvent(event); // Set `isTrusted` to `true` like an genuine event. if (mutableEvent.simulated) mutableEvent.isTrusted = true; // If no hook function, directly call listener. if (!ESHook.eventHook) return listener(mutableEvent); var callback = function (mutableEvent) { if (mutableEvent === null) return; // If the event is null, then block the event. listener(mutableEvent); }; if (ESHook.isEventHookAsync) { ESHook.eventHook(mutableEvent.type, mutableEvent, es, callback); } else { callback(ESHook.eventHook(mutableEvent.type, mutableEvent, es)); } }; }; // @ts-ignore es.addEventListener = function (type, listener, options) { var _a; // Ignore these types that are not event message. if (type === "open" || type === "error") { return this._nativeAddEventListener(type, listener, options); } // Throw error if wrong listener type like official impl. if (!["function", "object"].includes(typeof listener)) { throw new TypeError("Failed to execute 'addEventListener' on 'EventTarget': parameter 2 is not of type 'Object'."); } // Get event handler function if listener is an object. if (typeof listener === "object") { listener = listener.handleEvent; if (typeof listener !== "function") return; } // Proxy function to be called instead of listener. var proxy = this._createEventProxy(listener); this._nativeAddEventListener(type, proxy, options); // Store (listener -> proxy) to be able to remove the listener later. var proxies = (_a = this._mapListenerProxy.get(listener)) !== null && _a !== void 0 ? _a : {}; proxies[type] = proxy; this._mapListenerProxy.set(listener, proxies); }; /* ---------------------- Spoof `removeEventListener()` --------------------- */ // @ts-ignore es.removeEventListener = function (type, listener, options) { // Ignore these types that are not event message. if (type === "open" || type === "error") { return this._nativeRemoveEventListener(type, listener, options); } var proxies = this._mapListenerProxy.get(listener); var proxy = proxies === null || proxies === void 0 ? void 0 : proxies[type]; if (proxy) { this._nativeRemoveEventListener(type, proxy, options); delete proxies[type]; } }; /* ---------------------------- Spoof `onmessage` --------------------------- */ Object.defineProperty(es, "onmessage", { get: function () { return this._onmessage; }, set: function (listener) { if (typeof listener === "function") { this.addEventListener("message", listener); } else { listener = null; } this.removeEventListener("message", this._onmessage); this._onmessage = listener; }, }); es.onmessage = null; /* -------------------------------------------------------------------------- */ // Call the create hook function before returning the instance. (_b = ESHook.createHook) === null || _b === void 0 ? void 0 : _b.call(ESHook, es); return es; } /** Library `event-source-hook` static class that provides `EventSource` hooking utilities. */ var ESHook = /** @class */ (function () { function ESHook() { } Object.defineProperty(ESHook, "urlHook", { /* ------------------------------- Properties ------------------------------- */ /** Hook function invoked just before a connection is established. */ get: function () { return this._urlHook; }, /** Hook function invoked just before a connection is established. */ set: function (func) { this._urlHook = typeof func === "function" ? func : null; }, enumerable: false, configurable: true }); Object.defineProperty(ESHook, "createHook", { /** Hook function invoked just before a connection is established. */ get: function () { return this._createHook; }, /** Hook function invoked just before a connection is established. */ set: function (func) { this._createHook = typeof func === "function" ? func : null; }, enumerable: false, configurable: true }); Object.defineProperty(ESHook, "eventHook", { /** Hook function invoked just before an event is received on any connection. */ get: function () { return this._eventHook; }, /** Hook function invoked just before an event is received on any connection. */ set: function (func) { this._eventHook = typeof func === "function" ? func : null; }, enumerable: false, configurable: true }); Object.defineProperty(ESHook, "isEventHookAsync", { /** Is the provided event hook function async? */ get: function () { return typeof this._eventHook === "function" && this._eventHook.length >= 4; }, enumerable: false, configurable: true }); Object.defineProperty(ESHook, "enabled", { /** Is the native `EventSource` spoofed by the library? */ get: function () { return EventSource.name === "HookedEventSource"; }, enumerable: false, configurable: true }); /* --------------------------------- Methods -------------------------------- */ /** * Swap the native `EventSource` constructor with a spoofed one. * Any opened connections will be spoofed by the library while enabled. */ ESHook.enable = function () { // @ts-ignore EventSource = HookedEventSource; }; /** * Swap the native `EventSource` constructor back in. * Any opened connections while disabled will not be spoofed by the library even after re-enabling. * @note Hook event function will still be called while disabled. */ ESHook.disable = function () { // @ts-ignore EventSource = NativeEventSource; }; /** * Reset all hooks function to none. */ ESHook.resetHooks = function () { this._urlHook = null; this._createHook = null; this._eventHook = null; }; /** * Simulate a received event. It will be handled as if it was an genuine event received from the server. * @note `simulated` and `isTrusted` properties are set to `true` on the `MessageEvent` object. * @param eventSource - Connection where the event should be received. * @param type - Event type. Use `message` if you want a non-typed event (default type). * @param options - Options to be passed to the event (such as data). */ ESHook.simulate = function (eventSource, type, options) { var _a; if (options === void 0) { options = {}; } options.origin = (_a = options.origin) !== null && _a !== void 0 ? _a : new URL(eventSource.url).origin; options.data = JSON.stringify(options.data); var event = new MessageEvent(type, options); event.simulated = true; eventSource.dispatchEvent(event); }; ESHook._urlHook = null; ESHook._createHook = null; ESHook._eventHook = null; return ESHook; }()); exports.default = ESHook;