class CustomTypedEvent extends Event { get target() { return super.target as T | null; } } type CustomEventListener = (evt: T) => void; interface CustomeEventListenerObject { handleEvent: CustomEventListener; } type CustomEventListenerOrEventListenerObject = CustomEventListener | CustomeEventListenerObject; class DataEvent extends CustomTypedEvent { constructor(public readonly data: any) { super("data"); } } class Channel extends EventTarget { constructor( public readonly portNumber: number, public readonly name: string, private readonly port: NetscriptPort ) { super(); } private cancel: Promise | undefined = undefined; private resolve: ((reason?: any) => void) | undefined = undefined; start() { if (this.cancel !== undefined) return; this.cancel = new Promise( (resolve, _) => this.resolve = resolve); this.poll(); } stop() { if (this.resolve === undefined) return; const resolve = this.resolve; this.cancel = undefined; this.resolve = undefined; resolve(); } private poll() { if (this.cancel === undefined) return; let data; while ((data = this.port.read()) !== "NULL PORT DATA") this.dispatchEvent(new DataEvent(data)); Promise.race([this.port.nextWrite(), this.cancel]) .then(() => this.poll()); } addEventListener( type: "data", callback: CustomEventListenerOrEventListenerObject | null, options?: Parameters[2] ): void; addEventListener( type: Parameters[0], callback: Parameters[1], options?: Parameters[2] ) { super.addEventListener(type, callback, options); this.start(); } removeEventListener( type: "data", callback: CustomEventListenerOrEventListenerObject | null, options?: Parameters[2] ): void; removeEventListener( type: Parameters[0], callback: Parameters[1], options?: Parameters[2] ) { super.removeEventListener(type, callback, options); } peek() { const data = this.port.peek(); if (data === "NULL PORT DATA") return undefined; return data as T; } read() { const data = this.port.read(); if (data === "NULL PORT DATA") return undefined; return data as T; } write(value: any) { return this.port.tryWrite(value); } } const channelsByPid = new Map>(); export function getChannel(ns: NS, name: string) { const channels = setupPidContext(ns); const controlChannel = channels.get(undefined)!; let channel = channels.get(name); if (channel !== undefined) return channel; let allChannels = _getChannels(controlChannel); let portNum = allChannels.get(name); if (portNum === undefined) { portNum = allChannels.size + 2; allChannels.set(name, portNum); controlChannel.write(allChannels); if (allChannels.size > 1) controlChannel.read(); } channel = new Channel(portNum, name, ns.getPortHandle(portNum)); channels.set(name, channel); return channel; } function setupPidContext(ns: NS) { const pid = ns.pid; let channels = channelsByPid.get(pid); if (channels === undefined) { channels = new Map(); channelsByPid.set(pid, channels); } if (channels.has(undefined)) return channels; const controlChannel = new Channel(1, "__CONTROL__", ns.getPortHandle(1)); channels.set(undefined, controlChannel); ns.atExit( () => cleanup(pid), "lib/channels.ts"); return channels; } function cleanup(pid: number) { const channels = channelsByPid.get(pid); if (channels === undefined) return; channelsByPid.delete(pid); for (const channel of channels.values()) channel.stop(); } export function getChannels(ns: NS) { return _getChannels(setupPidContext(ns).get(undefined)!); } function _getChannels(controlChannel: Channel) { return controlChannel .peek>() ?? new Map(); } ====== import { getChannel } from "lib/channels.ts"; export async function main(ns: NS) { const channel = getChannel(ns, "testtest"); ns.tprint(`Got channel ${channel.name} on port #${channel.portNumber}`); channel.addEventListener("data", e => { ns.tprint(e.data); }); await new Promise(() => {}); } ======= import { getChannel, getChannels } from "lib/channels.ts"; export async function main(ns: NS) { if (ns.args.length === 0) { getChannels(ns).forEach((port, name) => ns.tprint(`${name} => ${port}`)); return; } const channel = getChannel(ns, "testtest"); ns.tprint(`Got channel ${channel.name} on port #${channel.portNumber}`); channel.write(ns.args.join(" ")); }