@steipete/oracle 0.7.6 → 0.8.1

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.
@@ -0,0 +1,138 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ const MAX_DATA_TRANSFER_BYTES = 20 * 1024 * 1024;
4
+ export async function transferAttachmentViaDataTransfer(runtime, attachment, selector) {
5
+ const fileContent = await readFile(attachment.path);
6
+ if (fileContent.length > MAX_DATA_TRANSFER_BYTES) {
7
+ throw new Error(`Attachment ${path.basename(attachment.path)} is too large for data transfer (${fileContent.length} bytes). Maximum size is ${MAX_DATA_TRANSFER_BYTES} bytes.`);
8
+ }
9
+ const base64Content = fileContent.toString('base64');
10
+ const fileName = path.basename(attachment.path);
11
+ const mimeType = guessMimeType(fileName);
12
+ const expression = `(() => {
13
+ if (!('File' in window) || !('Blob' in window) || !('DataTransfer' in window) || typeof atob !== 'function') {
14
+ return { success: false, error: 'Required file APIs are not available in this browser' };
15
+ }
16
+
17
+ const fileInput = document.querySelector(${JSON.stringify(selector)});
18
+ if (!fileInput) {
19
+ return { success: false, error: 'File input not found' };
20
+ }
21
+ if (!(fileInput instanceof HTMLInputElement) || fileInput.type !== 'file') {
22
+ return { success: false, error: 'Found element is not a file input' };
23
+ }
24
+
25
+ const base64Data = ${JSON.stringify(base64Content)};
26
+ const binaryString = atob(base64Data);
27
+ const bytes = new Uint8Array(binaryString.length);
28
+ for (let i = 0; i < binaryString.length; i++) {
29
+ bytes[i] = binaryString.charCodeAt(i);
30
+ }
31
+ const blob = new Blob([bytes], { type: ${JSON.stringify(mimeType)} });
32
+
33
+ const file = new File([blob], ${JSON.stringify(fileName)}, {
34
+ type: ${JSON.stringify(mimeType)},
35
+ lastModified: Date.now(),
36
+ });
37
+
38
+ const dataTransfer = new DataTransfer();
39
+ dataTransfer.items.add(file);
40
+ let assigned = false;
41
+
42
+ const proto = Object.getPrototypeOf(fileInput);
43
+ const descriptor = proto ? Object.getOwnPropertyDescriptor(proto, 'files') : null;
44
+ if (descriptor?.set) {
45
+ try {
46
+ descriptor.set.call(fileInput, dataTransfer.files);
47
+ assigned = true;
48
+ } catch {
49
+ assigned = false;
50
+ }
51
+ }
52
+ if (!assigned) {
53
+ try {
54
+ Object.defineProperty(fileInput, 'files', {
55
+ configurable: true,
56
+ get: () => dataTransfer.files,
57
+ });
58
+ assigned = true;
59
+ } catch {
60
+ assigned = false;
61
+ }
62
+ }
63
+ if (!assigned) {
64
+ try {
65
+ fileInput.files = dataTransfer.files;
66
+ assigned = true;
67
+ } catch {
68
+ assigned = false;
69
+ }
70
+ }
71
+ if (!assigned) {
72
+ return { success: false, error: 'Unable to assign FileList to input' };
73
+ }
74
+
75
+ fileInput.dispatchEvent(new Event('change', { bubbles: true }));
76
+ return { success: true, fileName: file.name, size: file.size };
77
+ })()`;
78
+ const evalResult = await runtime.evaluate({ expression, returnByValue: true });
79
+ if (evalResult.exceptionDetails) {
80
+ const description = evalResult.exceptionDetails.text ?? 'JS evaluation failed';
81
+ throw new Error(`Failed to transfer file to browser: ${description}`);
82
+ }
83
+ if (!evalResult.result || typeof evalResult.result.value !== 'object' || evalResult.result.value == null) {
84
+ throw new Error('Failed to transfer file to browser: unexpected evaluation result');
85
+ }
86
+ const uploadResult = evalResult.result.value;
87
+ if (!uploadResult.success) {
88
+ throw new Error(`Failed to transfer file to browser: ${uploadResult.error || 'Unknown error'}`);
89
+ }
90
+ return {
91
+ fileName: uploadResult.fileName ?? fileName,
92
+ size: typeof uploadResult.size === 'number' ? uploadResult.size : fileContent.length,
93
+ };
94
+ }
95
+ export function guessMimeType(fileName) {
96
+ const ext = path.extname(fileName).toLowerCase();
97
+ const mimeTypes = {
98
+ '.txt': 'text/plain',
99
+ '.md': 'text/markdown',
100
+ '.csv': 'text/csv',
101
+ '.json': 'application/json',
102
+ '.js': 'text/javascript',
103
+ '.ts': 'text/typescript',
104
+ '.jsx': 'text/javascript',
105
+ '.tsx': 'text/typescript',
106
+ '.py': 'text/x-python',
107
+ '.java': 'text/x-java',
108
+ '.c': 'text/x-c',
109
+ '.cpp': 'text/x-c++',
110
+ '.h': 'text/x-c',
111
+ '.hpp': 'text/x-c++',
112
+ '.sh': 'text/x-sh',
113
+ '.bash': 'text/x-sh',
114
+ '.html': 'text/html',
115
+ '.css': 'text/css',
116
+ '.xml': 'text/xml',
117
+ '.yaml': 'text/yaml',
118
+ '.yml': 'text/yaml',
119
+ '.pdf': 'application/pdf',
120
+ '.doc': 'application/msword',
121
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
122
+ '.xls': 'application/vnd.ms-excel',
123
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
124
+ '.ppt': 'application/vnd.ms-powerpoint',
125
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
126
+ '.png': 'image/png',
127
+ '.jpg': 'image/jpeg',
128
+ '.jpeg': 'image/jpeg',
129
+ '.gif': 'image/gif',
130
+ '.svg': 'image/svg+xml',
131
+ '.webp': 'image/webp',
132
+ '.zip': 'application/zip',
133
+ '.tar': 'application/x-tar',
134
+ '.gz': 'application/gzip',
135
+ '.7z': 'application/x-7z-compressed',
136
+ };
137
+ return mimeTypes[ext] || 'application/octet-stream';
138
+ }