@ccheever/exact-ibex-runtime 0.1.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.
- package/package.json +63 -0
- package/src/abort/AbortController.ts +23 -0
- package/src/abort/AbortSignal.ts +152 -0
- package/src/abort/index.ts +2 -0
- package/src/accessibility.ts +12 -0
- package/src/arraybuffer-detach.ts +109 -0
- package/src/base64/base64.ts +168 -0
- package/src/base64/index.ts +1 -0
- package/src/blob/Blob.ts +259 -0
- package/src/blob/File.ts +59 -0
- package/src/blob/FormData.ts +323 -0
- package/src/blob/index.ts +3 -0
- package/src/bootstrap.ts +1946 -0
- package/src/broadcast/BroadcastChannel.ts +280 -0
- package/src/broadcast/index.ts +5 -0
- package/src/cache/Cache.ts +349 -0
- package/src/cache/CacheStorage.ts +89 -0
- package/src/cache/index.ts +27 -0
- package/src/camera/index.ts +6202 -0
- package/src/camera/processor.worker.ts +194 -0
- package/src/camera/scene.ts +195 -0
- package/src/clipboard/Clipboard.ts +129 -0
- package/src/clipboard/ClipboardItem.ts +97 -0
- package/src/clipboard/index.ts +6 -0
- package/src/clone/index.ts +1 -0
- package/src/clone/structuredClone.ts +389 -0
- package/src/clone/transferableSymbols.ts +2 -0
- package/src/compression/CompressionStream.ts +146 -0
- package/src/compression/DecompressionStream.ts +342 -0
- package/src/compression/index.ts +4 -0
- package/src/console/Console.ts +341 -0
- package/src/console/index.ts +2 -0
- package/src/core/accessibility-state.ts +263 -0
- package/src/core/accessibility.ts +184 -0
- package/src/core/agent-state.ts +37 -0
- package/src/core/diagnostics-logs.ts +144 -0
- package/src/core/host-call-bridge.ts +16 -0
- package/src/core/i18n-helpers.ts +189 -0
- package/src/core/locale-state.ts +253 -0
- package/src/core/locale.ts +95 -0
- package/src/crypto/Crypto.ts +2743 -0
- package/src/crypto/index.ts +1 -0
- package/src/diagnostics/logs.ts +7 -0
- package/src/encoding/TextDecoder.ts +1181 -0
- package/src/encoding/TextDecoderStream.ts +58 -0
- package/src/encoding/TextEncoder.ts +180 -0
- package/src/encoding/TextEncoderStream.ts +39 -0
- package/src/encoding/index.ts +8 -0
- package/src/events/CloseEvent.ts +91 -0
- package/src/events/DOMException.ts +409 -0
- package/src/events/ErrorEvent.ts +39 -0
- package/src/events/Event.ts +151 -0
- package/src/events/EventTarget.ts +280 -0
- package/src/events/FocusEvent.ts +27 -0
- package/src/events/KeyboardEvent.ts +46 -0
- package/src/events/MessageEvent.ts +61 -0
- package/src/events/ProgressEvent.ts +33 -0
- package/src/events/PromiseRejectionEvent.ts +31 -0
- package/src/events/index.ts +52 -0
- package/src/eventsource/EventSource.ts +371 -0
- package/src/eventsource/index.ts +2 -0
- package/src/fetch/Headers.ts +642 -0
- package/src/fetch/Request.ts +760 -0
- package/src/fetch/Response.ts +543 -0
- package/src/fetch/body.ts +1256 -0
- package/src/fetch/cookie-jar.ts +566 -0
- package/src/fetch/demo.ts +207 -0
- package/src/fetch/errors.ts +101 -0
- package/src/fetch/fetch.ts +2610 -0
- package/src/fetch/index.ts +101 -0
- package/src/fetch/native-bridge.ts +65 -0
- package/src/fetch/types.ts +258 -0
- package/src/filereader/FileReader.ts +236 -0
- package/src/filereader/index.ts +1 -0
- package/src/fs/Dirent.ts +39 -0
- package/src/fs/ExactFile.ts +450 -0
- package/src/fs/Stats.ts +80 -0
- package/src/fs/index.ts +944 -0
- package/src/fs/promises.ts +386 -0
- package/src/fs/shared.ts +328 -0
- package/src/http-server/index.js +697 -0
- package/src/http-server/index.ts +27 -0
- package/src/identity.generated.ts +14 -0
- package/src/index.ts +283 -0
- package/src/indexeddb/IDBCursor.ts +188 -0
- package/src/indexeddb/IDBDatabase.ts +343 -0
- package/src/indexeddb/IDBFactory.ts +269 -0
- package/src/indexeddb/IDBIndex.ts +194 -0
- package/src/indexeddb/IDBKeyRange.ts +109 -0
- package/src/indexeddb/IDBObjectStore.ts +468 -0
- package/src/indexeddb/IDBRequest.ts +163 -0
- package/src/indexeddb/IDBTransaction.ts +207 -0
- package/src/indexeddb/index.ts +34 -0
- package/src/indexeddb/utils.ts +52 -0
- package/src/inspect/index.ts +1 -0
- package/src/inspect/inspect.ts +465 -0
- package/src/internal/detect.ts +104 -0
- package/src/locale.ts +10 -0
- package/src/location/index.ts +1059 -0
- package/src/locks/LockManager.ts +460 -0
- package/src/locks/index.ts +12 -0
- package/src/media/VideoFrame.ts +58 -0
- package/src/messaging/MessageChannel.ts +31 -0
- package/src/messaging/MessagePort.ts +180 -0
- package/src/messaging/index.ts +2 -0
- package/src/messaging.ts +247 -0
- package/src/native/NativeModules.ts +354 -0
- package/src/native/index.ts +1 -0
- package/src/navigator/Navigator.ts +351 -0
- package/src/navigator/index.ts +1 -0
- package/src/node/Buffer.ts +1786 -0
- package/src/node/index.ts +4 -0
- package/src/node/path.ts +495 -0
- package/src/node/process.ts +2528 -0
- package/src/performance/Performance.ts +532 -0
- package/src/performance/index.ts +21 -0
- package/src/polyfills/array.ts +236 -0
- package/src/polyfills/arraybuffer.ts +172 -0
- package/src/polyfills/groupby.ts +85 -0
- package/src/polyfills/index.ts +85 -0
- package/src/polyfills/intl.ts +1956 -0
- package/src/polyfills/iterator.ts +479 -0
- package/src/polyfills/promise.ts +37 -0
- package/src/polyfills/set.ts +245 -0
- package/src/polyfills/string.ts +85 -0
- package/src/polyfills/typedarray.ts +110 -0
- package/src/promise-rejection-tracking.ts +464 -0
- package/src/react-native/index.ts +388 -0
- package/src/runtime-entry.ts +55 -0
- package/src/scheduling/AnimationFrame.ts +105 -0
- package/src/scheduling/IdleCallback.ts +167 -0
- package/src/scheduling/index.ts +13 -0
- package/src/security/Capabilities.ts +1146 -0
- package/src/security/Permissions.ts +392 -0
- package/src/security/capability-bits.generated.ts +63 -0
- package/src/security/index.ts +16 -0
- package/src/sqlite/Database.ts +456 -0
- package/src/sqlite/Statement.ts +206 -0
- package/src/sqlite/constants.ts +79 -0
- package/src/sqlite/errors.ts +25 -0
- package/src/sqlite/index.ts +34 -0
- package/src/sqlite/module.js +438 -0
- package/src/storage/Storage.ts +291 -0
- package/src/storage/StorageManager.ts +91 -0
- package/src/storage/index.ts +3 -0
- package/src/stream-compat.ts +47 -0
- package/src/streams/ReadableStream.ts +4131 -0
- package/src/streams/TransformStream.ts +375 -0
- package/src/streams/WritableStream.ts +866 -0
- package/src/streams/index.ts +41 -0
- package/src/timers/Timers.ts +296 -0
- package/src/timers/index.ts +11 -0
- package/src/url/URL.ts +656 -0
- package/src/url/URLPattern.ts +850 -0
- package/src/url/URLSearchParams.ts +244 -0
- package/src/url/index.ts +9 -0
- package/src/websocket/WebSocket.ts +770 -0
- package/src/websocket/WebSocketError.ts +52 -0
- package/src/websocket/WebSocketStream.ts +628 -0
- package/src/websocket/index.ts +7 -0
- package/src/window/index.ts +872 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { DOMException } from '../events/DOMException';
|
|
2
|
+
|
|
3
|
+
export interface WebSocketErrorInit {
|
|
4
|
+
closeCode?: number | null;
|
|
5
|
+
reason?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function isValidCloseCode(code: number): boolean {
|
|
9
|
+
return code === 1000 || code === 1005 || code === 1006 || (code >= 3000 && code <= 4999);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function createRealmDomException(message: string, name: string): DOMException {
|
|
13
|
+
const DOMExceptionCtor = ((globalThis as any).DOMException || DOMException) as typeof DOMException;
|
|
14
|
+
return new DOMExceptionCtor(message, name);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class WebSocketError extends DOMException {
|
|
18
|
+
readonly closeCode: number | null;
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
|
|
21
|
+
constructor(message: string = '', init: WebSocketErrorInit = {}) {
|
|
22
|
+
const reason = typeof init.reason === 'string' ? init.reason : '';
|
|
23
|
+
let closeCode = init.closeCode ?? null;
|
|
24
|
+
|
|
25
|
+
if (reason !== '' && closeCode === null) {
|
|
26
|
+
closeCode = 1000;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (closeCode !== null) {
|
|
30
|
+
if (typeof closeCode !== 'number' || !Number.isInteger(closeCode) || !isValidCloseCode(closeCode)) {
|
|
31
|
+
throw createRealmDomException('Invalid close code', 'InvalidAccessError');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (reason !== '') {
|
|
36
|
+
const encoded = new TextEncoder().encode(reason);
|
|
37
|
+
if (encoded.byteLength > 123) {
|
|
38
|
+
throw createRealmDomException('Close reason is too long', 'SyntaxError');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
super(message, 'WebSocketError');
|
|
43
|
+
this.closeCode = closeCode;
|
|
44
|
+
this.reason = reason;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get [Symbol.toStringTag](): string {
|
|
48
|
+
return 'WebSocketError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default WebSocketError;
|
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { DOMException } from '../events/DOMException';
|
|
3
|
+
import { ReadableStream, ReadableStreamDefaultController, WritableStream } from '../streams';
|
|
4
|
+
import { WebSocket } from './WebSocket';
|
|
5
|
+
import { WebSocketError } from './WebSocketError';
|
|
6
|
+
|
|
7
|
+
export interface WebSocketStreamOptions {
|
|
8
|
+
protocols?: string[];
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface WebSocketCloseInfo {
|
|
13
|
+
closeCode?: number;
|
|
14
|
+
reason?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface OpenedInfo {
|
|
18
|
+
readable: ReadableStream<string | Uint8Array>;
|
|
19
|
+
writable: WritableStream<string | ArrayBuffer | ArrayBufferView | Blob>;
|
|
20
|
+
protocol: string;
|
|
21
|
+
extensions: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ClosedInfo {
|
|
25
|
+
closeCode: number;
|
|
26
|
+
reason: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// NSURLSessionWebSocketTask does not expose a reliable send-drain signal for
|
|
30
|
+
// large messages, so WebSocketStream applies a conservative completion budget
|
|
31
|
+
// for oversized writes to preserve backpressure semantics.
|
|
32
|
+
const STREAM_WRITE_IMMEDIATE_WINDOW_BYTES = 256 * 1024;
|
|
33
|
+
const STREAM_WRITE_DRAIN_BYTES_PER_MS = 4096;
|
|
34
|
+
|
|
35
|
+
function createDeferred<T>(): {
|
|
36
|
+
promise: Promise<T>;
|
|
37
|
+
resolve: (value: T | PromiseLike<T>) => void;
|
|
38
|
+
reject: (reason?: unknown) => void;
|
|
39
|
+
} {
|
|
40
|
+
let resolve!: (value: T | PromiseLike<T>) => void;
|
|
41
|
+
let reject!: (reason?: unknown) => void;
|
|
42
|
+
const promise = new Promise<T>((res, rej) => {
|
|
43
|
+
resolve = res;
|
|
44
|
+
reject = rej;
|
|
45
|
+
});
|
|
46
|
+
return { promise, resolve, reject };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function createRealmDomException(message: string, name: string): DOMException {
|
|
50
|
+
const DOMExceptionCtor = ((globalThis as any).DOMException || DOMException) as typeof DOMException;
|
|
51
|
+
return new DOMExceptionCtor(message, name);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function createAbortError(): DOMException {
|
|
55
|
+
return createRealmDomException('The operation was aborted.', 'AbortError');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function createInvalidStateError(): DOMException {
|
|
59
|
+
return createRealmDomException('The stream is no longer writable.', 'InvalidStateError');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isObjectLike(value: unknown): value is Record<string, unknown> {
|
|
63
|
+
return (typeof value === 'object' && value !== null) || typeof value === 'function';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isSharedArrayBufferInstance(buffer: unknown): boolean {
|
|
67
|
+
return typeof SharedArrayBuffer === 'function' && buffer instanceof SharedArrayBuffer;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isResizableArrayBuffer(buffer: unknown): boolean {
|
|
71
|
+
return !!buffer &&
|
|
72
|
+
typeof buffer === 'object' &&
|
|
73
|
+
typeof (buffer as any).byteLength === 'number' &&
|
|
74
|
+
(buffer as any).resizable === true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeCloseArguments(value: unknown): { code?: number; reason?: string } {
|
|
78
|
+
if (value === undefined) {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
if (!isObjectLike(value)) {
|
|
82
|
+
throw new TypeError('close() requires an options object');
|
|
83
|
+
}
|
|
84
|
+
const raw = value as WebSocketCloseInfo;
|
|
85
|
+
const reason = raw.reason === undefined ? undefined : String(raw.reason);
|
|
86
|
+
let code = raw.closeCode;
|
|
87
|
+
if (reason !== undefined && reason !== '' && code === undefined) {
|
|
88
|
+
code = 1000;
|
|
89
|
+
}
|
|
90
|
+
if (code !== undefined) {
|
|
91
|
+
if (typeof code !== 'number' || !Number.isInteger(code) || (code !== 1000 && (code < 3000 || code > 4999))) {
|
|
92
|
+
throw createRealmDomException('Invalid close code', 'InvalidAccessError');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (reason !== undefined) {
|
|
96
|
+
const encoded = new TextEncoder().encode(reason);
|
|
97
|
+
if (encoded.byteLength > 123) {
|
|
98
|
+
throw createRealmDomException('Close reason is too long', 'SyntaxError');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (code === undefined && reason === '') {
|
|
102
|
+
return {};
|
|
103
|
+
}
|
|
104
|
+
return { code, reason };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getAbortCloseArguments(reason: unknown): { code?: number; reason?: string } {
|
|
108
|
+
if (reason instanceof WebSocketError) {
|
|
109
|
+
const closeCode = reason.closeCode === null ? undefined : reason.closeCode;
|
|
110
|
+
return {
|
|
111
|
+
code: closeCode === undefined ? undefined : closeCode,
|
|
112
|
+
reason: reason.reason || undefined,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function closeSocket(socket: WebSocket, code?: number, reason?: string): void {
|
|
119
|
+
if (code === undefined) {
|
|
120
|
+
socket.close();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (reason === undefined) {
|
|
124
|
+
socket.close(code);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
socket.close(code, reason);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function normalizeClosedInfo(code: number, reason: string): ClosedInfo {
|
|
131
|
+
return {
|
|
132
|
+
closeCode: code || 1005,
|
|
133
|
+
reason: reason || '',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function createWebSocketCloseError(message: string, code: number, reason: string): WebSocketError {
|
|
138
|
+
const closeCode = code === 1005 ? null : code;
|
|
139
|
+
return new WebSocketError(message, {
|
|
140
|
+
closeCode: closeCode === undefined ? null : closeCode,
|
|
141
|
+
reason,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getMonotonicNow(): number {
|
|
146
|
+
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
|
|
147
|
+
return performance.now();
|
|
148
|
+
}
|
|
149
|
+
return Date.now();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function estimateWriteResolveDelay(byteLength: number): number {
|
|
153
|
+
if (byteLength <= STREAM_WRITE_IMMEDIATE_WINDOW_BYTES) {
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
return Math.ceil(
|
|
157
|
+
(byteLength - STREAM_WRITE_IMMEDIATE_WINDOW_BYTES) / STREAM_WRITE_DRAIN_BYTES_PER_MS
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function normalizeWritableChunk(
|
|
162
|
+
chunk: unknown
|
|
163
|
+
): { sendValue: string | ArrayBuffer | ArrayBufferView | Blob; byteLength: number } {
|
|
164
|
+
if (typeof chunk === 'string') {
|
|
165
|
+
return {
|
|
166
|
+
sendValue: chunk,
|
|
167
|
+
byteLength: new TextEncoder().encode(chunk).byteLength,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (chunk instanceof ArrayBuffer) {
|
|
172
|
+
if (isResizableArrayBuffer(chunk)) {
|
|
173
|
+
throw new TypeError('Resizable ArrayBuffer is not supported');
|
|
174
|
+
}
|
|
175
|
+
return { sendValue: chunk, byteLength: chunk.byteLength };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (ArrayBuffer.isView(chunk)) {
|
|
179
|
+
if (isSharedArrayBufferInstance(chunk.buffer)) {
|
|
180
|
+
throw new TypeError('SharedArrayBuffer-backed views are not supported');
|
|
181
|
+
}
|
|
182
|
+
if (isResizableArrayBuffer(chunk.buffer)) {
|
|
183
|
+
throw new TypeError('Resizable ArrayBuffer is not supported');
|
|
184
|
+
}
|
|
185
|
+
return { sendValue: chunk, byteLength: chunk.byteLength };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (chunk && typeof chunk === 'object' && typeof (chunk as Blob).arrayBuffer === 'function' && typeof (chunk as Blob).size === 'number') {
|
|
189
|
+
return { sendValue: chunk as Blob, byteLength: (chunk as Blob).size };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const stringified = String(chunk);
|
|
193
|
+
return {
|
|
194
|
+
sendValue: stringified,
|
|
195
|
+
byteLength: new TextEncoder().encode(stringified).byteLength,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export class WebSocketStream {
|
|
200
|
+
readonly url: string;
|
|
201
|
+
readonly opened: Promise<OpenedInfo>;
|
|
202
|
+
readonly closed: Promise<ClosedInfo>;
|
|
203
|
+
|
|
204
|
+
_socket: WebSocket | null = null;
|
|
205
|
+
_readableController: ReadableStreamDefaultController<string | Uint8Array> | null = null;
|
|
206
|
+
_readable: ReadableStream<string | Uint8Array> | null = null;
|
|
207
|
+
_writable: WritableStream<string | ArrayBuffer | ArrayBufferView | Blob> | null = null;
|
|
208
|
+
_openedDeferred = createDeferred<OpenedInfo>();
|
|
209
|
+
_closedDeferred = createDeferred<ClosedInfo>();
|
|
210
|
+
_openedSettled = false;
|
|
211
|
+
_closedSettled = false;
|
|
212
|
+
_connected = false;
|
|
213
|
+
_localCloseInitiated = false;
|
|
214
|
+
_closedDuringHandshake = false;
|
|
215
|
+
_ignoredTerminal = false;
|
|
216
|
+
_pendingWriteRequests: Array<{
|
|
217
|
+
remaining: number;
|
|
218
|
+
readyAt: number;
|
|
219
|
+
resolve: () => void;
|
|
220
|
+
reject: (reason: unknown) => void;
|
|
221
|
+
}> = [];
|
|
222
|
+
_writableInvalidStateError: DOMException | null = null;
|
|
223
|
+
_pendingWriteResolveTimer: number | null = null;
|
|
224
|
+
|
|
225
|
+
constructor(url: string | URL, options?: WebSocketStreamOptions) {
|
|
226
|
+
const urlString = url instanceof URL ? url.href : String(url);
|
|
227
|
+
this.url = urlString;
|
|
228
|
+
this.opened = this._openedDeferred.promise;
|
|
229
|
+
this.closed = this._closedDeferred.promise;
|
|
230
|
+
|
|
231
|
+
if (arguments.length === 0) {
|
|
232
|
+
throw new TypeError('WebSocketStream constructor requires a URL');
|
|
233
|
+
}
|
|
234
|
+
if (options !== undefined && !isObjectLike(options)) {
|
|
235
|
+
throw new TypeError('WebSocketStream options must be an object');
|
|
236
|
+
}
|
|
237
|
+
if (options?.protocols !== undefined && !Array.isArray(options.protocols)) {
|
|
238
|
+
throw new TypeError('WebSocketStream protocols option must be an array');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const signal = options?.signal;
|
|
242
|
+
if (signal?.aborted) {
|
|
243
|
+
const abortError = createAbortError();
|
|
244
|
+
this._rejectOpened(abortError);
|
|
245
|
+
this._rejectClosed(abortError);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this._readable = new ReadableStream<string | Uint8Array>({
|
|
250
|
+
start: (controller) => {
|
|
251
|
+
this._readableController = controller;
|
|
252
|
+
},
|
|
253
|
+
pull: () => {
|
|
254
|
+
this._syncReadableBackpressure();
|
|
255
|
+
},
|
|
256
|
+
cancel: async (reason?: unknown) => {
|
|
257
|
+
const closeArgs = getAbortCloseArguments(reason);
|
|
258
|
+
await this._initiateClose(closeArgs.code, closeArgs.reason);
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
this._writable = new WritableStream<string | ArrayBuffer | ArrayBufferView | Blob>({
|
|
263
|
+
write: async (chunk) => {
|
|
264
|
+
if (!this._socket || !this._connected) {
|
|
265
|
+
throw createInvalidStateError();
|
|
266
|
+
}
|
|
267
|
+
const normalized = normalizeWritableChunk(chunk);
|
|
268
|
+
return new Promise<void>((resolve, reject) => {
|
|
269
|
+
const resolveDelay = estimateWriteResolveDelay(normalized.byteLength);
|
|
270
|
+
this._pendingWriteRequests.push({
|
|
271
|
+
remaining: normalized.byteLength,
|
|
272
|
+
readyAt: getMonotonicNow() + resolveDelay,
|
|
273
|
+
resolve,
|
|
274
|
+
reject,
|
|
275
|
+
});
|
|
276
|
+
try {
|
|
277
|
+
this._socket!.send(normalized.sendValue as any);
|
|
278
|
+
if (normalized.byteLength === 0) {
|
|
279
|
+
const waiter = this._pendingWriteRequests.shift();
|
|
280
|
+
waiter?.resolve();
|
|
281
|
+
}
|
|
282
|
+
} catch (error) {
|
|
283
|
+
const waiter = this._pendingWriteRequests.pop();
|
|
284
|
+
waiter?.reject(error);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
},
|
|
288
|
+
close: async () => {
|
|
289
|
+
await this._initiateClose(undefined, undefined);
|
|
290
|
+
},
|
|
291
|
+
abort: async (reason?: unknown) => {
|
|
292
|
+
const closeArgs = getAbortCloseArguments(reason);
|
|
293
|
+
await this._initiateClose(closeArgs.code, closeArgs.reason);
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const socket = new WebSocket(urlString, options?.protocols);
|
|
298
|
+
socket.binaryType = 'arraybuffer';
|
|
299
|
+
socket._setIncomingFlowControl(true);
|
|
300
|
+
this._socket = socket;
|
|
301
|
+
|
|
302
|
+
const originalHandleBytesSent = socket._handleBytesSent.bind(socket);
|
|
303
|
+
socket._handleBytesSent = (bytesSent: number) => {
|
|
304
|
+
originalHandleBytesSent(bytesSent);
|
|
305
|
+
this._handleBytesSent(bytesSent);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
socket.addEventListener('open', () => {
|
|
309
|
+
this._connected = true;
|
|
310
|
+
if (signal) {
|
|
311
|
+
signal.removeEventListener('abort', abortListener);
|
|
312
|
+
}
|
|
313
|
+
this._resolveOpened({
|
|
314
|
+
readable: this._readable!,
|
|
315
|
+
writable: this._writable!,
|
|
316
|
+
protocol: socket.protocol,
|
|
317
|
+
extensions: socket.extensions,
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
socket.addEventListener('message', (event: any) => {
|
|
322
|
+
if (!this._readableController) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (typeof event.data === 'string') {
|
|
326
|
+
this._readableController.enqueue(event.data);
|
|
327
|
+
} else if (event.data instanceof ArrayBuffer) {
|
|
328
|
+
this._readableController.enqueue(new Uint8Array(event.data));
|
|
329
|
+
} else if (ArrayBuffer.isView(event.data)) {
|
|
330
|
+
this._readableController.enqueue(new Uint8Array(event.data.buffer, event.data.byteOffset, event.data.byteLength));
|
|
331
|
+
}
|
|
332
|
+
this._syncReadableBackpressure();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
socket.addEventListener('error', () => {
|
|
336
|
+
// Close carries the structured outcome we care about.
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
socket.addEventListener('close', (event: any) => {
|
|
340
|
+
if (signal) {
|
|
341
|
+
signal.removeEventListener('abort', abortListener);
|
|
342
|
+
}
|
|
343
|
+
this._handleSocketClose(event.code, event.reason || '', !!event.wasClean);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const abortListener = () => {
|
|
347
|
+
if (this._connected) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const abortError = createAbortError();
|
|
351
|
+
this._ignoredTerminal = true;
|
|
352
|
+
this._rejectOpened(abortError);
|
|
353
|
+
this._rejectClosed(abortError);
|
|
354
|
+
try {
|
|
355
|
+
socket.close();
|
|
356
|
+
} catch (_abortCloseErr) {}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
if (signal) {
|
|
360
|
+
signal.addEventListener('abort', abortListener, { once: true });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
close(info?: WebSocketCloseInfo): void {
|
|
365
|
+
const normalized = normalizeCloseArguments(info);
|
|
366
|
+
void this._initiateClose(normalized.code, normalized.reason);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
_handleBytesSent(bytesSent: number): void {
|
|
370
|
+
let remainingBytes = bytesSent;
|
|
371
|
+
for (const request of this._pendingWriteRequests) {
|
|
372
|
+
if (remainingBytes <= 0) {
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
if (request.remaining <= 0) {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (remainingBytes >= request.remaining) {
|
|
379
|
+
remainingBytes -= request.remaining;
|
|
380
|
+
request.remaining = 0;
|
|
381
|
+
} else {
|
|
382
|
+
request.remaining -= remainingBytes;
|
|
383
|
+
remainingBytes = 0;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
this._drainResolvedWrites();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
_syncReadableBackpressure(): void {
|
|
390
|
+
if (!this._socket || !this._connected || !this._readableController) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const desiredSize = this._readableController.desiredSize;
|
|
395
|
+
if (desiredSize !== null && desiredSize <= 0) {
|
|
396
|
+
this._socket._pauseIncoming();
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
this._socket._resumeIncoming();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async _initiateClose(code?: number, reason?: string): Promise<void> {
|
|
404
|
+
if (this._closedSettled) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (!this._socket) {
|
|
409
|
+
if (!this._closedSettled) {
|
|
410
|
+
const abortError = createAbortError();
|
|
411
|
+
this._rejectOpened(abortError);
|
|
412
|
+
this._rejectClosed(abortError);
|
|
413
|
+
}
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (!this._connected && !this._openedSettled) {
|
|
418
|
+
this._closedDuringHandshake = true;
|
|
419
|
+
this._localCloseInitiated = true;
|
|
420
|
+
const error = createWebSocketCloseError('WebSocketStream closed during handshake', 1005, '');
|
|
421
|
+
this._ignoredTerminal = true;
|
|
422
|
+
this._rejectOpened(error);
|
|
423
|
+
this._rejectClosed(error);
|
|
424
|
+
try {
|
|
425
|
+
closeSocket(this._socket, code, reason);
|
|
426
|
+
} catch (_closeDuringHandshakeErr) {}
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (this._localCloseInitiated) {
|
|
431
|
+
return this.closed.then(() => undefined);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
this._localCloseInitiated = true;
|
|
435
|
+
closeSocket(this._socket, code, reason);
|
|
436
|
+
return this.closed.then(() => undefined);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
_handleSocketClose(code: number, reason: string, wasClean: boolean): void {
|
|
440
|
+
if (this._ignoredTerminal) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
this._connected = false;
|
|
445
|
+
|
|
446
|
+
const closeInfo = normalizeClosedInfo(code, reason);
|
|
447
|
+
const closeError = createWebSocketCloseError(reason || 'WebSocket closed', closeInfo.closeCode, closeInfo.reason);
|
|
448
|
+
const hadPendingWrites = this._pendingWriteRequests.length > 0;
|
|
449
|
+
|
|
450
|
+
if (!this._openedSettled) {
|
|
451
|
+
this._rejectOpened(closeError);
|
|
452
|
+
this._rejectClosed(closeError);
|
|
453
|
+
this._rejectPendingWrites(createInvalidStateError());
|
|
454
|
+
this._errorReadable(closeError);
|
|
455
|
+
this._errorWritable(closeError);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (!wasClean || closeInfo.closeCode === 1006) {
|
|
460
|
+
this._rejectClosed(closeError);
|
|
461
|
+
this._errorReadable(closeError);
|
|
462
|
+
if (hadPendingWrites) {
|
|
463
|
+
const invalidStateError = this._getWritableInvalidStateError();
|
|
464
|
+
this._rejectPendingWrites(invalidStateError);
|
|
465
|
+
this._errorWritable(invalidStateError);
|
|
466
|
+
} else {
|
|
467
|
+
this._errorWritable(closeError);
|
|
468
|
+
}
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (hadPendingWrites) {
|
|
473
|
+
const invalidStateError = this._getWritableInvalidStateError();
|
|
474
|
+
this._rejectClosed(closeError);
|
|
475
|
+
this._closeReadable();
|
|
476
|
+
this._rejectPendingWrites(invalidStateError);
|
|
477
|
+
this._errorWritable(invalidStateError);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
this._resolveClosed(closeInfo);
|
|
482
|
+
this._closeReadable();
|
|
483
|
+
if (this._localCloseInitiated) {
|
|
484
|
+
this._finishWritableClose();
|
|
485
|
+
} else {
|
|
486
|
+
const invalidStateError = this._getWritableInvalidStateError();
|
|
487
|
+
this._rejectPendingWrites(invalidStateError);
|
|
488
|
+
this._errorWritable(invalidStateError);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
_getWritableInvalidStateError(): DOMException {
|
|
493
|
+
if (!this._writableInvalidStateError) {
|
|
494
|
+
this._writableInvalidStateError = createInvalidStateError();
|
|
495
|
+
}
|
|
496
|
+
return this._writableInvalidStateError;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
_rejectPendingWrites(reason: unknown): void {
|
|
500
|
+
this._clearPendingWriteResolveTimer();
|
|
501
|
+
while (this._pendingWriteRequests.length > 0) {
|
|
502
|
+
const request = this._pendingWriteRequests.shift();
|
|
503
|
+
request?.reject(reason);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
_drainResolvedWrites(): void {
|
|
508
|
+
this._clearPendingWriteResolveTimer();
|
|
509
|
+
|
|
510
|
+
while (this._pendingWriteRequests.length > 0) {
|
|
511
|
+
const current = this._pendingWriteRequests[0];
|
|
512
|
+
if (current.remaining > 0) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const delay = current.readyAt - getMonotonicNow();
|
|
517
|
+
if (delay > 0) {
|
|
518
|
+
this._pendingWriteResolveTimer = setTimeout(() => {
|
|
519
|
+
this._pendingWriteResolveTimer = null;
|
|
520
|
+
this._drainResolvedWrites();
|
|
521
|
+
}, delay) as unknown as number;
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
this._pendingWriteRequests.shift();
|
|
526
|
+
current.resolve();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
_clearPendingWriteResolveTimer(): void {
|
|
531
|
+
if (this._pendingWriteResolveTimer === null) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
clearTimeout(this._pendingWriteResolveTimer);
|
|
535
|
+
this._pendingWriteResolveTimer = null;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
_resolveOpened(value: OpenedInfo): void {
|
|
539
|
+
if (this._openedSettled) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
this._openedSettled = true;
|
|
543
|
+
this._openedDeferred.resolve(value);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
_rejectOpened(reason: unknown): void {
|
|
547
|
+
if (this._openedSettled) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
this._openedSettled = true;
|
|
551
|
+
this._openedDeferred.reject(reason);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
_resolveClosed(value: ClosedInfo): void {
|
|
555
|
+
if (this._closedSettled) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
this._closedSettled = true;
|
|
559
|
+
this._closedDeferred.resolve(value);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
_rejectClosed(reason: unknown): void {
|
|
563
|
+
if (this._closedSettled) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
this._closedSettled = true;
|
|
567
|
+
this._closedDeferred.reject(reason);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
_closeReadable(): void {
|
|
571
|
+
if (this._readableController) {
|
|
572
|
+
try {
|
|
573
|
+
this._readableController.close();
|
|
574
|
+
return;
|
|
575
|
+
} catch (_closeReadableErr) {}
|
|
576
|
+
}
|
|
577
|
+
if (!this._readable) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
(this._readable as any)._closeStream();
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
_errorReadable(reason: unknown): void {
|
|
584
|
+
if (!this._readable) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
(this._readable as any)._errorStream(reason);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
_finishWritableClose(): void {
|
|
591
|
+
if (!this._writable) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const writable = this._writable as any;
|
|
595
|
+
if (writable._state === 'closed' || writable._state === 'errored') {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
writable._state = 'closed';
|
|
600
|
+
writable._queue = [];
|
|
601
|
+
writable._queueTotalSize = 0;
|
|
602
|
+
|
|
603
|
+
const closeRequest = writable._inFlightCloseRequest;
|
|
604
|
+
if (closeRequest) {
|
|
605
|
+
closeRequest.resolve();
|
|
606
|
+
writable._inFlightCloseRequest = undefined;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const writer = writable._writer;
|
|
610
|
+
if (writer) {
|
|
611
|
+
writer._readyResolve?.(undefined);
|
|
612
|
+
writer._closedResolve?.(undefined);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
_errorWritable(reason: unknown): void {
|
|
617
|
+
if (!this._writable) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
(this._writable as any)._errorStream(reason);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
get [Symbol.toStringTag](): string {
|
|
624
|
+
return 'WebSocketStream';
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export default WebSocketStream;
|