@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,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DecompressionStream - Web Compression API
|
|
3
|
+
*
|
|
4
|
+
* Implements the DecompressionStream class that decompresses data using gzip, deflate, deflate-raw, or brotli.
|
|
5
|
+
* Uses the native __exactInflateSync bridge for actual decompression.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const g = globalThis as any;
|
|
9
|
+
const trailingDataErrorSymbol = Symbol.for("__exact.decompression.trailing-data-error");
|
|
10
|
+
|
|
11
|
+
function installTrailingDataUnhandledFilter(): void {
|
|
12
|
+
if (g.__exactHasDecompressionUnhandledFilter) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (typeof g.addEventListener !== "function") {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
g.__exactHasDecompressionUnhandledFilter = true;
|
|
21
|
+
g.addEventListener("unhandledrejection", (event: any) => {
|
|
22
|
+
if (event && event.reason && event.reason[trailingDataErrorSymbol]) {
|
|
23
|
+
if (typeof event.preventDefault === "function") {
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
}
|
|
26
|
+
if (typeof event.stopImmediatePropagation === "function") {
|
|
27
|
+
event.stopImmediatePropagation();
|
|
28
|
+
}
|
|
29
|
+
if (typeof event.stopPropagation === "function") {
|
|
30
|
+
event.stopPropagation();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
installTrailingDataUnhandledFilter();
|
|
37
|
+
// Retry via microtask in case addEventListener wasn't available at module load time.
|
|
38
|
+
// Use queueMicrotask (no timer ID, doesn't keep event loop alive) instead of setTimeout.
|
|
39
|
+
if (!g.__exactHasDecompressionUnhandledFilter) {
|
|
40
|
+
if (typeof g.queueMicrotask === "function") {
|
|
41
|
+
g.queueMicrotask(installTrailingDataUnhandledFilter);
|
|
42
|
+
} else if (typeof g.setTimeout === "function") {
|
|
43
|
+
const _h = g.setTimeout(installTrailingDataUnhandledFilter, 0);
|
|
44
|
+
if (_h && typeof _h === "object" && typeof _h.unref === "function") {
|
|
45
|
+
_h.unref();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type DecompressionFormat = 'gzip' | 'deflate' | 'deflate-raw' | 'brotli';
|
|
51
|
+
|
|
52
|
+
function toTypeError(error: unknown): TypeError {
|
|
53
|
+
if (error instanceof TypeError) {
|
|
54
|
+
return error;
|
|
55
|
+
}
|
|
56
|
+
if (error instanceof Error) {
|
|
57
|
+
let message = "TypeError";
|
|
58
|
+
try {
|
|
59
|
+
message = String(error.message);
|
|
60
|
+
} catch (_ignored) {
|
|
61
|
+
message = "TypeError";
|
|
62
|
+
}
|
|
63
|
+
return new TypeError(message);
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
return new TypeError(String(error));
|
|
67
|
+
} catch (_ignored) {
|
|
68
|
+
return new TypeError("TypeError");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isRecoverableIncompleteError(error: TypeError): boolean {
|
|
73
|
+
return error.message.toLowerCase().includes("incomplete data");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isTrailingDataError(error: TypeError): boolean {
|
|
77
|
+
return error.message.toLowerCase().includes("trailing data");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function markTrailingDataError(error: TypeError): TypeError {
|
|
81
|
+
const flagged = error as unknown as { [key: symbol]: true };
|
|
82
|
+
flagged[trailingDataErrorSymbol] = true;
|
|
83
|
+
return error;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class DecompressionStream {
|
|
87
|
+
private _format: DecompressionFormat;
|
|
88
|
+
private _readable: ReadableStream<Uint8Array>;
|
|
89
|
+
private _writable: WritableStream<Uint8Array>;
|
|
90
|
+
|
|
91
|
+
constructor(format: DecompressionFormat) {
|
|
92
|
+
if (format !== 'gzip' && format !== 'deflate' && format !== 'deflate-raw' && format !== 'brotli') {
|
|
93
|
+
throw new TypeError(`Unsupported decompression format: '${format}'`);
|
|
94
|
+
}
|
|
95
|
+
this._format = format;
|
|
96
|
+
|
|
97
|
+
// Native bridge mode: 0 = deflate (zlib header), 1 = gzip, 2 = raw deflate (no header)
|
|
98
|
+
const mode = format === 'gzip' ? 1 : format === 'deflate-raw' ? 2 : 0;
|
|
99
|
+
|
|
100
|
+
const decompressChunk = (chunk: Uint8Array, strict = false): Uint8Array => {
|
|
101
|
+
const input = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
|
|
102
|
+
|
|
103
|
+
if (format === 'brotli') {
|
|
104
|
+
if (typeof g.__exactBrotliDecompressSync !== 'function') {
|
|
105
|
+
throw new Error('Native decompression bridge (__exactBrotliDecompressSync) not available');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
return g.__exactBrotliDecompressSync(input, strict);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw toTypeError(error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (typeof g.__exactInflateSync !== 'function') {
|
|
116
|
+
throw new Error('Native decompression bridge (__exactInflateSync) not available');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
return g.__exactInflateSync(input, mode, strict);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw toTypeError(error);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
let streamError: TypeError | null = null;
|
|
127
|
+
let streamFinished = false;
|
|
128
|
+
let trailingDataError = false;
|
|
129
|
+
let writableClosed = false;
|
|
130
|
+
let readableController: ReadableStreamDefaultController<Uint8Array> | undefined;
|
|
131
|
+
const isBrotliFormat = format === 'brotli';
|
|
132
|
+
let streamInput = new Uint8Array(0);
|
|
133
|
+
let brotliOutputByteLength = 0;
|
|
134
|
+
|
|
135
|
+
const appendInput = (chunk: Uint8Array): void => {
|
|
136
|
+
if (chunk.length === 0) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const combined = new Uint8Array(streamInput.length + chunk.length);
|
|
141
|
+
combined.set(streamInput, 0);
|
|
142
|
+
combined.set(chunk, streamInput.length);
|
|
143
|
+
streamInput = combined;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const validateChunkCompletion = (): void => {
|
|
147
|
+
if (streamError || streamFinished) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
decompressChunk(streamInput, false);
|
|
153
|
+
streamFinished = true;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
const decodedError = toTypeError(error);
|
|
156
|
+
if (isRecoverableIncompleteError(decodedError)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (isTrailingDataError(decodedError)) {
|
|
160
|
+
markTrailingDataError(decodedError);
|
|
161
|
+
streamError = decodedError;
|
|
162
|
+
trailingDataError = true;
|
|
163
|
+
streamFinished = true;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
streamError = decodedError;
|
|
168
|
+
if (readableController) {
|
|
169
|
+
readableController.error(streamError);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const processBrotliChunk = (): void => {
|
|
175
|
+
if (streamError || !readableController) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let output: Uint8Array;
|
|
180
|
+
try {
|
|
181
|
+
output = decompressChunk(streamInput);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
const decodedError = toTypeError(error);
|
|
184
|
+
if (isRecoverableIncompleteError(decodedError)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (isTrailingDataError(decodedError)) {
|
|
188
|
+
markTrailingDataError(decodedError);
|
|
189
|
+
trailingDataError = true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
streamError = decodedError;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (output.byteLength > brotliOutputByteLength) {
|
|
197
|
+
readableController.enqueue(output.slice(brotliOutputByteLength));
|
|
198
|
+
brotliOutputByteLength = output.byteLength;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const readable = new ReadableStream<Uint8Array>({
|
|
203
|
+
start(controller) {
|
|
204
|
+
readableController = controller;
|
|
205
|
+
if (streamError) {
|
|
206
|
+
controller.error(streamError);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
pull(controller) {
|
|
211
|
+
if (!streamFinished && streamInput.length > 0 && !streamError) {
|
|
212
|
+
validateChunkCompletion();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (streamError && trailingDataError) {
|
|
216
|
+
controller.error(streamError);
|
|
217
|
+
return Promise.resolve();
|
|
218
|
+
}
|
|
219
|
+
if (streamError) {
|
|
220
|
+
controller.error(streamError);
|
|
221
|
+
return Promise.resolve();
|
|
222
|
+
}
|
|
223
|
+
if (writableClosed && streamError === null && !streamFinished) {
|
|
224
|
+
streamError = new TypeError("Decompression stream failed: incomplete data");
|
|
225
|
+
controller.error(streamError);
|
|
226
|
+
return Promise.resolve();
|
|
227
|
+
}
|
|
228
|
+
if (writableClosed && readableController && streamError === null) {
|
|
229
|
+
controller.close();
|
|
230
|
+
return Promise.resolve();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return Promise.resolve();
|
|
234
|
+
},
|
|
235
|
+
cancel() {
|
|
236
|
+
writableClosed = true;
|
|
237
|
+
return Promise.resolve();
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const writable = new WritableStream<Uint8Array>({
|
|
242
|
+
write(chunk) {
|
|
243
|
+
try {
|
|
244
|
+
if (streamError) {
|
|
245
|
+
throw streamError;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (isBrotliFormat) {
|
|
249
|
+
if (chunk.length === 0) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const output = decompressChunk(chunk);
|
|
254
|
+
streamFinished = true;
|
|
255
|
+
if (output.byteLength > 0 && readableController) {
|
|
256
|
+
readableController.enqueue(output);
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
appendInput(chunk);
|
|
262
|
+
|
|
263
|
+
let output: Uint8Array;
|
|
264
|
+
try {
|
|
265
|
+
output = decompressChunk(chunk);
|
|
266
|
+
streamFinished = true;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
streamError = toTypeError(error);
|
|
269
|
+
if (readableController) {
|
|
270
|
+
readableController.error(streamError);
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (output.byteLength > 0 && readableController) {
|
|
276
|
+
readableController.enqueue(output);
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
streamError = toTypeError(error);
|
|
280
|
+
if (readableController) {
|
|
281
|
+
readableController.error(streamError);
|
|
282
|
+
}
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
async close() {
|
|
287
|
+
try {
|
|
288
|
+
writableClosed = true;
|
|
289
|
+
|
|
290
|
+
if (streamError) {
|
|
291
|
+
if (readableController) {
|
|
292
|
+
readableController.error(streamError);
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (!streamFinished && streamInput.length > 0 && !streamError) {
|
|
298
|
+
validateChunkCompletion();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (streamError === null && !streamFinished) {
|
|
302
|
+
streamError = new TypeError("Decompression stream failed: incomplete data");
|
|
303
|
+
if (readableController) {
|
|
304
|
+
readableController.error(streamError);
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (streamError === null && readableController) {
|
|
310
|
+
readableController.close();
|
|
311
|
+
}
|
|
312
|
+
} catch (error) {
|
|
313
|
+
streamError = streamError || toTypeError(error);
|
|
314
|
+
if (readableController) {
|
|
315
|
+
readableController.error(streamError);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
async abort(reason) {
|
|
320
|
+
if (streamError === null) {
|
|
321
|
+
streamError = reason === undefined ? new TypeError("The operation was aborted.") : toTypeError(reason);
|
|
322
|
+
}
|
|
323
|
+
writableClosed = true;
|
|
324
|
+
if (readableController) {
|
|
325
|
+
readableController.error(streamError);
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
this._readable = readable;
|
|
331
|
+
this._writable = writable;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
get readable(): ReadableStream<Uint8Array> {
|
|
336
|
+
return this._readable;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
get writable(): WritableStream<Uint8Array> {
|
|
340
|
+
return this._writable;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console - Web Standard Console Implementation
|
|
3
|
+
*
|
|
4
|
+
* @see https://console.spec.whatwg.org/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { inspect } from "../inspect";
|
|
8
|
+
|
|
9
|
+
export type LogLevel = "log" | "info" | "warn" | "error" | "debug" | "trace";
|
|
10
|
+
|
|
11
|
+
export interface ConsoleOutput {
|
|
12
|
+
log(level: LogLevel, args: any[]): void;
|
|
13
|
+
group?(label: string, collapsed: boolean): void;
|
|
14
|
+
groupEnd?(): void;
|
|
15
|
+
clear?(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Capture the native console BEFORE we overwrite it
|
|
19
|
+
// The native layer (Hermes/JSC) sets up globalThis.console with native logging
|
|
20
|
+
const _nativeConsole = (globalThis as any).console;
|
|
21
|
+
|
|
22
|
+
// Default output sends to native console
|
|
23
|
+
// Note: We need to look up console dynamically because it might be
|
|
24
|
+
// set up AFTER our module loads but BEFORE our methods are called
|
|
25
|
+
let output: ConsoleOutput = {
|
|
26
|
+
log: (level, args) => {
|
|
27
|
+
// First try: use captured native console
|
|
28
|
+
if (_nativeConsole && typeof _nativeConsole[level] === 'function') {
|
|
29
|
+
_nativeConsole[level](...args);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Second try: use current global console (might be set up later)
|
|
33
|
+
const currentConsole = (globalThis as any).console;
|
|
34
|
+
if (currentConsole && currentConsole !== _nativeConsole && typeof currentConsole[level] === 'function') {
|
|
35
|
+
currentConsole[level](...args);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function setConsoleOutput(newOutput: ConsoleOutput): void {
|
|
42
|
+
output = newOutput;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Counters for console.count
|
|
46
|
+
const counters = new Map<string, number>();
|
|
47
|
+
|
|
48
|
+
// Timers for console.time
|
|
49
|
+
const timers = new Map<string, number>();
|
|
50
|
+
|
|
51
|
+
// Group depth tracking
|
|
52
|
+
let groupDepth = 0;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Format a single value for output.
|
|
56
|
+
*/
|
|
57
|
+
function formatValue(arg: any): string {
|
|
58
|
+
if (arg === undefined) return "undefined";
|
|
59
|
+
if (arg === null) return "null";
|
|
60
|
+
if (typeof arg === "object") {
|
|
61
|
+
return inspect(arg, { colors: false });
|
|
62
|
+
}
|
|
63
|
+
return String(arg);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Format arguments, supporting printf-style format specifiers.
|
|
68
|
+
* Supports: %s (string), %d/%i (integer), %f (float), %o/%O (object), %c (CSS - ignored)
|
|
69
|
+
*
|
|
70
|
+
* @see https://console.spec.whatwg.org/#formatter
|
|
71
|
+
*/
|
|
72
|
+
function formatArgs(args: any[]): any[] {
|
|
73
|
+
if (args.length === 0) return [];
|
|
74
|
+
|
|
75
|
+
const first = args[0];
|
|
76
|
+
|
|
77
|
+
// If first arg is not a string or has no format specifiers, just format all args
|
|
78
|
+
if (typeof first !== 'string' || !/%[sdifoOc%]/.test(first)) {
|
|
79
|
+
return args.map(formatValue);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Process format specifiers
|
|
83
|
+
let result = '';
|
|
84
|
+
let argIndex = 1;
|
|
85
|
+
let i = 0;
|
|
86
|
+
|
|
87
|
+
while (i < first.length) {
|
|
88
|
+
if (first[i] === '%' && i + 1 < first.length) {
|
|
89
|
+
const specifier = first[i + 1];
|
|
90
|
+
|
|
91
|
+
switch (specifier) {
|
|
92
|
+
case 's': // String
|
|
93
|
+
if (argIndex < args.length) {
|
|
94
|
+
result += String(args[argIndex++]);
|
|
95
|
+
} else {
|
|
96
|
+
result += '%s';
|
|
97
|
+
}
|
|
98
|
+
i += 2;
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
case 'd': // Integer
|
|
102
|
+
case 'i':
|
|
103
|
+
if (argIndex < args.length) {
|
|
104
|
+
const num = Number(args[argIndex++]);
|
|
105
|
+
result += isNaN(num) ? 'NaN' : String(Math.trunc(num));
|
|
106
|
+
} else {
|
|
107
|
+
result += `%${specifier}`;
|
|
108
|
+
}
|
|
109
|
+
i += 2;
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case 'f': // Float
|
|
113
|
+
if (argIndex < args.length) {
|
|
114
|
+
const num = Number(args[argIndex++]);
|
|
115
|
+
result += isNaN(num) ? 'NaN' : String(num);
|
|
116
|
+
} else {
|
|
117
|
+
result += '%f';
|
|
118
|
+
}
|
|
119
|
+
i += 2;
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
case 'o': // Object (compact)
|
|
123
|
+
case 'O': // Object (verbose)
|
|
124
|
+
if (argIndex < args.length) {
|
|
125
|
+
result += inspect(args[argIndex++], {
|
|
126
|
+
colors: false,
|
|
127
|
+
compact: specifier === 'o',
|
|
128
|
+
});
|
|
129
|
+
} else {
|
|
130
|
+
result += `%${specifier}`;
|
|
131
|
+
}
|
|
132
|
+
i += 2;
|
|
133
|
+
break;
|
|
134
|
+
|
|
135
|
+
case 'c': // CSS styling (ignored in non-browser environments)
|
|
136
|
+
// Skip the style argument but don't output anything
|
|
137
|
+
if (argIndex < args.length) {
|
|
138
|
+
argIndex++;
|
|
139
|
+
}
|
|
140
|
+
i += 2;
|
|
141
|
+
break;
|
|
142
|
+
|
|
143
|
+
case '%': // Literal %
|
|
144
|
+
result += '%';
|
|
145
|
+
i += 2;
|
|
146
|
+
break;
|
|
147
|
+
|
|
148
|
+
default:
|
|
149
|
+
// Unknown specifier, output as-is
|
|
150
|
+
result += first[i];
|
|
151
|
+
i++;
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
result += first[i];
|
|
155
|
+
i++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Append any remaining arguments
|
|
160
|
+
const remaining = args.slice(argIndex).map(formatValue);
|
|
161
|
+
|
|
162
|
+
if (remaining.length > 0) {
|
|
163
|
+
return [result, ...remaining];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return [result];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function getIndent(): string {
|
|
170
|
+
return " ".repeat(groupDepth);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export class Console {
|
|
174
|
+
log(...args: any[]): void {
|
|
175
|
+
output.log("log", formatArgs(args));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
info(...args: any[]): void {
|
|
179
|
+
output.log("info", formatArgs(args));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
warn(...args: any[]): void {
|
|
183
|
+
output.log("warn", formatArgs(args));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
error(...args: any[]): void {
|
|
187
|
+
output.log("error", formatArgs(args));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
debug(...args: any[]): void {
|
|
191
|
+
output.log("debug", formatArgs(args));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
trace(...args: any[]): void {
|
|
195
|
+
// Create stack trace
|
|
196
|
+
const err = new Error();
|
|
197
|
+
const stack = err.stack?.split("\n").slice(2).join("\n") ?? "";
|
|
198
|
+
output.log("trace", [...formatArgs(args), "\n" + stack]);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
assert(condition?: boolean, ...args: any[]): void {
|
|
202
|
+
if (!condition) {
|
|
203
|
+
const message = args.length > 0 ? formatArgs(args) : ["Assertion failed"];
|
|
204
|
+
output.log("error", ["Assertion failed:", ...message]);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
table(data: any, columns?: string[]): void {
|
|
209
|
+
if (data === null || data === undefined) {
|
|
210
|
+
output.log("log", [data]);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (typeof data !== "object") {
|
|
215
|
+
output.log("log", [data]);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Simple table implementation
|
|
220
|
+
const isArray = Array.isArray(data);
|
|
221
|
+
const entries = isArray
|
|
222
|
+
? data.map((item, index) => [index, item])
|
|
223
|
+
: Object.entries(data);
|
|
224
|
+
|
|
225
|
+
if (entries.length === 0) {
|
|
226
|
+
output.log("log", [isArray ? "[]" : "{}"]);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Build table string
|
|
231
|
+
const header = isArray ? "(index)" : "(key)";
|
|
232
|
+
let tableStr = `\n${header}\tValue\n`;
|
|
233
|
+
tableStr += "-".repeat(40) + "\n";
|
|
234
|
+
|
|
235
|
+
for (const [key, value] of entries) {
|
|
236
|
+
const valueStr =
|
|
237
|
+
typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
238
|
+
tableStr += `${key}\t${valueStr}\n`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
output.log("log", [tableStr]);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
group(label?: string): void {
|
|
245
|
+
const groupLabel = label ?? "console.group";
|
|
246
|
+
if (output.group) {
|
|
247
|
+
output.group(groupLabel, false);
|
|
248
|
+
} else {
|
|
249
|
+
output.log("log", [`${getIndent()}▼ ${groupLabel}`]);
|
|
250
|
+
}
|
|
251
|
+
groupDepth++;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
groupCollapsed(label?: string): void {
|
|
255
|
+
const groupLabel = label ?? "console.group";
|
|
256
|
+
if (output.group) {
|
|
257
|
+
output.group(groupLabel, true);
|
|
258
|
+
} else {
|
|
259
|
+
output.log("log", [`${getIndent()}▶ ${groupLabel}`]);
|
|
260
|
+
}
|
|
261
|
+
groupDepth++;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
groupEnd(): void {
|
|
265
|
+
if (groupDepth > 0) {
|
|
266
|
+
groupDepth--;
|
|
267
|
+
}
|
|
268
|
+
if (output.groupEnd) {
|
|
269
|
+
output.groupEnd();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
count(label?: string): void {
|
|
274
|
+
const countLabel = label ?? "default";
|
|
275
|
+
const count = (counters.get(countLabel) ?? 0) + 1;
|
|
276
|
+
counters.set(countLabel, count);
|
|
277
|
+
output.log("log", [`${countLabel}: ${count}`]);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
countReset(label?: string): void {
|
|
281
|
+
const countLabel = label ?? "default";
|
|
282
|
+
if (counters.has(countLabel)) {
|
|
283
|
+
counters.set(countLabel, 0);
|
|
284
|
+
} else {
|
|
285
|
+
output.log("warn", [`Count for '${countLabel}' does not exist`]);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
time(label?: string): void {
|
|
290
|
+
const timerLabel = label ?? "default";
|
|
291
|
+
if (timers.has(timerLabel)) {
|
|
292
|
+
output.log("warn", [`Timer '${timerLabel}' already exists`]);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
timers.set(timerLabel, performance?.now?.() ?? Date.now());
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
timeEnd(label?: string): void {
|
|
299
|
+
const timerLabel = label ?? "default";
|
|
300
|
+
const startTime = timers.get(timerLabel);
|
|
301
|
+
if (startTime === undefined) {
|
|
302
|
+
output.log("warn", [`Timer '${timerLabel}' does not exist`]);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const duration = (performance?.now?.() ?? Date.now()) - startTime;
|
|
306
|
+
timers.delete(timerLabel);
|
|
307
|
+
output.log("log", [`${timerLabel}: ${duration.toFixed(3)}ms`]);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
timeLog(label?: string, ...args: any[]): void {
|
|
311
|
+
const timerLabel = label ?? "default";
|
|
312
|
+
const startTime = timers.get(timerLabel);
|
|
313
|
+
if (startTime === undefined) {
|
|
314
|
+
output.log("warn", [`Timer '${timerLabel}' does not exist`]);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const duration = (performance?.now?.() ?? Date.now()) - startTime;
|
|
318
|
+
output.log("log", [`${timerLabel}: ${duration.toFixed(3)}ms`, ...formatArgs(args)]);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
clear(): void {
|
|
322
|
+
if (output.clear) {
|
|
323
|
+
output.clear();
|
|
324
|
+
} else {
|
|
325
|
+
// Output some newlines or clear indicator
|
|
326
|
+
output.log("log", ["\n".repeat(50) + "--- Console cleared ---"]);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Aliases
|
|
331
|
+
dir(obj: any): void {
|
|
332
|
+
this.log(obj);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
dirxml(obj: any): void {
|
|
336
|
+
this.log(obj);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Create singleton instance
|
|
341
|
+
export const console = new Console();
|