@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.
Files changed (161) hide show
  1. package/package.json +63 -0
  2. package/src/abort/AbortController.ts +23 -0
  3. package/src/abort/AbortSignal.ts +152 -0
  4. package/src/abort/index.ts +2 -0
  5. package/src/accessibility.ts +12 -0
  6. package/src/arraybuffer-detach.ts +109 -0
  7. package/src/base64/base64.ts +168 -0
  8. package/src/base64/index.ts +1 -0
  9. package/src/blob/Blob.ts +259 -0
  10. package/src/blob/File.ts +59 -0
  11. package/src/blob/FormData.ts +323 -0
  12. package/src/blob/index.ts +3 -0
  13. package/src/bootstrap.ts +1946 -0
  14. package/src/broadcast/BroadcastChannel.ts +280 -0
  15. package/src/broadcast/index.ts +5 -0
  16. package/src/cache/Cache.ts +349 -0
  17. package/src/cache/CacheStorage.ts +89 -0
  18. package/src/cache/index.ts +27 -0
  19. package/src/camera/index.ts +6202 -0
  20. package/src/camera/processor.worker.ts +194 -0
  21. package/src/camera/scene.ts +195 -0
  22. package/src/clipboard/Clipboard.ts +129 -0
  23. package/src/clipboard/ClipboardItem.ts +97 -0
  24. package/src/clipboard/index.ts +6 -0
  25. package/src/clone/index.ts +1 -0
  26. package/src/clone/structuredClone.ts +389 -0
  27. package/src/clone/transferableSymbols.ts +2 -0
  28. package/src/compression/CompressionStream.ts +146 -0
  29. package/src/compression/DecompressionStream.ts +342 -0
  30. package/src/compression/index.ts +4 -0
  31. package/src/console/Console.ts +341 -0
  32. package/src/console/index.ts +2 -0
  33. package/src/core/accessibility-state.ts +263 -0
  34. package/src/core/accessibility.ts +184 -0
  35. package/src/core/agent-state.ts +37 -0
  36. package/src/core/diagnostics-logs.ts +144 -0
  37. package/src/core/host-call-bridge.ts +16 -0
  38. package/src/core/i18n-helpers.ts +189 -0
  39. package/src/core/locale-state.ts +253 -0
  40. package/src/core/locale.ts +95 -0
  41. package/src/crypto/Crypto.ts +2743 -0
  42. package/src/crypto/index.ts +1 -0
  43. package/src/diagnostics/logs.ts +7 -0
  44. package/src/encoding/TextDecoder.ts +1181 -0
  45. package/src/encoding/TextDecoderStream.ts +58 -0
  46. package/src/encoding/TextEncoder.ts +180 -0
  47. package/src/encoding/TextEncoderStream.ts +39 -0
  48. package/src/encoding/index.ts +8 -0
  49. package/src/events/CloseEvent.ts +91 -0
  50. package/src/events/DOMException.ts +409 -0
  51. package/src/events/ErrorEvent.ts +39 -0
  52. package/src/events/Event.ts +151 -0
  53. package/src/events/EventTarget.ts +280 -0
  54. package/src/events/FocusEvent.ts +27 -0
  55. package/src/events/KeyboardEvent.ts +46 -0
  56. package/src/events/MessageEvent.ts +61 -0
  57. package/src/events/ProgressEvent.ts +33 -0
  58. package/src/events/PromiseRejectionEvent.ts +31 -0
  59. package/src/events/index.ts +52 -0
  60. package/src/eventsource/EventSource.ts +371 -0
  61. package/src/eventsource/index.ts +2 -0
  62. package/src/fetch/Headers.ts +642 -0
  63. package/src/fetch/Request.ts +760 -0
  64. package/src/fetch/Response.ts +543 -0
  65. package/src/fetch/body.ts +1256 -0
  66. package/src/fetch/cookie-jar.ts +566 -0
  67. package/src/fetch/demo.ts +207 -0
  68. package/src/fetch/errors.ts +101 -0
  69. package/src/fetch/fetch.ts +2610 -0
  70. package/src/fetch/index.ts +101 -0
  71. package/src/fetch/native-bridge.ts +65 -0
  72. package/src/fetch/types.ts +258 -0
  73. package/src/filereader/FileReader.ts +236 -0
  74. package/src/filereader/index.ts +1 -0
  75. package/src/fs/Dirent.ts +39 -0
  76. package/src/fs/ExactFile.ts +450 -0
  77. package/src/fs/Stats.ts +80 -0
  78. package/src/fs/index.ts +944 -0
  79. package/src/fs/promises.ts +386 -0
  80. package/src/fs/shared.ts +328 -0
  81. package/src/http-server/index.js +697 -0
  82. package/src/http-server/index.ts +27 -0
  83. package/src/identity.generated.ts +14 -0
  84. package/src/index.ts +283 -0
  85. package/src/indexeddb/IDBCursor.ts +188 -0
  86. package/src/indexeddb/IDBDatabase.ts +343 -0
  87. package/src/indexeddb/IDBFactory.ts +269 -0
  88. package/src/indexeddb/IDBIndex.ts +194 -0
  89. package/src/indexeddb/IDBKeyRange.ts +109 -0
  90. package/src/indexeddb/IDBObjectStore.ts +468 -0
  91. package/src/indexeddb/IDBRequest.ts +163 -0
  92. package/src/indexeddb/IDBTransaction.ts +207 -0
  93. package/src/indexeddb/index.ts +34 -0
  94. package/src/indexeddb/utils.ts +52 -0
  95. package/src/inspect/index.ts +1 -0
  96. package/src/inspect/inspect.ts +465 -0
  97. package/src/internal/detect.ts +104 -0
  98. package/src/locale.ts +10 -0
  99. package/src/location/index.ts +1059 -0
  100. package/src/locks/LockManager.ts +460 -0
  101. package/src/locks/index.ts +12 -0
  102. package/src/media/VideoFrame.ts +58 -0
  103. package/src/messaging/MessageChannel.ts +31 -0
  104. package/src/messaging/MessagePort.ts +180 -0
  105. package/src/messaging/index.ts +2 -0
  106. package/src/messaging.ts +247 -0
  107. package/src/native/NativeModules.ts +354 -0
  108. package/src/native/index.ts +1 -0
  109. package/src/navigator/Navigator.ts +351 -0
  110. package/src/navigator/index.ts +1 -0
  111. package/src/node/Buffer.ts +1786 -0
  112. package/src/node/index.ts +4 -0
  113. package/src/node/path.ts +495 -0
  114. package/src/node/process.ts +2528 -0
  115. package/src/performance/Performance.ts +532 -0
  116. package/src/performance/index.ts +21 -0
  117. package/src/polyfills/array.ts +236 -0
  118. package/src/polyfills/arraybuffer.ts +172 -0
  119. package/src/polyfills/groupby.ts +85 -0
  120. package/src/polyfills/index.ts +85 -0
  121. package/src/polyfills/intl.ts +1956 -0
  122. package/src/polyfills/iterator.ts +479 -0
  123. package/src/polyfills/promise.ts +37 -0
  124. package/src/polyfills/set.ts +245 -0
  125. package/src/polyfills/string.ts +85 -0
  126. package/src/polyfills/typedarray.ts +110 -0
  127. package/src/promise-rejection-tracking.ts +464 -0
  128. package/src/react-native/index.ts +388 -0
  129. package/src/runtime-entry.ts +55 -0
  130. package/src/scheduling/AnimationFrame.ts +105 -0
  131. package/src/scheduling/IdleCallback.ts +167 -0
  132. package/src/scheduling/index.ts +13 -0
  133. package/src/security/Capabilities.ts +1146 -0
  134. package/src/security/Permissions.ts +392 -0
  135. package/src/security/capability-bits.generated.ts +63 -0
  136. package/src/security/index.ts +16 -0
  137. package/src/sqlite/Database.ts +456 -0
  138. package/src/sqlite/Statement.ts +206 -0
  139. package/src/sqlite/constants.ts +79 -0
  140. package/src/sqlite/errors.ts +25 -0
  141. package/src/sqlite/index.ts +34 -0
  142. package/src/sqlite/module.js +438 -0
  143. package/src/storage/Storage.ts +291 -0
  144. package/src/storage/StorageManager.ts +91 -0
  145. package/src/storage/index.ts +3 -0
  146. package/src/stream-compat.ts +47 -0
  147. package/src/streams/ReadableStream.ts +4131 -0
  148. package/src/streams/TransformStream.ts +375 -0
  149. package/src/streams/WritableStream.ts +866 -0
  150. package/src/streams/index.ts +41 -0
  151. package/src/timers/Timers.ts +296 -0
  152. package/src/timers/index.ts +11 -0
  153. package/src/url/URL.ts +656 -0
  154. package/src/url/URLPattern.ts +850 -0
  155. package/src/url/URLSearchParams.ts +244 -0
  156. package/src/url/index.ts +9 -0
  157. package/src/websocket/WebSocket.ts +770 -0
  158. package/src/websocket/WebSocketError.ts +52 -0
  159. package/src/websocket/WebSocketStream.ts +628 -0
  160. package/src/websocket/index.ts +7 -0
  161. package/src/window/index.ts +872 -0
