@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,194 @@
1
+ /**
2
+ * Camera frame processor worker.
3
+ *
4
+ * The worker evaluates the user-supplied processor function source once and
5
+ * then executes it for each frame with a minimal `Frame` facade that matches
6
+ * the RFC shape closely enough for web camera labs and automated tests.
7
+ */
8
+
9
+ type CameraPreviewMode = "native" | "processed";
10
+ type CameraResizeMode = "cover" | "contain" | "stretch";
11
+
12
+ interface FrameMessage {
13
+ type: "frame";
14
+ id: number;
15
+ inputBuffer: ArrayBuffer;
16
+ width: number;
17
+ height: number;
18
+ timestamp: number;
19
+ previewMode: CameraPreviewMode;
20
+ resizeMode: CameraResizeMode;
21
+ mirror: boolean;
22
+ targetWidth: number;
23
+ targetHeight: number;
24
+ isCaptureFrame: boolean;
25
+ }
26
+
27
+ let processorFn: ((frame: unknown) => unknown) | null = null;
28
+ let currentCallIndex = 0;
29
+ const fpsMemory = new Map<string, { timestamp: number; value: unknown }>();
30
+
31
+ function computeDrawRect(
32
+ sourceWidth: number,
33
+ sourceHeight: number,
34
+ targetWidth: number,
35
+ targetHeight: number,
36
+ resizeMode: CameraResizeMode,
37
+ ) {
38
+ if (
39
+ resizeMode === "stretch" ||
40
+ sourceWidth <= 0 ||
41
+ sourceHeight <= 0 ||
42
+ targetWidth <= 0 ||
43
+ targetHeight <= 0
44
+ ) {
45
+ return { x: 0, y: 0, width: targetWidth, height: targetHeight };
46
+ }
47
+
48
+ const sourceAspect = sourceWidth / sourceHeight;
49
+ const targetAspect = targetWidth / targetHeight;
50
+ const cover = resizeMode === "cover";
51
+ const sourceIsWider = sourceAspect > targetAspect;
52
+
53
+ if (cover ? sourceIsWider : !sourceIsWider) {
54
+ const height = targetHeight;
55
+ const width = height * sourceAspect;
56
+ return {
57
+ x: (targetWidth - width) / 2,
58
+ y: 0,
59
+ width,
60
+ height,
61
+ };
62
+ }
63
+
64
+ const width = targetWidth;
65
+ const height = width / sourceAspect;
66
+ return {
67
+ x: 0,
68
+ y: (targetHeight - height) / 2,
69
+ width,
70
+ height,
71
+ };
72
+ }
73
+
74
+ function sensorToViewRect(
75
+ rect: { x: number; y: number; width: number; height: number },
76
+ message: FrameMessage,
77
+ ) {
78
+ const drawRect = computeDrawRect(
79
+ message.width,
80
+ message.height,
81
+ message.targetWidth,
82
+ message.targetHeight,
83
+ message.resizeMode,
84
+ );
85
+ const mapped = {
86
+ x: drawRect.x + (rect.x / message.width) * drawRect.width,
87
+ y: drawRect.y + (rect.y / message.height) * drawRect.height,
88
+ width: (rect.width / message.width) * drawRect.width,
89
+ height: (rect.height / message.height) * drawRect.height,
90
+ };
91
+
92
+ if (message.mirror) {
93
+ mapped.x = message.targetWidth - (mapped.x + mapped.width);
94
+ }
95
+
96
+ return {
97
+ x: mapped.x / message.targetWidth,
98
+ y: mapped.y / message.targetHeight,
99
+ width: mapped.width / message.targetWidth,
100
+ height: mapped.height / message.targetHeight,
101
+ };
102
+ }
103
+
104
+ function runAtTargetFps(targetFps: number, fn: () => unknown): unknown {
105
+ const key = `${targetFps}:${currentCallIndex++}`;
106
+ const last = fpsMemory.get(key);
107
+ const frameBudget = 1000 / Math.max(1, targetFps);
108
+ const now = Date.now();
109
+ if (last && now - last.timestamp < frameBudget) {
110
+ return last.value;
111
+ }
112
+ const value = fn();
113
+ fpsMemory.set(key, { timestamp: now, value });
114
+ return value;
115
+ }
116
+
117
+ function installRuntimeGlobals(): void {
118
+ (globalThis as typeof globalThis & {
119
+ runAtTargetFps?: typeof runAtTargetFps;
120
+ }).runAtTargetFps = runAtTargetFps;
121
+ }
122
+
123
+ installRuntimeGlobals();
124
+
125
+ self.onmessage = (event: MessageEvent) => {
126
+ const payload = event.data as
127
+ | { type: "init"; source: string }
128
+ | FrameMessage;
129
+
130
+ if (payload.type === "init") {
131
+ try {
132
+ // We intentionally evaluate the source in the worker so that processor
133
+ // execution stays off the main thread. The source comes from application
134
+ // code running in the same bundle, not from remote input.
135
+ processorFn = (0, eval)(`(${payload.source})`) as (frame: unknown) => unknown;
136
+ } catch (error) {
137
+ console.error("[CameraWorker] Failed to compile processor:", error);
138
+ processorFn = null;
139
+ }
140
+ return;
141
+ }
142
+
143
+ if (!processorFn) {
144
+ return;
145
+ }
146
+
147
+ try {
148
+ currentCallIndex = 0;
149
+ const input = payload.inputBuffer;
150
+ const writable =
151
+ payload.previewMode === "processed" || payload.isCaptureFrame
152
+ ? input.slice(0)
153
+ : null;
154
+
155
+ const frame = {
156
+ width: payload.width,
157
+ height: payload.height,
158
+ timestamp: payload.timestamp,
159
+ orientation: payload.width >= payload.height ? "landscape-left" : "portrait",
160
+ isCaptureFrame: payload.isCaptureFrame,
161
+ pixelFormat: "bgra",
162
+ rawPixelFormat: "bgra",
163
+ toArrayBuffer() {
164
+ return input;
165
+ },
166
+ toWritableBuffer() {
167
+ return writable;
168
+ },
169
+ sensorToView(rect: { x: number; y: number; width: number; height: number }) {
170
+ return sensorToViewRect(rect, payload);
171
+ },
172
+ getDepthData() {
173
+ return null;
174
+ },
175
+ };
176
+
177
+ const result = processorFn(frame);
178
+ self.postMessage(
179
+ {
180
+ type: "result",
181
+ id: payload.id,
182
+ result,
183
+ outputBuffer: writable ?? undefined,
184
+ },
185
+ writable ? [writable] : [],
186
+ );
187
+ } catch (error) {
188
+ self.postMessage({
189
+ type: "error",
190
+ id: payload.id,
191
+ message: error instanceof Error ? error.message : String(error),
192
+ });
193
+ }
194
+ };
@@ -0,0 +1,195 @@
1
+ export interface SceneRectLike {
2
+ x: number;
3
+ y: number;
4
+ width: number;
5
+ height: number;
6
+ }
7
+
8
+ export interface SceneBarcodeLike {
9
+ type: string;
10
+ data: string;
11
+ bounds: SceneRectLike;
12
+ }
13
+
14
+ export interface SceneFaceLike {
15
+ bounds: SceneRectLike;
16
+ }
17
+
18
+ export interface SceneTextLike {
19
+ value: string;
20
+ bounds: SceneRectLike;
21
+ }
22
+
23
+ export interface SceneObjectLike {
24
+ label: string;
25
+ bounds: SceneRectLike;
26
+ confidence: number;
27
+ }
28
+
29
+ function clamp01(value: number): number {
30
+ return Math.min(1, Math.max(0, value));
31
+ }
32
+
33
+ function clampSceneRect(rect: SceneRectLike): SceneRectLike {
34
+ const x = clamp01(rect.x);
35
+ const y = clamp01(rect.y);
36
+ const right = clamp01(rect.x + Math.max(0, rect.width));
37
+ const bottom = clamp01(rect.y + Math.max(0, rect.height));
38
+ return {
39
+ x,
40
+ y,
41
+ width: Math.max(0, right - x),
42
+ height: Math.max(0, bottom - y),
43
+ };
44
+ }
45
+
46
+ function sceneRectArea(rect: SceneRectLike): number {
47
+ return Math.max(0, rect.width) * Math.max(0, rect.height);
48
+ }
49
+
50
+ function unionSceneRects(rects: SceneRectLike[]): SceneRectLike {
51
+ const clamped = rects.map(clampSceneRect);
52
+ const minX = Math.min(...clamped.map((rect) => rect.x));
53
+ const minY = Math.min(...clamped.map((rect) => rect.y));
54
+ const maxX = Math.max(...clamped.map((rect) => rect.x + rect.width));
55
+ const maxY = Math.max(...clamped.map((rect) => rect.y + rect.height));
56
+ return clampSceneRect({
57
+ x: minX,
58
+ y: minY,
59
+ width: maxX - minX,
60
+ height: maxY - minY,
61
+ });
62
+ }
63
+
64
+ function sceneRectIntersectionArea(a: SceneRectLike, b: SceneRectLike): number {
65
+ const left = Math.max(a.x, b.x);
66
+ const top = Math.max(a.y, b.y);
67
+ const right = Math.min(a.x + a.width, b.x + b.width);
68
+ const bottom = Math.min(a.y + a.height, b.y + b.height);
69
+ return Math.max(0, right - left) * Math.max(0, bottom - top);
70
+ }
71
+
72
+ function sceneRectIou(a: SceneRectLike, b: SceneRectLike): number {
73
+ const intersection = sceneRectIntersectionArea(a, b);
74
+ if (intersection === 0) {
75
+ return 0;
76
+ }
77
+ const union = sceneRectArea(a) + sceneRectArea(b) - intersection;
78
+ return union <= 0 ? 0 : intersection / union;
79
+ }
80
+
81
+ function sceneRectOverlapRatio(a: SceneRectLike, b: SceneRectLike): number {
82
+ const intersection = sceneRectIntersectionArea(a, b);
83
+ if (intersection === 0) {
84
+ return 0;
85
+ }
86
+ const smallerArea = Math.min(sceneRectArea(a), sceneRectArea(b));
87
+ return smallerArea <= 0 ? 0 : intersection / smallerArea;
88
+ }
89
+
90
+ function mergeSceneObjects(candidates: SceneObjectLike[]): SceneObjectLike[] {
91
+ const merged: SceneObjectLike[] = [];
92
+ // Normalize and sort first so downstream merges keep the highest-confidence
93
+ // label/bounds pair when multiple detectors describe the same real-world
94
+ // subject (for example OCR + rectangle detection both finding a document).
95
+ const sorted = candidates
96
+ .map((candidate) => ({
97
+ ...candidate,
98
+ bounds: clampSceneRect(candidate.bounds),
99
+ confidence: clamp01(candidate.confidence),
100
+ }))
101
+ .filter((candidate) => sceneRectArea(candidate.bounds) > 0)
102
+ .sort((left, right) => right.confidence - left.confidence);
103
+
104
+ for (const candidate of sorted) {
105
+ const existing = merged.find((entry) => {
106
+ if (entry.label !== candidate.label) {
107
+ return false;
108
+ }
109
+ return (
110
+ sceneRectIou(entry.bounds, candidate.bounds) >= 0.4 ||
111
+ sceneRectOverlapRatio(entry.bounds, candidate.bounds) >= 0.8
112
+ );
113
+ });
114
+ if (existing) {
115
+ existing.bounds = unionSceneRects([existing.bounds, candidate.bounds]);
116
+ existing.confidence = Math.max(existing.confidence, candidate.confidence);
117
+ continue;
118
+ }
119
+ merged.push(candidate);
120
+ }
121
+
122
+ return merged;
123
+ }
124
+
125
+ function buildDocumentCandidatesFromText(text: SceneTextLike[]): SceneObjectLike[] {
126
+ const candidates = text.filter((entry) => entry.value.trim().length > 0);
127
+ if (candidates.length === 0) {
128
+ return [];
129
+ }
130
+
131
+ const rects = candidates.map((entry) => clampSceneRect(entry.bounds));
132
+ const union = unionSceneRects(rects);
133
+ const totalArea = rects.reduce((sum, rect) => sum + sceneRectArea(rect), 0);
134
+ // Treat dense or large text blocks as a document-like region. Small, isolated
135
+ // snippets stay labeled as plain text so agent consumers can distinguish a
136
+ // button label from a full sheet of paper.
137
+ const looksLikeDocument =
138
+ candidates.length > 1 || union.width >= 0.35 || union.height >= 0.28 || totalArea >= 0.08;
139
+
140
+ if (looksLikeDocument) {
141
+ return [
142
+ {
143
+ label: "document",
144
+ bounds: union,
145
+ confidence: candidates.length > 1 ? 0.72 : 0.64,
146
+ },
147
+ ];
148
+ }
149
+
150
+ return candidates.map((entry) => ({
151
+ label: "text",
152
+ bounds: clampSceneRect(entry.bounds),
153
+ confidence: 0.58,
154
+ }));
155
+ }
156
+
157
+ export function deriveSceneObjectsFromSignals(input: {
158
+ barcodes?: SceneBarcodeLike[] | null;
159
+ faces?: SceneFaceLike[] | null;
160
+ text?: SceneTextLike[] | null;
161
+ documentBounds?: SceneRectLike[] | null;
162
+ }): SceneObjectLike[] {
163
+ const candidates: SceneObjectLike[] = [];
164
+
165
+ // The public `objects` surface is intentionally derived from the detector
166
+ // signals we already have. That keeps the API useful on platforms that lack
167
+ // a dedicated generic object detector while still reporting bounded subjects
168
+ // like people, documents, and barcodes in a stable way.
169
+ for (const barcode of input.barcodes ?? []) {
170
+ candidates.push({
171
+ label: "barcode",
172
+ bounds: barcode.bounds,
173
+ confidence: 0.94,
174
+ });
175
+ }
176
+
177
+ for (const face of input.faces ?? []) {
178
+ candidates.push({
179
+ label: "person",
180
+ bounds: face.bounds,
181
+ confidence: 0.86,
182
+ });
183
+ }
184
+
185
+ for (const bounds of input.documentBounds ?? []) {
186
+ candidates.push({
187
+ label: "document",
188
+ bounds,
189
+ confidence: 0.7,
190
+ });
191
+ }
192
+
193
+ candidates.push(...buildDocumentCandidatesFromText(input.text ?? []));
194
+ return mergeSceneObjects(candidates);
195
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Clipboard - Web Clipboard API implementation for Ibex runtime
3
+ *
4
+ * Provides async read/write access to the system clipboard.
5
+ * By default, stores data in memory. When native bridge functions
6
+ * (__exactClipboardRead / __exactClipboardWrite) are available,
7
+ * they are used for actual system clipboard access.
8
+ *
9
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Clipboard
10
+ * @see https://w3c.github.io/clipboard-apis/#clipboard-interface
11
+ */
12
+
13
+ import { ClipboardItem } from './ClipboardItem';
14
+
15
+ /**
16
+ * The Clipboard interface provides read and write access to the contents
17
+ * of the system clipboard, allowing cut, copy, and paste operations in
18
+ * web applications.
19
+ *
20
+ * This implementation falls back to in-memory storage when native clipboard
21
+ * access is not available. Native bridge functions can be plugged in via:
22
+ * - globalThis.__exactClipboardRead: () => string
23
+ * - globalThis.__exactClipboardWrite: (text: string) => void
24
+ */
25
+ export class Clipboard {
26
+ /** In-memory fallback for clipboard text. */
27
+ #text: string = '';
28
+
29
+ /** In-memory fallback for clipboard items. */
30
+ #items: ClipboardItem[] = [];
31
+
32
+ /**
33
+ * Read text from the clipboard.
34
+ *
35
+ * Uses the native bridge if available (__exactClipboardRead),
36
+ * otherwise returns from in-memory storage.
37
+ *
38
+ * @returns A Promise resolving to the clipboard text content.
39
+ */
40
+ async readText(): Promise<string> {
41
+ const g = globalThis as any;
42
+ if (typeof g.__exactClipboardRead === 'function') {
43
+ return String(g.__exactClipboardRead());
44
+ }
45
+ return this.#text;
46
+ }
47
+
48
+ /**
49
+ * Write text to the clipboard.
50
+ *
51
+ * Uses the native bridge if available (__exactClipboardWrite),
52
+ * otherwise stores in memory.
53
+ *
54
+ * @param text - The text to write to the clipboard.
55
+ */
56
+ async writeText(text: string): Promise<void> {
57
+ const g = globalThis as any;
58
+ if (typeof g.__exactClipboardWrite === 'function') {
59
+ g.__exactClipboardWrite(String(text));
60
+ }
61
+ this.#text = String(text);
62
+ // Also update the items to keep them in sync
63
+ this.#items = [
64
+ new ClipboardItem({ 'text/plain': text }),
65
+ ];
66
+ }
67
+
68
+ /**
69
+ * Read structured data from the clipboard as ClipboardItem[].
70
+ *
71
+ * For now, returns items from the in-memory store. If only text was
72
+ * written, wraps it as a ClipboardItem with 'text/plain' type.
73
+ *
74
+ * @returns A Promise resolving to an array of ClipboardItems.
75
+ */
76
+ async read(): Promise<ClipboardItem[]> {
77
+ // If native bridge is available, read text and wrap it
78
+ const g = globalThis as any;
79
+ if (typeof g.__exactClipboardRead === 'function') {
80
+ const text = String(g.__exactClipboardRead());
81
+ if (text) {
82
+ return [new ClipboardItem({ 'text/plain': text })];
83
+ }
84
+ return [];
85
+ }
86
+
87
+ // Return a copy of the stored items
88
+ return [...this.#items];
89
+ }
90
+
91
+ /**
92
+ * Write structured data to the clipboard.
93
+ *
94
+ * Stores the items in memory. If any item contains 'text/plain',
95
+ * also updates the text representation for readText() compatibility.
96
+ *
97
+ * @param items - An array of ClipboardItems to write.
98
+ */
99
+ async write(items: ClipboardItem[]): Promise<void> {
100
+ if (!Array.isArray(items)) {
101
+ throw new TypeError('Clipboard.write: argument must be an array of ClipboardItems');
102
+ }
103
+
104
+ this.#items = [...items];
105
+
106
+ // Sync text representation from the first text/plain item
107
+ for (const item of items) {
108
+ if (item.types.includes('text/plain')) {
109
+ try {
110
+ const blob = await item.getType('text/plain');
111
+ this.#text = await blob.text();
112
+
113
+ // Also write to native if available
114
+ const g = globalThis as any;
115
+ if (typeof g.__exactClipboardWrite === 'function') {
116
+ g.__exactClipboardWrite(this.#text);
117
+ }
118
+ } catch {
119
+ // Ignore errors extracting text
120
+ }
121
+ break;
122
+ }
123
+ }
124
+ }
125
+
126
+ get [Symbol.toStringTag](): string {
127
+ return 'Clipboard';
128
+ }
129
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * ClipboardItem - Web Clipboard API ClipboardItem implementation
3
+ *
4
+ * Represents a single item on the clipboard, potentially with multiple
5
+ * representations (e.g., text/plain and text/html for the same content).
6
+ *
7
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem
8
+ * @see https://w3c.github.io/clipboard-apis/#clipboarditem
9
+ */
10
+
11
+ /**
12
+ * A ClipboardItem represents data that can be placed on or read from
13
+ * the system clipboard. Each item can have multiple MIME-typed representations.
14
+ */
15
+ export class ClipboardItem {
16
+ readonly #items: Map<string, Blob | string | Promise<Blob | string>>;
17
+ readonly #types: readonly string[];
18
+
19
+ /**
20
+ * Create a new ClipboardItem.
21
+ *
22
+ * @param items - A record mapping MIME types to their data. Values can be
23
+ * Blob, string, or Promise resolving to either.
24
+ * @param options - Optional settings (presentationStyle, etc.) - reserved for future use.
25
+ */
26
+ constructor(
27
+ items: Record<string, Blob | string | Promise<Blob | string>>,
28
+ _options?: { presentationStyle?: 'unspecified' | 'inline' | 'attachment' },
29
+ ) {
30
+ if (!items || typeof items !== 'object') {
31
+ throw new TypeError('ClipboardItem constructor: items must be an object');
32
+ }
33
+
34
+ const keys = Object.keys(items);
35
+ if (keys.length === 0) {
36
+ throw new TypeError('ClipboardItem constructor: items must not be empty');
37
+ }
38
+
39
+ this.#items = new Map();
40
+ for (let i = 0; i < keys.length; i++) {
41
+ const type = keys[i];
42
+ this.#items.set(type, items[type]);
43
+ }
44
+ this.#types = Object.freeze([...keys]);
45
+ }
46
+
47
+ /**
48
+ * The MIME types available for this clipboard item.
49
+ */
50
+ get types(): readonly string[] {
51
+ return this.#types;
52
+ }
53
+
54
+ /**
55
+ * Retrieve the data for a specific MIME type as a Blob.
56
+ *
57
+ * @param type - The MIME type to retrieve.
58
+ * @returns A Promise resolving to a Blob containing the data.
59
+ * @throws DOMException with name "NotFoundError" if the type is not available.
60
+ */
61
+ async getType(type: string): Promise<Blob> {
62
+ const data = this.#items.get(type);
63
+ if (data === undefined) {
64
+ throw new DOMException(
65
+ `ClipboardItem.getType: type '${type}' not found`,
66
+ 'NotFoundError',
67
+ );
68
+ }
69
+
70
+ const resolved = await data;
71
+
72
+ if (typeof resolved === 'string') {
73
+ return new Blob([resolved], { type });
74
+ }
75
+
76
+ if (resolved instanceof Blob) {
77
+ return resolved;
78
+ }
79
+
80
+ throw new TypeError(
81
+ `ClipboardItem.getType: unexpected data type for '${type}'`,
82
+ );
83
+ }
84
+
85
+ get [Symbol.toStringTag](): string {
86
+ return 'ClipboardItem';
87
+ }
88
+ }
89
+
90
+ // DOMException polyfill for this module
91
+ class DOMException extends Error {
92
+ readonly code: number = 0;
93
+ constructor(message: string, name: string) {
94
+ super(message);
95
+ this.name = name;
96
+ }
97
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Web Clipboard API module exports
3
+ */
4
+
5
+ export { Clipboard } from './Clipboard';
6
+ export { ClipboardItem } from './ClipboardItem';
@@ -0,0 +1 @@
1
+ export { structuredClone, type StructuredSerializeOptions } from './structuredClone';