@scelar/nodepod 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/LICENSE +43 -0
  2. package/README.md +240 -0
  3. package/dist/child_process-BJOMsZje.js +8233 -0
  4. package/dist/child_process-BJOMsZje.js.map +1 -0
  5. package/dist/child_process-Cj8vOcuc.cjs +7434 -0
  6. package/dist/child_process-Cj8vOcuc.cjs.map +1 -0
  7. package/dist/index-Cb1Cgdnd.js +35308 -0
  8. package/dist/index-Cb1Cgdnd.js.map +1 -0
  9. package/dist/index-DsMGS-xc.cjs +37195 -0
  10. package/dist/index-DsMGS-xc.cjs.map +1 -0
  11. package/dist/index.cjs +65 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.mjs +59 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/package.json +95 -0
  16. package/src/__tests__/smoke.test.ts +11 -0
  17. package/src/constants/cdn-urls.ts +18 -0
  18. package/src/constants/config.ts +236 -0
  19. package/src/cross-origin.ts +26 -0
  20. package/src/engine-factory.ts +176 -0
  21. package/src/engine-types.ts +56 -0
  22. package/src/helpers/byte-encoding.ts +39 -0
  23. package/src/helpers/digest.ts +9 -0
  24. package/src/helpers/event-loop.ts +96 -0
  25. package/src/helpers/wasm-cache.ts +133 -0
  26. package/src/iframe-sandbox.ts +141 -0
  27. package/src/index.ts +192 -0
  28. package/src/isolation-helpers.ts +148 -0
  29. package/src/memory-volume.ts +941 -0
  30. package/src/module-transformer.ts +368 -0
  31. package/src/packages/archive-extractor.ts +248 -0
  32. package/src/packages/browser-bundler.ts +284 -0
  33. package/src/packages/installer.ts +396 -0
  34. package/src/packages/registry-client.ts +131 -0
  35. package/src/packages/version-resolver.ts +411 -0
  36. package/src/polyfills/assert.ts +384 -0
  37. package/src/polyfills/async_hooks.ts +144 -0
  38. package/src/polyfills/buffer.ts +628 -0
  39. package/src/polyfills/child_process.ts +2288 -0
  40. package/src/polyfills/chokidar.ts +336 -0
  41. package/src/polyfills/cluster.ts +106 -0
  42. package/src/polyfills/console.ts +136 -0
  43. package/src/polyfills/constants.ts +123 -0
  44. package/src/polyfills/crypto.ts +885 -0
  45. package/src/polyfills/dgram.ts +87 -0
  46. package/src/polyfills/diagnostics_channel.ts +76 -0
  47. package/src/polyfills/dns.ts +134 -0
  48. package/src/polyfills/domain.ts +68 -0
  49. package/src/polyfills/esbuild.ts +854 -0
  50. package/src/polyfills/events.ts +276 -0
  51. package/src/polyfills/fs.ts +2888 -0
  52. package/src/polyfills/fsevents.ts +79 -0
  53. package/src/polyfills/http.ts +1449 -0
  54. package/src/polyfills/http2.ts +199 -0
  55. package/src/polyfills/https.ts +76 -0
  56. package/src/polyfills/inspector.ts +62 -0
  57. package/src/polyfills/lightningcss.ts +105 -0
  58. package/src/polyfills/module.ts +191 -0
  59. package/src/polyfills/net.ts +353 -0
  60. package/src/polyfills/os.ts +238 -0
  61. package/src/polyfills/path.ts +206 -0
  62. package/src/polyfills/perf_hooks.ts +102 -0
  63. package/src/polyfills/process.ts +690 -0
  64. package/src/polyfills/punycode.ts +159 -0
  65. package/src/polyfills/querystring.ts +93 -0
  66. package/src/polyfills/quic.ts +118 -0
  67. package/src/polyfills/readdirp.ts +229 -0
  68. package/src/polyfills/readline.ts +692 -0
  69. package/src/polyfills/repl.ts +134 -0
  70. package/src/polyfills/rollup.ts +119 -0
  71. package/src/polyfills/sea.ts +33 -0
  72. package/src/polyfills/sqlite.ts +78 -0
  73. package/src/polyfills/stream.ts +1620 -0
  74. package/src/polyfills/string_decoder.ts +25 -0
  75. package/src/polyfills/tailwindcss-oxide.ts +309 -0
  76. package/src/polyfills/test.ts +197 -0
  77. package/src/polyfills/timers.ts +32 -0
  78. package/src/polyfills/tls.ts +105 -0
  79. package/src/polyfills/trace_events.ts +50 -0
  80. package/src/polyfills/tty.ts +71 -0
  81. package/src/polyfills/url.ts +174 -0
  82. package/src/polyfills/util.ts +559 -0
  83. package/src/polyfills/v8.ts +126 -0
  84. package/src/polyfills/vm.ts +132 -0
  85. package/src/polyfills/volume-registry.ts +15 -0
  86. package/src/polyfills/wasi.ts +44 -0
  87. package/src/polyfills/worker_threads.ts +326 -0
  88. package/src/polyfills/ws.ts +595 -0
  89. package/src/polyfills/zlib.ts +881 -0
  90. package/src/request-proxy.ts +716 -0
  91. package/src/script-engine.ts +3375 -0
  92. package/src/sdk/nodepod-fs.ts +93 -0
  93. package/src/sdk/nodepod-process.ts +86 -0
  94. package/src/sdk/nodepod-terminal.ts +350 -0
  95. package/src/sdk/nodepod.ts +509 -0
  96. package/src/sdk/types.ts +70 -0
  97. package/src/shell/commands/bun.ts +121 -0
  98. package/src/shell/commands/directory.ts +297 -0
  99. package/src/shell/commands/file-ops.ts +525 -0
  100. package/src/shell/commands/git.ts +2142 -0
  101. package/src/shell/commands/node.ts +80 -0
  102. package/src/shell/commands/npm.ts +198 -0
  103. package/src/shell/commands/pm-types.ts +45 -0
  104. package/src/shell/commands/pnpm.ts +82 -0
  105. package/src/shell/commands/search.ts +264 -0
  106. package/src/shell/commands/shell-env.ts +352 -0
  107. package/src/shell/commands/text-processing.ts +1152 -0
  108. package/src/shell/commands/yarn.ts +84 -0
  109. package/src/shell/shell-builtins.ts +19 -0
  110. package/src/shell/shell-helpers.ts +250 -0
  111. package/src/shell/shell-interpreter.ts +514 -0
  112. package/src/shell/shell-parser.ts +429 -0
  113. package/src/shell/shell-types.ts +85 -0
  114. package/src/syntax-transforms.ts +561 -0
  115. package/src/threading/engine-worker.ts +64 -0
  116. package/src/threading/inline-worker.ts +372 -0
  117. package/src/threading/offload-types.ts +112 -0
  118. package/src/threading/offload-worker.ts +383 -0
  119. package/src/threading/offload.ts +271 -0
  120. package/src/threading/process-context.ts +92 -0
  121. package/src/threading/process-handle.ts +275 -0
  122. package/src/threading/process-manager.ts +956 -0
  123. package/src/threading/process-worker-entry.ts +854 -0
  124. package/src/threading/shared-vfs.ts +352 -0
  125. package/src/threading/sync-channel.ts +135 -0
  126. package/src/threading/task-queue.ts +177 -0
  127. package/src/threading/vfs-bridge.ts +231 -0
  128. package/src/threading/worker-pool.ts +233 -0
  129. package/src/threading/worker-protocol.ts +358 -0
  130. package/src/threading/worker-vfs.ts +218 -0
  131. package/src/types/externals.d.ts +38 -0
  132. package/src/types/fs-streams.ts +142 -0
  133. package/src/types/manifest.ts +17 -0
  134. package/src/worker-sandbox.ts +90 -0