@@ -0,0 +1,872 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Window API Implementation for Ibex Runtime
4
+ *
5
+ * Provides browser-compatible window object for library compatibility.
6
+ * Many npm packages check for `typeof window !== 'undefined'` to detect
7
+ * browser environments.
8
+ *
9
+ * Note: This is NOT a DOM window - it's a compatibility shim that provides
10
+ * commonly-used window properties that make sense in a native app context.
11
+ */
12
+
13
+ import { CustomEvent, Event, EventTarget } from "../events";
14
+
15
+ // Type declarations for native bridge
16
+ declare global {
17
+ var __exactGetScreenInfo: (() => {
18
+ width: number;
19
+ height: number;
20
+ scale: number;
21
+ fontScale: number;
22
+ }) | undefined;
23
+ var __exactAppearanceState:
24
+ | {
25
+ colorScheme?: 'light' | 'dark';
26
+ reducedMotion?: boolean;
27
+ }
28
+ | undefined;
29
+ var __exactWindowNotifyMediaChange:
30
+ | ((next: { colorScheme?: 'light' | 'dark'; reducedMotion?: boolean }) => void)
31
+ | undefined;
32
+ var __exactWindowNotifyResize: (() => void) | undefined;
33
+ var __exactAndroidDispatchPlatformEvent:
34
+ | ((event: string | AndroidPlatformEvent, state?: AndroidPlatformState | null) => void)
35
+ | undefined;
36
+ var __exactAndroidDrainPlatformEvents: (() => void) | undefined;
37
+ var __exactAndroidGetPlatformState: (() => AndroidPlatformState) | undefined;
38
+ var __exactAppState: AndroidAppState | undefined;
39
+ var __exactInitialURL: string | null | undefined;
40
+ var __exactLocaleChanged:
41
+ | ((snapshot?: { tag?: string; tags?: readonly string[]; uses24Hour?: boolean } | null) => void)
42
+ | undefined;
43
+ var __exactAccessibilityChanged:
44
+ | ((snapshot?: Record<string, unknown> | null) => void)
45
+ | undefined;
46
+ var __exactReactNativeNotifyDimensionsChange:
47
+ | ((next?: AndroidPlatformState['dimensions']) => void)
48
+ | undefined;
49
+ var __exactReactNativeNotifyAppState:
50
+ | ((state: AndroidAppState) => void)
51
+ | undefined;
52
+ var __exactReactNativeNotifyMemoryWarning: (() => void) | undefined;
53
+ var __exactReactNativeNotifyURL: ((url: string) => void) | undefined;
54
+ var __exactNativeDialog:
55
+ | ((type: 'alert' | 'confirm' | 'prompt', message: string, defaultValue?: string) => string | null)
56
+ | undefined;
57
+ }
58
+
59
+ type AndroidAppState = 'active' | 'background' | 'inactive' | 'unknown';
60
+
61
+ interface AndroidPlatformEvent {
62
+ type?: string;
63
+ state?: AndroidAppState;
64
+ url?: string;
65
+ }
66
+
67
+ interface AndroidPlatformState {
68
+ appState?: AndroidAppState;
69
+ initialURL?: string | null;
70
+ locale?: { tag?: string; tags?: readonly string[]; uses24Hour?: boolean };
71
+ screen?: {
72
+ width?: number;
73
+ height?: number;
74
+ scale?: number;
75
+ fontScale?: number;
76
+ };
77
+ dimensions?: {
78
+ window?: {
79
+ width?: number;
80
+ height?: number;
81
+ scale?: number;
82
+ fontScale?: number;
83
+ };
84
+ screen?: {
85
+ width?: number;
86
+ height?: number;
87
+ scale?: number;
88
+ fontScale?: number;
89
+ };
90
+ };
91
+ appearance?: {
92
+ colorScheme?: 'light' | 'dark';
93
+ reducedMotion?: boolean;
94
+ };
95
+ accessibility?: Record<string, unknown>;
96
+ }
97
+
98
+ /**
99
+ * Screen information
100
+ */
101
+ export interface ScreenInfo {
102
+ /** Screen width in points */
103
+ width: number;
104
+ /** Screen height in points */
105
+ height: number;
106
+ /** Available width (minus system UI) */
107
+ availWidth: number;
108
+ /** Available height (minus system UI) */
109
+ availHeight: number;
110
+ /** Color depth (always 24 for mobile) */
111
+ colorDepth: number;
112
+ /** Pixel depth (always 24 for mobile) */
113
+ pixelDepth: number;
114
+ /** Screen orientation */
115
+ orientation: {
116
+ type: 'portrait-primary' | 'portrait-secondary' | 'landscape-primary' | 'landscape-secondary';
117
+ angle: number;
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Location stub for compatibility
123
+ * In a native app, this represents the current "virtual" location
124
+ */
125
+ export interface LocationInfo {
126
+ href: string;
127
+ protocol: string;
128
+ host: string;
129
+ hostname: string;
130
+ port: string;
131
+ pathname: string;
132
+ search: string;
133
+ hash: string;
134
+ origin: string;
135
+ reload: () => void;
136
+ replace: (url: string) => void;
137
+ assign: (url: string) => void;
138
+ }
139
+
140
+ /**
141
+ * Get screen dimensions from native or use defaults
142
+ */
143
+ function getScreenDimensions(): { width: number; height: number; scale: number; fontScale: number } {
144
+ if (typeof __exactGetScreenInfo === 'function') {
145
+ const info = __exactGetScreenInfo();
146
+ return {
147
+ width: info.width,
148
+ height: info.height,
149
+ scale: info.scale,
150
+ fontScale: info.fontScale,
151
+ };
152
+ }
153
+ // Default fallback (iPhone 14 Pro dimensions)
154
+ return { width: 393, height: 852, scale: 3, fontScale: 1 };
155
+ }
156
+
157
+ function getScreenOrientation(): ScreenInfo['orientation'] {
158
+ const dims = getScreenDimensions();
159
+ const landscape = dims.width > dims.height;
160
+ return {
161
+ type: landscape ? 'landscape-primary' : 'portrait-primary',
162
+ angle: landscape ? 90 : 0,
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Create the screen object
168
+ */
169
+ function createScreen(): ScreenInfo {
170
+ const dims = getScreenDimensions();
171
+
172
+ return {
173
+ get width() {
174
+ return getScreenDimensions().width;
175
+ },
176
+ get height() {
177
+ return getScreenDimensions().height;
178
+ },
179
+ get availWidth() {
180
+ return getScreenDimensions().width;
181
+ },
182
+ get availHeight() {
183
+ // Subtract status bar height approximation
184
+ return getScreenDimensions().height - 47;
185
+ },
186
+ colorDepth: 24,
187
+ pixelDepth: 24,
188
+ orientation: {
189
+ get type() {
190
+ return getScreenOrientation().type;
191
+ },
192
+ get angle() {
193
+ return getScreenOrientation().angle;
194
+ },
195
+ },
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Create location stub
201
+ */
202
+ function createLocation(): LocationInfo {
203
+ // In native apps, we use a virtual location
204
+ // Apps can override this if they implement deep linking
205
+ let currentHref = 'exact://app/';
206
+
207
+ const parseUrl = (href: string) => {
208
+ try {
209
+ const url = new URL(href);
210
+ return {
211
+ href: url.href,
212
+ protocol: url.protocol,
213
+ host: url.host,
214
+ hostname: url.hostname,
215
+ port: url.port,
216
+ pathname: url.pathname,
217
+ search: url.search,
218
+ hash: url.hash,
219
+ origin: url.origin,
220
+ };
221
+ } catch {
222
+ return {
223
+ href,
224
+ protocol: 'exact:',
225
+ host: 'app',
226
+ hostname: 'app',
227
+ port: '',
228
+ pathname: '/',
229
+ search: '',
230
+ hash: '',
231
+ origin: 'exact://app',
232
+ };
233
+ }
234
+ };
235
+
236
+ const location: LocationInfo = {
237
+ get href() { return parseUrl(currentHref).href; },
238
+ get protocol() { return parseUrl(currentHref).protocol; },
239
+ get host() { return parseUrl(currentHref).host; },
240
+ get hostname() { return parseUrl(currentHref).hostname; },
241
+ get port() { return parseUrl(currentHref).port; },
242
+ get pathname() { return parseUrl(currentHref).pathname; },
243
+ get search() { return parseUrl(currentHref).search; },
244
+ get hash() { return parseUrl(currentHref).hash; },
245
+ get origin() { return parseUrl(currentHref).origin; },
246
+ toString() { return parseUrl(currentHref).href; },
247
+ reload: () => {
248
+ // No-op in native - could trigger app refresh
249
+ console.warn('location.reload() is not supported in Ibex runtime');
250
+ },
251
+ replace: (url: string) => {
252
+ currentHref = url;
253
+ },
254
+ assign: (url: string) => {
255
+ currentHref = url;
256
+ },
257
+ };
258
+
259
+ return location;
260
+ }
261
+
262
+ interface AppearanceState {
263
+ colorScheme: 'light' | 'dark';
264
+ reducedMotion: boolean;
265
+ }
266
+
267
+ const mediaQueryLists = new Set<MediaQueryList>();
268
+
269
+ function normalizeAppearanceState(value: unknown): AppearanceState {
270
+ const candidate =
271
+ typeof value === 'object' && value !== null
272
+ ? (value as { colorScheme?: unknown; reducedMotion?: unknown })
273
+ : null;
274
+
275
+ return {
276
+ colorScheme: candidate?.colorScheme === 'dark' ? 'dark' : 'light',
277
+ reducedMotion: candidate?.reducedMotion === true,
278
+ };
279
+ }
280
+
281
+ let appearanceState = normalizeAppearanceState(globalThis.__exactAppearanceState);
282
+
283
+ function updateAppearanceState(next: unknown): void {
284
+ appearanceState = normalizeAppearanceState(next);
285
+ globalThis.__exactAppearanceState = { ...appearanceState };
286
+ for (const mediaQueryList of mediaQueryLists) {
287
+ mediaQueryList._syncFromAppearance();
288
+ }
289
+ }
290
+
291
+ function syncMediaQueries(): void {
292
+ for (const mediaQueryList of mediaQueryLists) {
293
+ mediaQueryList._syncFromAppearance();
294
+ }
295
+ }
296
+
297
+ function dispatchWindowEvent(event: Event): void {
298
+ try {
299
+ window.dispatchEvent(event);
300
+ } catch {
301
+ // Ignore app listener failures.
302
+ }
303
+ const globalDispatch = (globalThis as typeof globalThis & {
304
+ dispatchEvent?: (event: Event) => boolean;
305
+ }).dispatchEvent;
306
+ if (typeof globalDispatch === 'function' && globalThis !== window) {
307
+ try {
308
+ globalDispatch.call(globalThis, event);
309
+ } catch {
310
+ // Ignore app listener failures.
311
+ }
312
+ }
313
+ }
314
+
315
+ function normalizeAndroidPlatformEvent(event: string | AndroidPlatformEvent): AndroidPlatformEvent {
316
+ if (typeof event === 'string') {
317
+ try {
318
+ const parsed = JSON.parse(event);
319
+ return parsed && typeof parsed === 'object' ? parsed : { type: event };
320
+ } catch {
321
+ return { type: event };
322
+ }
323
+ }
324
+ return event && typeof event === 'object' ? event : {};
325
+ }
326
+
327
+ function applyAndroidPlatformState(
328
+ event: AndroidPlatformEvent,
329
+ state?: AndroidPlatformState | null,
330
+ ): void {
331
+ if (state?.locale) {
332
+ globalThis.__exactLocaleSnapshot = state.locale;
333
+ globalThis.__exactLocaleChanged?.(state.locale);
334
+ }
335
+
336
+ if (state?.accessibility) {
337
+ globalThis.__exactAccessibilitySnapshot = state.accessibility;
338
+ globalThis.__exactAccessibilityChanged?.(state.accessibility);
339
+ } else if (state?.appearance) {
340
+ updateAppearanceState(state.appearance);
341
+ }
342
+
343
+ if (state?.initialURL !== undefined) {
344
+ globalThis.__exactInitialURL = state.initialURL ?? null;
345
+ }
346
+
347
+ const nextState = event.state ?? state?.appState;
348
+ if (nextState) {
349
+ const previous = globalThis.__exactAppState;
350
+ globalThis.__exactAppState = nextState;
351
+ globalThis.__exactReactNativeNotifyAppState?.(nextState);
352
+ if (previous !== nextState) {
353
+ dispatchWindowEvent(new CustomEvent('appstatechange', { detail: { state: nextState } }));
354
+ }
355
+ }
356
+
357
+ if (state?.dimensions) {
358
+ globalThis.__exactReactNativeNotifyDimensionsChange?.(state.dimensions);
359
+ }
360
+
361
+ if (event.type === 'configuration') {
362
+ syncMediaQueries();
363
+ dispatchWindowEvent(new Event('resize'));
364
+ dispatchWindowEvent(new Event('orientationchange'));
365
+ } else if (event.type === 'memoryWarning') {
366
+ globalThis.__exactReactNativeNotifyMemoryWarning?.();
367
+ dispatchWindowEvent(new Event('memorywarning'));
368
+ }
369
+
370
+ if (event.type === 'url' && typeof event.url === 'string' && event.url.length > 0) {
371
+ globalThis.__exactInitialURL ??= event.url;
372
+ globalThis.__exactReactNativeNotifyURL?.(event.url);
373
+ dispatchWindowEvent(new CustomEvent('url', { detail: { url: event.url } }));
374
+ }
375
+ }
376
+
377
+ function callNativeDialog(
378
+ type: 'alert' | 'confirm' | 'prompt',
379
+ message: string,
380
+ defaultValue = '',
381
+ ): string | null | undefined {
382
+ if (typeof globalThis.__exactNativeDialog !== 'function') {
383
+ return undefined;
384
+ }
385
+ try {
386
+ return globalThis.__exactNativeDialog(type, message, defaultValue);
387
+ } catch (error) {
388
+ const detail = error instanceof Error ? error.message : String(error);
389
+ console.warn(`${type}() native dialog failed: ${detail}`);
390
+ return undefined;
391
+ }
392
+ }
393
+
394
+ /**
395
+ * MediaQueryList for matchMedia support
396
+ */
397
+ export class MediaQueryList extends EventTarget {
398
+ readonly media: string;
399
+ private _matches: boolean;
400
+
401
+ /** @deprecated Use addEventListener instead */
402
+ onchange: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | null = null;
403
+
404
+ constructor(media: string) {
405
+ super();
406
+ this.media = media;
407
+ this._matches = this._evaluate(media);
408
+ mediaQueryLists.add(this);
409
+ }
410
+
411
+ get matches(): boolean {
412
+ return this._matches;
413
+ }
414
+
415
+ private _evaluate(query: string): boolean {
416
+ const dims = getScreenDimensions();
417
+
418
+ // Simple media query parser
419
+ // Supports: (min-width: Xpx), (max-width: Xpx), (orientation: portrait/landscape)
420
+ // and (prefers-color-scheme: dark/light)
421
+
422
+ const minWidthMatch = query.match(/\(min-width:\s*(\d+)px\)/);
423
+ if (minWidthMatch) {
424
+ return dims.width >= parseInt(minWidthMatch[1], 10);
425
+ }
426
+
427
+ const maxWidthMatch = query.match(/\(max-width:\s*(\d+)px\)/);
428
+ if (maxWidthMatch) {
429
+ return dims.width <= parseInt(maxWidthMatch[1], 10);
430
+ }
431
+
432
+ const minHeightMatch = query.match(/\(min-height:\s*(\d+)px\)/);
433
+ if (minHeightMatch) {
434
+ return dims.height >= parseInt(minHeightMatch[1], 10);
435
+ }
436
+
437
+ const maxHeightMatch = query.match(/\(max-height:\s*(\d+)px\)/);
438
+ if (maxHeightMatch) {
439
+ return dims.height <= parseInt(maxHeightMatch[1], 10);
440
+ }
441
+
442
+ const orientationMatch = query.match(/\(orientation:\s*(portrait|landscape)\)/);
443
+ if (orientationMatch) {
444
+ const isPortrait = dims.height > dims.width;
445
+ return orientationMatch[1] === 'portrait' ? isPortrait : !isPortrait;
446
+ }
447
+
448
+ const colorSchemeMatch = query.match(/\(prefers-color-scheme:\s*(dark|light)\)/);
449
+ if (colorSchemeMatch) {
450
+ return appearanceState.colorScheme === colorSchemeMatch[1];
451
+ }
452
+
453
+ const reducedMotionMatch = query.match(/\(prefers-reduced-motion:\s*(reduce|no-preference)\)/);
454
+ if (reducedMotionMatch) {
455
+ return reducedMotionMatch[1] === 'reduce'
456
+ ? appearanceState.reducedMotion
457
+ : !appearanceState.reducedMotion;
458
+ }
459
+
460
+ // Unknown query - return false
461
+ return false;
462
+ }
463
+
464
+ /** @deprecated Use addEventListener('change', ...) instead */
465
+ addListener(callback: (this: MediaQueryList, ev: MediaQueryListEvent) => any): void {
466
+ this.addEventListener('change', callback as EventListener);
467
+ }
468
+
469
+ /** @deprecated Use removeEventListener('change', ...) instead */
470
+ removeListener(callback: (this: MediaQueryList, ev: MediaQueryListEvent) => any): void {
471
+ this.removeEventListener('change', callback as EventListener);
472
+ }
473
+
474
+ _syncFromAppearance(): void {
475
+ const nextMatches = this._evaluate(this.media);
476
+ if (nextMatches === this._matches) {
477
+ return;
478
+ }
479
+
480
+ this._matches = nextMatches;
481
+ const event = new MediaQueryListEvent('change', {
482
+ matches: nextMatches,
483
+ media: this.media,
484
+ });
485
+ this.dispatchEvent(event);
486
+ this.onchange?.call(this, event);
487
+ }
488
+ }
489
+
490
+ /**
491
+ * MediaQueryListEvent for matchMedia change events
492
+ */
493
+ export class MediaQueryListEvent extends Event {
494
+ readonly matches: boolean;
495
+ readonly media: string;
496
+
497
+ constructor(type: string, init?: { matches?: boolean; media?: string }) {
498
+ super(type);
499
+ this.matches = init?.matches ?? false;
500
+ this.media = init?.media ?? '';
501
+ }
502
+ }
503
+
504
+ /**
505
+ * Window object implementation
506
+ */
507
+ class Window extends EventTarget {
508
+ // Self-references
509
+ get window(): Window { return this; }
510
+ get self(): Window { return this; }
511
+ get globalThis(): typeof globalThis { return globalThis; }
512
+ get frames(): Window { return this; }
513
+ get parent(): Window { return this; }
514
+ get top(): Window { return this; }
515
+
516
+ // Length (number of frames - always 0 in native)
517
+ readonly length: number = 0;
518
+
519
+ // Name (empty in native)
520
+ name: string = '';
521
+
522
+ // Opener (null in native - no popup windows)
523
+ readonly opener: null = null;
524
+
525
+ // Frame element (null in native)
526
+ readonly frameElement: null = null;
527
+
528
+ // Closed status
529
+ readonly closed: boolean = false;
530
+
531
+ // Screen info
532
+ readonly screen: ScreenInfo = createScreen();
533
+
534
+ // Location
535
+ readonly location: LocationInfo = createLocation();
536
+
537
+ // Device pixel ratio
538
+ get devicePixelRatio(): number {
539
+ return getScreenDimensions().scale;
540
+ }
541
+
542
+ // Viewport dimensions (same as screen in native fullscreen apps)
543
+ get innerWidth(): number {
544
+ return getScreenDimensions().width;
545
+ }
546
+
547
+ get innerHeight(): number {
548
+ return getScreenDimensions().height;
549
+ }
550
+
551
+ get outerWidth(): number {
552
+ return getScreenDimensions().width;
553
+ }
554
+
555
+ get outerHeight(): number {
556
+ return getScreenDimensions().height;
557
+ }
558
+
559
+ // Scroll position (always 0 in native - no document scrolling)
560
+ get scrollX(): number { return 0; }
561
+ get scrollY(): number { return 0; }
562
+ get pageXOffset(): number { return 0; }
563
+ get pageYOffset(): number { return 0; }
564
+
565
+ // Screen position (always 0)
566
+ get screenX(): number { return 0; }
567
+ get screenY(): number { return 0; }
568
+ get screenLeft(): number { return 0; }
569
+ get screenTop(): number { return 0; }
570
+
571
+ // Visual viewport (same as dimensions)
572
+ get visualViewport(): { width: number; height: number; scale: number; offsetLeft: number; offsetTop: number } {
573
+ const dims = getScreenDimensions();
574
+ return {
575
+ width: dims.width,
576
+ height: dims.height,
577
+ scale: 1,
578
+ offsetLeft: 0,
579
+ offsetTop: 0,
580
+ };
581
+ }
582
+
583
+ // Is secure context (always true in native)
584
+ get isSecureContext(): boolean {
585
+ return true;
586
+ }
587
+
588
+ // Origin
589
+ get origin(): string {
590
+ return 'exact://app';
591
+ }
592
+
593
+ // Methods
594
+
595
+ /**
596
+ * Match media query
597
+ */
598
+ matchMedia(query: string): MediaQueryList {
599
+ return new MediaQueryList(query);
600
+ }
601
+
602
+ /**
603
+ * Get computed style (stub - no DOM)
604
+ */
605
+ getComputedStyle(_element: unknown, _pseudoElt?: string | null): Record<string, string> {
606
+ console.warn('getComputedStyle() is not supported in Ibex runtime (no DOM)');
607
+ return new Proxy({}, {
608
+ get: () => '',
609
+ });
610
+ }
611
+
612
+ /**
613
+ * Get selection (stub - no DOM)
614
+ */
615
+ getSelection(): null {
616
+ return null;
617
+ }
618
+
619
+ /**
620
+ * Scroll methods (no-op in native)
621
+ */
622
+ scroll(_x?: number | ScrollToOptions, _y?: number): void {
623
+ // No-op - no document to scroll
624
+ }
625
+
626
+ scrollTo(_x?: number | ScrollToOptions, _y?: number): void {
627
+ // No-op
628
+ }
629
+
630
+ scrollBy(_x?: number | ScrollToOptions, _y?: number): void {
631
+ // No-op
632
+ }
633
+
634
+ /**
635
+ * Focus/blur (no-op in native)
636
+ */
637
+ focus(): void {
638
+ // No-op
639
+ }
640
+
641
+ blur(): void {
642
+ // No-op
643
+ }
644
+
645
+ /**
646
+ * Print (not supported)
647
+ */
648
+ print(): void {
649
+ console.warn('print() is not supported in Ibex runtime');
650
+ }
651
+
652
+ /**
653
+ * Stop (no-op)
654
+ */
655
+ stop(): void {
656
+ // No-op
657
+ }
658
+
659
+ /**
660
+ * Open (not supported - no popup windows)
661
+ */
662
+ open(_url?: string, _target?: string, _features?: string): null {
663
+ console.warn('window.open() is not supported in Ibex runtime');
664
+ return null;
665
+ }
666
+
667
+ /**
668
+ * Close (not supported)
669
+ */
670
+ close(): void {
671
+ console.warn('window.close() is not supported in Ibex runtime');
672
+ }
673
+
674
+ /**
675
+ * Alert dialog
676
+ */
677
+ alert(message?: any): void {
678
+ const nativeResult = callNativeDialog('alert', String(message ?? ''));
679
+ if (nativeResult !== undefined) {
680
+ return;
681
+ }
682
+ console.log('[Alert]', String(message ?? ''));
683
+ }
684
+
685
+ /**
686
+ * Confirm dialog
687
+ */
688
+ confirm(message?: string): boolean {
689
+ const nativeResult = callNativeDialog('confirm', String(message ?? ''));
690
+ if (nativeResult !== undefined) {
691
+ return nativeResult === 'true';
692
+ }
693
+ console.warn('confirm() is not fully supported in Ibex runtime');
694
+ return false;
695
+ }
696
+
697
+ /**
698
+ * Prompt dialog
699
+ */
700
+ prompt(message?: string, defaultValue?: string): string | null {
701
+ const nativeResult = callNativeDialog(
702
+ 'prompt',
703
+ String(message ?? ''),
704
+ String(defaultValue ?? ''),
705
+ );
706
+ if (nativeResult !== undefined) {
707
+ return nativeResult;
708
+ }
709
+ console.warn('prompt() is not fully supported in Ibex runtime');
710
+ return null;
711
+ }
712
+
713
+ /**
714
+ * Post message (for compatibility)
715
+ */
716
+ postMessage(message: any, _targetOrigin?: string, _transfer?: Transferable[]): void {
717
+ // Dispatch message event to self
718
+ const event = new MessageEvent('message', {
719
+ data: message,
720
+ origin: this.origin,
721
+ source: this,
722
+ });
723
+ this.dispatchEvent(event);
724
+ }
725
+
726
+ /**
727
+ * Request animation frame - forward to global
728
+ */
729
+ requestAnimationFrame(callback: FrameRequestCallback): number {
730
+ return (globalThis as any).requestAnimationFrame(callback);
731
+ }
732
+
733
+ /**
734
+ * Cancel animation frame - forward to global
735
+ */
736
+ cancelAnimationFrame(handle: number): void {
737
+ return (globalThis as any).cancelAnimationFrame(handle);
738
+ }
739
+
740
+ /**
741
+ * Request idle callback - forward to global
742
+ */
743
+ requestIdleCallback(callback: IdleRequestCallback, options?: IdleRequestOptions): number {
744
+ return (globalThis as any).requestIdleCallback(callback, options);
745
+ }
746
+
747
+ /**
748
+ * Cancel idle callback - forward to global
749
+ */
750
+ cancelIdleCallback(handle: number): void {
751
+ return (globalThis as any).cancelIdleCallback(handle);
752
+ }
753
+
754
+ /**
755
+ * Set timeout - forward to global
756
+ */
757
+ setTimeout(handler: TimerHandler, timeout?: number, ...args: any[]): number {
758
+ return (globalThis as any).setTimeout(handler, timeout, ...args);
759
+ }
760
+
761
+ /**
762
+ * Clear timeout - forward to global
763
+ */
764
+ clearTimeout(id?: number): void {
765
+ return (globalThis as any).clearTimeout(id);
766
+ }
767
+
768
+ /**
769
+ * Set interval - forward to global
770
+ */
771
+ setInterval(handler: TimerHandler, timeout?: number, ...args: any[]): number {
772
+ return (globalThis as any).setInterval(handler, timeout, ...args);
773
+ }
774
+
775
+ /**
776
+ * Clear interval - forward to global
777
+ */
778
+ clearInterval(id?: number): void {
779
+ return (globalThis as any).clearInterval(id);
780
+ }
781
+
782
+ /**
783
+ * Queue microtask - forward to global
784
+ */
785
+ queueMicrotask(callback: VoidFunction): void {
786
+ return (globalThis as any).queueMicrotask(callback);
787
+ }
788
+
789
+ /**
790
+ * Create image bitmap (not supported - no DOM)
791
+ */
792
+ createImageBitmap(..._args: any[]): Promise<never> {
793
+ return Promise.reject(new Error('createImageBitmap() is not supported in Ibex runtime'));
794
+ }
795
+
796
+ /**
797
+ * Fetch - forward to global
798
+ */
799
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
800
+ return (globalThis as any).fetch(input, init);
801
+ }
802
+
803
+ /**
804
+ * btoa - forward to global
805
+ */
806
+ btoa(data: string): string {
807
+ return (globalThis as any).btoa(data);
808
+ }
809
+
810
+ /**
811
+ * atob - forward to global
812
+ */
813
+ atob(data: string): string {
814
+ return (globalThis as any).atob(data);
815
+ }
816
+
817
+ /**
818
+ * structuredClone - forward to global
819
+ */
820
+ structuredClone<T>(value: T, options?: StructuredSerializeOptions): T {
821
+ return (globalThis as any).structuredClone(value, options);
822
+ }
823
+
824
+ // String tag
825
+ get [Symbol.toStringTag](): string {
826
+ return 'Window';
827
+ }
828
+ }
829
+
830
+ // Type definitions for callbacks
831
+ type TimerHandler = string | Function;
832
+ type FrameRequestCallback = (time: DOMHighResTimeStamp) => void;
833
+ type IdleRequestCallback = (deadline: IdleDeadline) => void;
834
+ interface IdleRequestOptions {
835
+ timeout?: number;
836
+ }
837
+ interface IdleDeadline {
838
+ didTimeout: boolean;
839
+ timeRemaining(): DOMHighResTimeStamp;
840
+ }
841
+ type DOMHighResTimeStamp = number;
842
+
843
+ // Create window instance directly
844
+ // Note: This runs at module load time, but the circular dependency
845
+ // issue was fixed by having Navigator use inline detection functions
846
+ export const window = new Window();
847
+
848
+ globalThis.__exactWindowNotifyMediaChange = (next) => {
849
+ updateAppearanceState(next);
850
+ };
851
+
852
+ globalThis.__exactWindowNotifyResize = () => {
853
+ syncMediaQueries();
854
+ dispatchWindowEvent(new Event('resize'));
855
+ dispatchWindowEvent(new Event('orientationchange'));
856
+ };
857
+
858
+ globalThis.__exactAndroidDispatchPlatformEvent = (event, state) => {
859
+ applyAndroidPlatformState(normalizeAndroidPlatformEvent(event), state);
860
+ };
861
+
862
+ if (typeof globalThis.__exactAndroidGetPlatformState === 'function') {
863
+ applyAndroidPlatformState({ type: 'initial' }, globalThis.__exactAndroidGetPlatformState());
864
+ }
865
+ if (typeof globalThis.__exactAndroidDrainPlatformEvents === 'function') {
866
+ globalThis.__exactAndroidDrainPlatformEvents();
867
+ }
868
+
869
+ // Also export the class for type checking
870
+ export { Window };
871
+
872
+ export default window;