Skip to content

Commit d923550

Browse files
committed
feat: Add a global WebSocket polyfill
1 parent 3104ab5 commit d923550

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed

deno/WebSocketPolyfill.ts

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import {
2+
connectWebSocket,
3+
WebSocket as WebSocketInterface,
4+
WebSocketCloseEvent,
5+
isWebSocketCloseEvent,
6+
isWebSocketPingEvent,
7+
isWebSocketPongEvent,
8+
} from 'https://deno.land/std/ws/mod.ts';
9+
import { OnMessageListener, MessageEvent } from './MessageTarget.ts';
10+
11+
enum ReadyState {
12+
CONNECTING = 0,
13+
OPEN = 1,
14+
CLOSING = 2,
15+
CLOSED = 3,
16+
}
17+
18+
class WebSocketImpl extends EventTarget {
19+
private _connectionPromise: Promise<WebSocketInterface>;
20+
private _readyState = ReadyState.CONNECTING;
21+
private _url: string;
22+
private _socket: WebSocketInterface | null;
23+
24+
constructor(url: string) {
25+
super();
26+
this.onclose = null;
27+
this.onmessage = null;
28+
this.onerror = null;
29+
this.onopen = null;
30+
this._socket = null;
31+
this._url = url;
32+
this._connectionPromise = connectWebSocket(url);
33+
this._connectionPromise.catch((err) => {
34+
this._sendOnError(new ErrorEvent(err));
35+
});
36+
37+
this._connectionPromise.then(async (socket) => {
38+
this._socket = socket;
39+
this._sendOnOpen(new Event('open'));
40+
for await (const msg of socket) {
41+
if (isWebSocketCloseEvent(msg)) {
42+
this._sendOnClose(new CloseEvent(msg.code, msg.reason));
43+
} else {
44+
this._sendOnMessage(
45+
new MessageEvent('message', {
46+
data: msg,
47+
})
48+
);
49+
}
50+
}
51+
52+
if (!socket.isClosed) {
53+
await socket.close(1000);
54+
this._sendOnClose(new CloseEvent(1000));
55+
}
56+
});
57+
}
58+
59+
get url() {
60+
return this._url;
61+
}
62+
63+
get binaryType() {
64+
return 'arraybuffer';
65+
}
66+
67+
get bufferedAmount() {
68+
return 0;
69+
}
70+
71+
get extensions() {
72+
return '';
73+
}
74+
75+
get readyState() {
76+
return this._readyState;
77+
}
78+
79+
close(code?: number, reason?: string) {
80+
if (
81+
this._readyState !== ReadyState.CLOSING &&
82+
this._readyState !== ReadyState.CLOSED
83+
) {
84+
this._readyState = ReadyState.CLOSING;
85+
this._connectionPromise.then((socket) => {
86+
if (typeof code === 'number' && typeof reason === 'string') {
87+
socket.close(code, reason);
88+
} else if (typeof code === 'number') {
89+
socket.close(code);
90+
} else {
91+
socket.close();
92+
}
93+
});
94+
}
95+
}
96+
97+
send(data: string | Uint8Array) {
98+
if (this._readyState !== ReadyState.OPEN) {
99+
throw new DOMException(
100+
'readyState must be OPEN.',
101+
'InvalidStateException'
102+
);
103+
}
104+
this._socket?.send(data);
105+
}
106+
107+
onclose: OnCloseHandler | null;
108+
onmessage: OnMessageListener | null;
109+
onerror: OnErrorHandler | null;
110+
onopen: OnOpenHandler | null;
111+
112+
private _sendOnClose(event: CloseEvent) {
113+
this._readyState = ReadyState.CLOSED;
114+
if (this.onclose) {
115+
this.onclose(event);
116+
}
117+
this.dispatchEvent(event);
118+
}
119+
120+
private _sendOnMessage(event: MessageEvent) {
121+
if (this.onmessage) {
122+
this.onmessage(event);
123+
}
124+
this.dispatchEvent(event);
125+
}
126+
127+
private _sendOnError(event: ErrorEvent) {
128+
this._readyState = ReadyState.CLOSED;
129+
if (this.onerror) {
130+
this.onerror(event);
131+
}
132+
this.dispatchEvent(event);
133+
}
134+
135+
private _sendOnOpen(event: Event) {
136+
this._readyState = ReadyState.OPEN;
137+
if (this.onopen) {
138+
this.onopen(event);
139+
}
140+
this.dispatchEvent(event);
141+
}
142+
}
143+
144+
interface OnCloseHandler {
145+
(event: CloseEvent): void;
146+
}
147+
148+
interface OnErrorHandler {
149+
(event: ErrorEvent): void;
150+
}
151+
152+
interface OnOpenHandler {
153+
(event: Event): void;
154+
}
155+
156+
class CloseEvent extends Event {
157+
code: number;
158+
reason: string | null;
159+
constructor(code: number, reason?: string) {
160+
super('close');
161+
this.code = code;
162+
this.reason = reason || null;
163+
}
164+
}
165+
166+
class ErrorEvent extends Event {
167+
error: any;
168+
constructor(error: any) {
169+
super('error');
170+
this.error = error;
171+
}
172+
}
173+
174+
if (typeof (<any>globalThis).WebSocket === 'undefined') {
175+
console.log('[WebSocketPolyfill] Polyfilling global WebSocket');
176+
(<any>globalThis).WebSocket = WebSocketImpl;
177+
}

deno/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import './WebSocketPolyfill.ts';
12
import { connectWebSocket, WebSocket } from 'https://deno.land/std/ws/mod.ts';
23
import {
34
serializeStructure,

0 commit comments

Comments
 (0)