@@ -0,0 +1,595 @@
1
+ // ws-compatible WebSocket polyfill wrapping browser native WebSocket
2
+
3
+
4
+ import { encodeFrame, decodeFrame } from "./http";
5
+ import { Buffer } from "./buffer";
6
+ import { createHash } from "./crypto";
7
+ import type { TcpSocket } from "./net";
8
+
9
+ // polyfill for environments missing CloseEvent / MessageEvent
10
+ const SafeCloseEvent: typeof CloseEvent =
11
+ typeof CloseEvent !== 'undefined'
12
+ ? CloseEvent
13
+ : (class SyntheticClose extends Event {
14
+ code: number;
15
+ reason: string;
16
+ wasClean: boolean;
17
+ constructor(kind: string, init?: { code?: number; reason?: string; wasClean?: boolean }) {
18
+ super(kind);
19
+ this.code = init?.code ?? 1000;
20
+ this.reason = init?.reason ?? '';
21
+ this.wasClean = init?.wasClean ?? true;
22
+ }
23
+ } as unknown as typeof CloseEvent);
24
+
25
+ const SafeMessageEvent: typeof MessageEvent =
26
+ typeof MessageEvent !== 'undefined'
27
+ ? MessageEvent
28
+ : (class SyntheticMessage extends Event {
29
+ data: unknown;
30
+ constructor(kind: string, init?: { data?: unknown }) {
31
+ super(kind);
32
+ this.data = init?.data;
33
+ }
34
+ } as unknown as typeof MessageEvent);
35
+
36
+ // in-process server <-> client messaging via BroadcastChannel
37
+ let internalChannel: BroadcastChannel | null = null;
38
+ try {
39
+ internalChannel = new BroadcastChannel('nodepod-ws-bridge');
40
+ } catch { /* not available */ }
41
+
42
+ const activeServers = new Map<string, WebSocketServer>();
43
+ let nextClientId = 0;
44
+
45
+ type Handler = (...args: unknown[]) => void;
46
+
47
+ interface TinyEmitter {
48
+ _listeners: Map<string, Set<Handler>>;
49
+ on(evt: string, fn: Handler): this;
50
+ off(evt: string, fn: Handler): this;
51
+ emit(evt: string, ...args: unknown[]): void;
52
+ }
53
+
54
+ interface TinyEmitterConstructor {
55
+ new (): TinyEmitter;
56
+ (this: any): void;
57
+ prototype: any;
58
+ }
59
+
60
+ const TinyEmitter = function TinyEmitter(this: any) {
61
+ if (!this) return;
62
+ this._listeners = new Map<string, Set<Handler>>();
63
+ } as unknown as TinyEmitterConstructor;
64
+
65
+ TinyEmitter.prototype.on = function on(this: any, evt: string, fn: Handler): any {
66
+ if (!this._listeners.has(evt)) this._listeners.set(evt, new Set());
67
+ this._listeners.get(evt)!.add(fn);
68
+ return this;
69
+ };
70
+
71
+ TinyEmitter.prototype.off = function off(this: any, evt: string, fn: Handler): any {
72
+ this._listeners.get(evt)?.delete(fn);
73
+ return this;
74
+ };
75
+
76
+ TinyEmitter.prototype.emit = function emit(this: any, evt: string, ...args: unknown[]): void {
77
+ const s = this._listeners.get(evt);
78
+ if (!s) return;
79
+ for (const fn of s) {
80
+ try { fn(...args); } catch { /* swallow handler errors */ }
81
+ }
82
+ };
83
+
84
+ export const CONNECTING = 0;
85
+ export const OPEN = 1;
86
+ export const CLOSING = 2;
87
+ export const CLOSED = 3;
88
+
89
+ export interface WebSocket extends TinyEmitter {
90
+ readonly CONNECTING: number;
91
+ readonly OPEN: number;
92
+ readonly CLOSING: number;
93
+ readonly CLOSED: number;
94
+ readyState: number;
95
+ url: string;
96
+ protocol: string;
97
+ extensions: string;
98
+ bufferedAmount: number;
99
+ binaryType: 'blob' | 'arraybuffer';
100
+ _uid: string;
101
+ _boundServer: WebSocketServer | null;
102
+ _native: globalThis.WebSocket | null;
103
+ _tcpSocket: TcpSocket | null;
104
+ _tcpInboundBuf: Uint8Array;
105
+ onopen: ((ev: Event) => void) | null;
106
+ onclose: ((ev: CloseEvent) => void) | null;
107
+ onerror: ((ev: Event) => void) | null;
108
+ onmessage: ((ev: MessageEvent) => void) | null;
109
+ _open(): void;
110
+ _openNative(): void;
111
+ send(payload: string | ArrayBuffer | Uint8Array): void;
112
+ close(code?: number, reason?: string): void;
113
+ ping(): void;
114
+ pong(): void;
115
+ terminate(): void;
116
+ _bindServer(srv: WebSocketServer): void;
117
+ _deliverMessage(data: unknown): void;
118
+ }
119
+
120
+ interface WebSocketConstructor {
121
+ new (address: string, protocols?: string | string[]): WebSocket;
122
+ (this: any, address: string, protocols?: string | string[]): void;
123
+ prototype: any;
124
+ readonly CONNECTING: number;
125
+ readonly OPEN: number;
126
+ readonly CLOSING: number;
127
+ readonly CLOSED: number;
128
+ }
129
+
130
+ export const WebSocket = function WebSocket(this: any, address: string, protocols?: string | string[]) {
131
+ if (!this) return;
132
+ TinyEmitter.call(this);
133
+ this.CONNECTING = CONNECTING;
134
+ this.OPEN = OPEN;
135
+ this.CLOSING = CLOSING;
136
+ this.CLOSED = CLOSED;
137
+ this.readyState = CONNECTING;
138
+ this.url = address;
139
+ this.protocol = '';
140
+ this.extensions = '';
141
+ this.bufferedAmount = 0;
142
+ this.binaryType = 'blob';
143
+ this._uid = `ws-${++nextClientId}`;
144
+ this._boundServer = null;
145
+ this._native = null;
146
+ this._tcpSocket = null;
147
+ this._tcpInboundBuf = new Uint8Array(0);
148
+ this.onopen = null;
149
+ this.onclose = null;
150
+ this.onerror = null;
151
+ this.onmessage = null;
152
+ if (protocols) this.protocol = Array.isArray(protocols) ? protocols[0] : protocols;
153
+ const self = this;
154
+ setTimeout(() => self._open(), 0);
155
+ } as unknown as WebSocketConstructor;
156
+
157
+ Object.setPrototypeOf(WebSocket.prototype, TinyEmitter.prototype);
158
+
159
+ (WebSocket as any).CONNECTING = CONNECTING;
160
+ (WebSocket as any).OPEN = OPEN;
161
+ (WebSocket as any).CLOSING = CLOSING;
162
+ (WebSocket as any).CLOSED = CLOSED;
163
+
164
+ WebSocket.prototype._open = function _open(this: any): void {
165
+ // Internal loopback connection (server-side socket)
166
+ if (this.url.startsWith('internal://')) {
167
+ this.readyState = OPEN;
168
+ this.emit('open');
169
+ this.onopen?.(new Event('open'));
170
+ return;
171
+ }
172
+
173
+ // Real remote connection -- delegate to browser's native WebSocket
174
+ if (this.url.startsWith('ws://') || this.url.startsWith('wss://')) {
175
+ this._openNative();
176
+ return;
177
+ }
178
+
179
+ // BroadcastChannel-based in-process connection
180
+ if (!internalChannel) {
181
+ const self = this;
182
+ setTimeout(() => {
183
+ self.readyState = OPEN;
184
+ self.emit('open');
185
+ self.onopen?.(new Event('open'));
186
+ }, 0);
187
+ return;
188
+ }
189
+
190
+ internalChannel.postMessage({ kind: 'connect', uid: this._uid, url: this.url });
191
+
192
+ const chan = internalChannel;
193
+ const self = this;
194
+ const onMsg = (ev: MessageEvent) => {
195
+ const d = ev.data;
196
+ if (d.targetUid !== self._uid) return;
197
+
198
+ if (d.kind === 'connected') {
199
+ self.readyState = OPEN;
200
+ self.emit('open');
201
+ self.onopen?.(new Event('open'));
202
+ } else if (d.kind === 'payload') {
203
+ const me = new SafeMessageEvent('message', { data: d.body });
204
+ self.emit('message', me);
205
+ self.onmessage?.(me);
206
+ } else if (d.kind === 'closed') {
207
+ self.readyState = CLOSED;
208
+ const ce = new SafeCloseEvent('close', { code: d.code || 1000, reason: d.reason || '', wasClean: true });
209
+ self.emit('close', ce);
210
+ self.onclose?.(ce);
211
+ chan.removeEventListener('message', onMsg);
212
+ } else if (d.kind === 'fault') {
213
+ const ee = new Event('error');
214
+ self.emit('error', ee);
215
+ self.onerror?.(ee);
216
+ }
217
+ };
218
+ chan.addEventListener('message', onMsg);
219
+
220
+ // If nobody responds within 100 ms, consider the socket "open" anyway
221
+ setTimeout(() => {
222
+ if (self.readyState === CONNECTING) {
223
+ self.readyState = OPEN;
224
+ self.emit('open');
225
+ self.onopen?.(new Event('open'));
226
+ }
227
+ }, 100);
228
+ };
229
+
230
+ WebSocket.prototype._openNative = function _openNative(this: any): void {
231
+ const inBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
232
+ const NativeImpl = inBrowser && typeof globalThis.WebSocket === 'function' && globalThis.WebSocket !== (WebSocket as unknown)
233
+ ? globalThis.WebSocket
234
+ : null;
235
+
236
+ if (!NativeImpl) {
237
+ const self = this;
238
+ setTimeout(() => {
239
+ self.readyState = OPEN;
240
+ self.emit('open');
241
+ self.onopen?.(new Event('open'));
242
+ }, 0);
243
+ return;
244
+ }
245
+
246
+ try {
247
+ this._native = new NativeImpl(this.url);
248
+ this._native.binaryType = this.binaryType === 'arraybuffer' ? 'arraybuffer' : 'blob';
249
+ } catch {
250
+ this.readyState = CLOSED;
251
+ const errEvt = new Event('error');
252
+ this.emit('error', errEvt);
253
+ this.onerror?.(errEvt);
254
+ return;
255
+ }
256
+
257
+ const self = this;
258
+ this._native.onopen = () => {
259
+ self.readyState = OPEN;
260
+ self.emit('open');
261
+ self.onopen?.(new Event('open'));
262
+ };
263
+
264
+ this._native.onmessage = (raw: globalThis.MessageEvent) => {
265
+ const me = new SafeMessageEvent('message', { data: raw.data });
266
+ self.emit('message', me);
267
+ self.onmessage?.(me);
268
+ };
269
+
270
+ this._native.onclose = (raw: globalThis.CloseEvent) => {
271
+ self.readyState = CLOSED;
272
+ self._native = null;
273
+ const ce = new SafeCloseEvent('close', { code: raw.code, reason: raw.reason, wasClean: raw.wasClean });
274
+ self.emit('close', ce);
275
+ self.onclose?.(ce);
276
+ };
277
+
278
+ this._native.onerror = () => {
279
+ const errEvt = new Event('error');
280
+ self.emit('error', errEvt);
281
+ self.onerror?.(errEvt);
282
+ };
283
+ };
284
+
285
+ WebSocket.prototype.send = function send(this: any, payload: string | ArrayBuffer | Uint8Array): void {
286
+ if (this.readyState !== OPEN) throw new Error('WebSocket is not open');
287
+
288
+ if (this._native) { this._native.send(payload); return; }
289
+
290
+ // TcpSocket-backed (from handleUpgrade) — write real WS frames
291
+ if (this._tcpSocket) {
292
+ let data: Uint8Array;
293
+ let op: number;
294
+ if (typeof payload === 'string') {
295
+ data = new TextEncoder().encode(payload);
296
+ op = 0x01; // text frame
297
+ } else if (payload instanceof ArrayBuffer) {
298
+ data = new Uint8Array(payload);
299
+ op = 0x02; // binary frame
300
+ } else {
301
+ data = payload;
302
+ op = 0x02; // binary frame
303
+ }
304
+ // Server frames are NOT masked
305
+ const frame = encodeFrame(op, data, false);
306
+ this._tcpSocket.write(Buffer.from(frame));
307
+ return;
308
+ }
309
+
310
+ if (this._boundServer) { this._boundServer._injectClientPayload(this, payload); return; }
311
+
312
+ if (internalChannel) {
313
+ internalChannel.postMessage({ kind: 'payload', uid: this._uid, url: this.url, body: payload });
314
+ }
315
+ };
316
+
317
+ WebSocket.prototype.close = function close(this: any, code?: number, reason?: string): void {
318
+ if (this.readyState === CLOSED || this.readyState === CLOSING) return;
319
+ this.readyState = CLOSING;
320
+
321
+ if (this._native) { this._native.close(code, reason); return; }
322
+
323
+ // TcpSocket-backed — send close frame
324
+ if (this._tcpSocket) {
325
+ const c = code ?? 1000;
326
+ const closeBuf = new Uint8Array(2);
327
+ closeBuf[0] = (c >> 8) & 0xff;
328
+ closeBuf[1] = c & 0xff;
329
+ const frame = encodeFrame(0x08, closeBuf, false);
330
+ try { this._tcpSocket.write(Buffer.from(frame)); } catch { /* socket may be dead */ }
331
+ const self = this;
332
+ setTimeout(() => {
333
+ self.readyState = CLOSED;
334
+ const ce = new SafeCloseEvent('close', { code: c, reason: reason || '', wasClean: true });
335
+ self.emit('close', ce);
336
+ self.onclose?.(ce);
337
+ self._tcpSocket = null;
338
+ }, 0);
339
+ return;
340
+ }
341
+
342
+ if (internalChannel) {
343
+ internalChannel.postMessage({ kind: 'disconnect', uid: this._uid, url: this.url, code, reason });
344
+ }
345
+
346
+ const self = this;
347
+ setTimeout(() => {
348
+ self.readyState = CLOSED;
349
+ const ce = new SafeCloseEvent('close', { code: code || 1000, reason: reason || '', wasClean: true });
350
+ self.emit('close', ce);
351
+ self.onclose?.(ce);
352
+ }, 0);
353
+ };
354
+
355
+ WebSocket.prototype.ping = function ping(): void { /* no-op in browser */ };
356
+ WebSocket.prototype.pong = function pong(): void { /* no-op in browser */ };
357
+
358
+ WebSocket.prototype.terminate = function terminate(this: any): void {
359
+ if (this._native) { this._native.close(); this._native = null; }
360
+ if (this._tcpSocket) { try { this._tcpSocket.destroy(); } catch { /* */ } this._tcpSocket = null; }
361
+ this.readyState = CLOSED;
362
+ const ce = new SafeCloseEvent('close', { code: 1006, reason: 'Terminated', wasClean: false });
363
+ this.emit('close', ce);
364
+ this.onclose?.(ce);
365
+ };
366
+
367
+ WebSocket.prototype._bindServer = function _bindServer(this: any, srv: WebSocketServer): void { this._boundServer = srv; };
368
+
369
+ WebSocket.prototype._deliverMessage = function _deliverMessage(this: any, data: unknown): void {
370
+ const me = new SafeMessageEvent('message', { data });
371
+ this.emit('message', me);
372
+ this.onmessage?.(me);
373
+ };
374
+
375
+ // Sec-WebSocket-Accept key computation (RFC 6455)
376
+ const WS_GUID = '258EAFA5-E914-47DA-95CA-5AB5DC76CB76';
377
+
378
+ function _computeAcceptKey(wsKey: string): string {
379
+ try {
380
+ const hash = createHash('sha1');
381
+ hash.update(wsKey + WS_GUID);
382
+ return hash.digest('base64') as string;
383
+ } catch {
384
+ // bridge only checks for "HTTP/1.1 101", so a placeholder works
385
+ return btoa(wsKey + WS_GUID).slice(0, 28);
386
+ }
387
+ }
388
+
389
+ export interface ServerConfig {
390
+ host?: string;
391
+ port?: number;
392
+ server?: unknown;
393
+ noServer?: boolean;
394
+ path?: string;
395
+ clientTracking?: boolean;
396
+ perMessageDeflate?: boolean | object;
397
+ maxPayload?: number;
398
+ }
399
+
400
+ export interface WebSocketServer extends TinyEmitter {
401
+ clients: Set<WebSocket>;
402
+ options: ServerConfig;
403
+ _route: string;
404
+ _channelCb: ((ev: MessageEvent) => void) | null;
405
+ _listen(): void;
406
+ _injectClientPayload(source: WebSocket, data: unknown): void;
407
+ handleUpgrade(req: unknown, socket: unknown, head: unknown, done: (ws: WebSocket, req: unknown) => void): void;
408
+ close(done?: () => void): void;
409
+ address(): { port: number; family: string; address: string } | null;
410
+ }
411
+
412
+ interface WebSocketServerConstructor {
413
+ new (opts?: ServerConfig): WebSocketServer;
414
+ (this: any, opts?: ServerConfig): void;
415
+ prototype: any;
416
+ }
417
+
418
+ export const WebSocketServer = function WebSocketServer(this: any, opts: ServerConfig = {}) {
419
+ if (!this) return;
420
+ TinyEmitter.call(this);
421
+ this.clients = new Set<WebSocket>();
422
+ this.options = opts;
423
+ this._route = opts.path || '/';
424
+ this._channelCb = null;
425
+
426
+ if (!opts.noServer) this._listen();
427
+ activeServers.set(this._route, this);
428
+ } as unknown as WebSocketServerConstructor;
429
+
430
+ Object.setPrototypeOf(WebSocketServer.prototype, TinyEmitter.prototype);
431
+
432
+ WebSocketServer.prototype._listen = function _listen(this: any): void {
433
+ if (!internalChannel) return;
434
+ const chan = internalChannel;
435
+ const self = this;
436
+
437
+ this._channelCb = (ev: MessageEvent) => {
438
+ const d = ev.data;
439
+
440
+ if (d.kind === 'connect') {
441
+ const sock = new WebSocket('internal://' + self._route);
442
+ sock._bindServer(self);
443
+ (sock as unknown as { _uid: string })._uid = d.uid;
444
+ self.clients.add(sock);
445
+ chan.postMessage({ kind: 'connected', targetUid: d.uid });
446
+ self.emit('connection', sock, { url: d.url });
447
+ }
448
+
449
+ if (d.kind === 'payload') {
450
+ for (const c of self.clients) {
451
+ if ((c as unknown as { _uid: string })._uid === d.uid) {
452
+ c._deliverMessage(d.body);
453
+ break;
454
+ }
455
+ }
456
+ }
457
+
458
+ if (d.kind === 'disconnect') {
459
+ for (const c of self.clients) {
460
+ if ((c as unknown as { _uid: string })._uid === d.uid) {
461
+ c.close(d.code, d.reason);
462
+ self.clients.delete(c);
463
+ break;
464
+ }
465
+ }
466
+ }
467
+ };
468
+ chan.addEventListener('message', this._channelCb);
469
+ };
470
+
471
+ WebSocketServer.prototype._injectClientPayload = function _injectClientPayload(source: WebSocket, data: unknown): void {
472
+ const me = new SafeMessageEvent('message', { data });
473
+ source.emit('message', me);
474
+ };
475
+
476
+ WebSocketServer.prototype.handleUpgrade = function handleUpgrade(
477
+ this: any,
478
+ req: unknown,
479
+ socket: unknown,
480
+ head: unknown,
481
+ done: (ws: WebSocket, req: unknown) => void,
482
+ ): void {
483
+ const sock = new WebSocket('internal://' + this._route);
484
+ sock._bindServer(this);
485
+ if (this.options.clientTracking !== false) this.clients.add(sock);
486
+
487
+ // Check if socket is a real TcpSocket (from http.Server.dispatchUpgrade)
488
+ const tcp = socket as TcpSocket | null;
489
+ const isTcp = tcp && typeof tcp.write === 'function' && typeof tcp._feedData === 'function';
490
+ if (isTcp) {
491
+ // Wire the ws.WebSocket to the TcpSocket for frame-level I/O
492
+ sock._tcpSocket = tcp;
493
+
494
+ // Compute Sec-WebSocket-Accept
495
+ const reqHeaders = (req as { headers?: Record<string, string> })?.headers || {};
496
+ const wsKey = reqHeaders['sec-websocket-key'] || '';
497
+ const acceptKey = _computeAcceptKey(wsKey);
498
+
499
+ // Write HTTP 101 Switching Protocols response to the TcpSocket
500
+ // This triggers handshakeDone in the request-proxy bridge
501
+ const handshake = `HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${acceptKey}\r\n\r\n`;
502
+ tcp.write(Buffer.from(handshake));
503
+
504
+ // Listen for incoming data from the TcpSocket (client→server frames)
505
+ tcp.on('data', (chunk: unknown) => {
506
+ const raw = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk as ArrayBuffer);
507
+ const merged = new Uint8Array(sock._tcpInboundBuf.length + raw.length);
508
+ merged.set(sock._tcpInboundBuf, 0);
509
+ merged.set(raw, sock._tcpInboundBuf.length);
510
+ sock._tcpInboundBuf = merged;
511
+
512
+ while (sock._tcpInboundBuf.length >= 2) {
513
+ const frame = decodeFrame(sock._tcpInboundBuf);
514
+ if (!frame) break;
515
+ sock._tcpInboundBuf = sock._tcpInboundBuf.slice(frame.consumed);
516
+
517
+ switch (frame.op) {
518
+ case 0x01: { // text
519
+ const text = new TextDecoder().decode(frame.data);
520
+ const me = new SafeMessageEvent('message', { data: text });
521
+ sock.emit('message', me);
522
+ sock.onmessage?.(me);
523
+ break;
524
+ }
525
+ case 0x02: { // binary
526
+ const me = new SafeMessageEvent('message', { data: frame.data.buffer });
527
+ sock.emit('message', me);
528
+ sock.onmessage?.(me);
529
+ break;
530
+ }
531
+ case 0x08: { // close
532
+ const code = frame.data.length >= 2
533
+ ? (frame.data[0] << 8) | frame.data[1]
534
+ : 1000;
535
+ sock.readyState = CLOSED;
536
+ const ce = new SafeCloseEvent('close', { code, reason: '', wasClean: true });
537
+ sock.emit('close', ce);
538
+ sock.onclose?.(ce);
539
+ sock._tcpSocket = null;
540
+ break;
541
+ }
542
+ case 0x09: { // ping — respond with pong
543
+ const pong = encodeFrame(0x0a, frame.data, false);
544
+ tcp.write(Buffer.from(pong));
545
+ break;
546
+ }
547
+ case 0x0a: { // pong
548
+ sock.emit('pong', frame.data);
549
+ break;
550
+ }
551
+ }
552
+ }
553
+ });
554
+
555
+ tcp.on('close', () => {
556
+ if (sock.readyState !== CLOSED) {
557
+ sock.readyState = CLOSED;
558
+ const ce = new SafeCloseEvent('close', { code: 1006, reason: 'Connection lost', wasClean: false });
559
+ sock.emit('close', ce);
560
+ sock.onclose?.(ce);
561
+ }
562
+ sock._tcpSocket = null;
563
+ });
564
+ }
565
+
566
+ const self = this;
567
+ setTimeout(() => {
568
+ done(sock, req);
569
+ self.emit('connection', sock, req);
570
+ }, 0);
571
+ };
572
+
573
+ WebSocketServer.prototype.close = function close(this: any, done?: () => void): void {
574
+ for (const c of this.clients) c.close(1001, 'Server closing');
575
+ this.clients.clear();
576
+ activeServers.delete(this._route);
577
+ if (this._channelCb && internalChannel) {
578
+ internalChannel.removeEventListener('message', this._channelCb);
579
+ this._channelCb = null;
580
+ }
581
+ this.emit('close');
582
+ if (done) setTimeout(done, 0);
583
+ };
584
+
585
+ WebSocketServer.prototype.address = function address(this: any): { port: number; family: string; address: string } | null {
586
+ return { port: this.options.port || 0, family: 'IPv4', address: this.options.host || '0.0.0.0' };
587
+ };
588
+
589
+ export const Server = WebSocketServer;
590
+
591
+ export const createWebSocketStream = (): never => {
592
+ throw new Error('createWebSocketStream is not available in the browser');
593
+ };
594
+
595
+ export default WebSocket;