@autonomys/auto-dag-data 1.5.6 → 1.5.7

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/dist/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export * from './compression/index.js';
3
3
  export * from './encryption/index.js';
4
4
  export * from './ipld/index.js';
5
5
  export * from './metadata/index.js';
6
+ export * from './utils/file.js';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,qBAAqB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA"}
package/dist/index.js CHANGED
@@ -3,3 +3,4 @@ export * from './compression/index.js';
3
3
  export * from './encryption/index.js';
4
4
  export * from './ipld/index.js';
5
5
  export * from './metadata/index.js';
6
+ export * from './utils/file.js';
@@ -0,0 +1,15 @@
1
+ import { FileUploadOptions, OffchainMetadata } from '../metadata/index.js';
2
+ export type FileData = {
3
+ name: string;
4
+ rawData?: string;
5
+ dataArrayBuffer: ArrayBuffer;
6
+ isEncrypted: boolean;
7
+ uploadOptions: FileUploadOptions;
8
+ };
9
+ export declare const asyncFromStream: (stream: ReadableStream) => AsyncIterable<Buffer>;
10
+ export declare const detectFileType: (arrayBuffer: ArrayBuffer) => Promise<string>;
11
+ export declare const decryptFileData: (password: string, fileData: FileData) => Promise<FileData>;
12
+ export declare const canDisplayDirectly: (metadata: OffchainMetadata) => boolean;
13
+ export declare const needsContentParsing: (metadata: OffchainMetadata) => boolean;
14
+ export declare const processFileData: (fileData: FileData) => Promise<Blob>;
15
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/utils/file.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,sBAAsB,CAAA;AAE7B,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,eAAe,EAAE,WAAW,CAAA;IAC5B,WAAW,EAAE,OAAO,CAAA;IACpB,aAAa,EAAE,iBAAiB,CAAA;CACjC,CAAA;AAGD,eAAO,MAAM,eAAe,GAAI,QAAQ,cAAc,KAAG,aAAa,CAAC,MAAM,CAe5E,CAAA;AAED,eAAO,MAAM,cAAc,GAAU,aAAa,WAAW,KAAG,OAAO,CAAC,MAAM,CAkE7E,CAAA;AAGD,eAAO,MAAM,eAAe,GAAU,UAAU,MAAM,EAAE,UAAU,QAAQ,KAAG,OAAO,CAAC,QAAQ,CAiC5F,CAAA;AAGD,eAAO,MAAM,kBAAkB,GAAI,UAAU,gBAAgB,KAAG,OAmD/D,CAAA;AAGD,eAAO,MAAM,mBAAmB,GAAI,UAAU,gBAAgB,KAAG,OA+BhE,CAAA;AAED,eAAO,MAAM,eAAe,GAAU,UAAU,QAAQ,kBAIvD,CAAA"}
@@ -0,0 +1,248 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
11
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
12
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
13
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
14
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
15
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
16
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
17
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
18
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
19
+ function fulfill(value) { resume("next", value); }
20
+ function reject(value) { resume("throw", value); }
21
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
22
+ };
23
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
24
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
25
+ var m = o[Symbol.asyncIterator], i;
26
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
27
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
28
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
29
+ };
30
+ import { decompressFile } from '../compression/index.js';
31
+ import { decryptFile } from '../encryption/index.js';
32
+ import { CompressionAlgorithm, EncryptionAlgorithm, } from '../metadata/index.js';
33
+ // Helper to convert stream to async iterable
34
+ export const asyncFromStream = (stream) => {
35
+ const reader = stream.getReader();
36
+ return {
37
+ [Symbol.asyncIterator]() {
38
+ return __asyncGenerator(this, arguments, function* _a() {
39
+ try {
40
+ while (true) {
41
+ const { done, value } = yield __await(reader.read());
42
+ if (done)
43
+ break;
44
+ yield yield __await(Buffer.from(value));
45
+ }
46
+ }
47
+ finally {
48
+ reader.releaseLock();
49
+ }
50
+ });
51
+ },
52
+ };
53
+ };
54
+ export const detectFileType = (arrayBuffer) => __awaiter(void 0, void 0, void 0, function* () {
55
+ const bytes = [...new Uint8Array(arrayBuffer.slice(0, 4))]
56
+ .map((byte) => byte.toString(16).padStart(2, '0'))
57
+ .join('')
58
+ .toUpperCase();
59
+ // File type magic numbers and corresponding types
60
+ const magicNumbers = {
61
+ '89504E47': 'image/png',
62
+ FFD8FFE0: 'image/jpeg', // JPEG start of image marker
63
+ FFD8FFE1: 'image/jpeg', // JPEG EXIF
64
+ FFD8FFE2: 'image/jpeg', // JPEG EXIF
65
+ FFD8FFE3: 'image/jpeg', // JPEG EXIF
66
+ FFD8FFE8: 'image/jpeg', // JPEG SPIFF
67
+ FFD8FFDB: 'image/jpeg', // JPEG quantization table marker
68
+ FFD8FFEE: 'image/jpeg', // JPEG comment marker
69
+ '47494638': 'image/gif',
70
+ '25504446': 'application/pdf',
71
+ '504B0304': 'application/zip', // Also covers .docx, .xlsx, etc.
72
+ '1F8B08': 'application/gzip',
73
+ '504B34': 'application/jar',
74
+ '494433': 'audio/mp3',
75
+ '000001BA': 'video/mpeg',
76
+ '000001B3': 'video/mpeg',
77
+ '66747970': 'video/mp4', // Part of MP4 signature
78
+ '3C3F786D': 'image/svg+xml', // SVG XML declaration <?xml
79
+ '3C737667': 'image/svg+xml', // SVG starting with <svg
80
+ '252150532D': 'application/postscript', // EPS files start with %!PS-
81
+ '4D5A': 'application/exe', // Windows executable
82
+ CAFEBABE: 'application/java', // Java class file
83
+ D0CF11E0: 'application/msword', // Microsoft Office document
84
+ '377ABCAF271C': 'application/7z', // 7-Zip archive
85
+ '52617221': 'application/rar', // RAR archive
86
+ '424D': 'image/bmp', // Bitmap image
87
+ '49492A00': 'image/tiff', // TIFF image
88
+ '4D4D002A': 'image/tiff', // TIFF image
89
+ '1A45DFA3': 'video/webm', // WebM video
90
+ '00000100': 'image/x-icon', // ICO file
91
+ '4F676753': 'audio/ogg', // OGG audio
92
+ '52494646': 'audio/wav', // WAV audio
93
+ '2E524D46': 'audio/aiff', // AIFF audio
94
+ '00000020': 'video/quicktime', // QuickTime video
95
+ '3026B2758E66CF11': 'video/x-ms-wmv', // WMV video
96
+ '4D546864': 'audio/midi', // MIDI audio
97
+ '1F9D': 'application/tar-compressed', // TAR compressed file
98
+ '1FA0': 'application/tar-compressed', // TAR compressed file
99
+ '7573746172': 'application/tar', // TAR archive
100
+ '3C21444F43545950452068746D6C3E': 'text/html', // HTML document
101
+ '3C48544D4C3E': 'text/html', // HTML document
102
+ '3C3F786D6C20': 'application/xml', // XML document
103
+ '3C3F786D6C': 'application/xml', // XML document
104
+ '49443303': 'audio/mpeg', // MP3 audio
105
+ '38425053': 'application/psd', // Adobe Photoshop file
106
+ '7B5C727466': 'application/rtf', // RTF document
107
+ '3C21454E54495459': 'text/html', // HTML document
108
+ '4D5A9000': 'application/exe', // Windows executable
109
+ };
110
+ // Check the magic number against known file types
111
+ for (const [signature, type] of Object.entries(magicNumbers)) {
112
+ if (bytes.startsWith(signature)) {
113
+ return type;
114
+ }
115
+ }
116
+ return 'application/octet-stream'; // File type not recognized
117
+ });
118
+ // Enhanced decryption function that handles both decryption and decompression
119
+ export const decryptFileData = (password, fileData) => __awaiter(void 0, void 0, void 0, function* () {
120
+ var _a, e_1, _b, _c;
121
+ try {
122
+ const stream = new ReadableStream({
123
+ start(controller) {
124
+ controller.enqueue(fileData.dataArrayBuffer);
125
+ controller.close();
126
+ },
127
+ });
128
+ let iterable = asyncFromStream(stream);
129
+ iterable = decryptFile(iterable, password, {
130
+ algorithm: EncryptionAlgorithm.AES_256_GCM,
131
+ });
132
+ iterable = decompressFile(iterable, {
133
+ algorithm: CompressionAlgorithm.ZLIB,
134
+ });
135
+ const processedChunks = [];
136
+ try {
137
+ for (var _d = true, iterable_1 = __asyncValues(iterable), iterable_1_1; iterable_1_1 = yield iterable_1.next(), _a = iterable_1_1.done, !_a; _d = true) {
138
+ _c = iterable_1_1.value;
139
+ _d = false;
140
+ const chunk = _c;
141
+ processedChunks.push(chunk);
142
+ }
143
+ }
144
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
145
+ finally {
146
+ try {
147
+ if (!_d && !_a && (_b = iterable_1.return)) yield _b.call(iterable_1);
148
+ }
149
+ finally { if (e_1) throw e_1.error; }
150
+ }
151
+ const combined = new Uint8Array(processedChunks.reduce((acc, chunk) => acc + chunk.length, 0));
152
+ let offset = 0;
153
+ for (const chunk of processedChunks) {
154
+ combined.set(chunk, offset);
155
+ offset += chunk.length;
156
+ }
157
+ fileData.dataArrayBuffer = combined.buffer;
158
+ fileData.isEncrypted = false;
159
+ return fileData;
160
+ }
161
+ catch (error) {
162
+ throw new Error(error.message);
163
+ }
164
+ });
165
+ // Helper function to determine if a file can be displayed directly via URL
166
+ export const canDisplayDirectly = (metadata) => {
167
+ var _a, _b, _c, _d;
168
+ if ((_a = metadata.uploadOptions) === null || _a === void 0 ? void 0 : _a.encryption)
169
+ return false; // Encrypted files need decryption
170
+ const extension = ((_c = (_b = metadata.name) === null || _b === void 0 ? void 0 : _b.split('.').pop()) === null || _c === void 0 ? void 0 : _c.toLowerCase()) || '';
171
+ const mimeType = 'mimeType' in metadata ? ((_d = metadata.mimeType) === null || _d === void 0 ? void 0 : _d.toLowerCase()) || '' : '';
172
+ // Media files that can be displayed directly
173
+ const directDisplayTypes = [
174
+ // Images
175
+ 'image/',
176
+ // Videos
177
+ 'video/',
178
+ // Audio
179
+ 'audio/',
180
+ // PDFs
181
+ 'application/pdf',
182
+ ];
183
+ const directDisplayExtensions = [
184
+ // Images
185
+ 'jpg',
186
+ 'jpeg',
187
+ 'png',
188
+ 'gif',
189
+ 'svg',
190
+ 'webp',
191
+ 'bmp',
192
+ 'ico',
193
+ // Videos
194
+ 'mp4',
195
+ 'webm',
196
+ 'avi',
197
+ 'mov',
198
+ 'mkv',
199
+ 'flv',
200
+ 'wmv',
201
+ // Audio
202
+ 'mp3',
203
+ 'wav',
204
+ 'ogg',
205
+ 'flac',
206
+ 'm4a',
207
+ 'aac',
208
+ // PDFs
209
+ 'pdf',
210
+ ];
211
+ return (directDisplayTypes.some((type) => mimeType.startsWith(type)) ||
212
+ directDisplayExtensions.includes(extension));
213
+ };
214
+ // Helper function to determine if a file needs content parsing
215
+ export const needsContentParsing = (metadata) => {
216
+ var _a, _b, _c;
217
+ const extension = ((_b = (_a = metadata.name) === null || _a === void 0 ? void 0 : _a.split('.').pop()) === null || _b === void 0 ? void 0 : _b.toLowerCase()) || '';
218
+ const mimeType = 'mimeType' in metadata ? ((_c = metadata.mimeType) === null || _c === void 0 ? void 0 : _c.toLowerCase()) || '' : '';
219
+ // Text files and JSON files need content parsing
220
+ const textTypes = ['text/'];
221
+ const textExtensions = [
222
+ 'js',
223
+ 'jsx',
224
+ 'ts',
225
+ 'tsx',
226
+ 'html',
227
+ 'css',
228
+ 'py',
229
+ 'java',
230
+ 'rb',
231
+ 'go',
232
+ 'rust',
233
+ 'php',
234
+ 'txt',
235
+ 'md',
236
+ 'xml',
237
+ 'csv',
238
+ 'json',
239
+ ];
240
+ return (textTypes.some((type) => mimeType.startsWith(type)) ||
241
+ textExtensions.includes(extension) ||
242
+ mimeType === 'application/json');
243
+ };
244
+ export const processFileData = (fileData) => __awaiter(void 0, void 0, void 0, function* () {
245
+ const fileType = yield detectFileType(fileData.dataArrayBuffer);
246
+ const blob = new Blob([fileData.dataArrayBuffer], { type: fileType });
247
+ return blob;
248
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@autonomys/auto-dag-data",
3
3
  "packageManager": "yarn@4.7.0",
4
- "version": "1.5.6",
4
+ "version": "1.5.7",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
7
7
  "type": "module",
@@ -40,7 +40,7 @@
40
40
  "typescript": "^5.8.3"
41
41
  },
42
42
  "dependencies": {
43
- "@autonomys/asynchronous": "^1.5.6",
43
+ "@autonomys/asynchronous": "^1.5.7",
44
44
  "@ipld/dag-pb": "^4.1.3",
45
45
  "@peculiar/webcrypto": "^1.5.0",
46
46
  "@webbuf/blake3": "^3.0.26",
@@ -52,5 +52,5 @@
52
52
  "protons": "^7.6.0",
53
53
  "protons-runtime": "^5.5.0"
54
54
  },
55
- "gitHead": "08fd5fa885e2b0312f096ac7d1754289f51e920d"
55
+ "gitHead": "39e5b7764ef03b5e010bba41fdd716f9c412170c"
56
56
  }
package/src/index.ts CHANGED
@@ -3,3 +3,4 @@ export * from './compression/index.js'
3
3
  export * from './encryption/index.js'
4
4
  export * from './ipld/index.js'
5
5
  export * from './metadata/index.js'
6
+ export * from './utils/file.js'
@@ -0,0 +1,232 @@
1
+ import { decompressFile } from '../compression/index.js'
2
+ import { decryptFile } from '../encryption/index.js'
3
+ import {
4
+ CompressionAlgorithm,
5
+ EncryptionAlgorithm,
6
+ FileUploadOptions,
7
+ OffchainMetadata,
8
+ } from '../metadata/index.js'
9
+
10
+ export type FileData = {
11
+ name: string
12
+ rawData?: string
13
+ dataArrayBuffer: ArrayBuffer
14
+ isEncrypted: boolean
15
+ uploadOptions: FileUploadOptions
16
+ }
17
+
18
+ // Helper to convert stream to async iterable
19
+ export const asyncFromStream = (stream: ReadableStream): AsyncIterable<Buffer> => {
20
+ const reader = stream.getReader()
21
+ return {
22
+ async *[Symbol.asyncIterator]() {
23
+ try {
24
+ while (true) {
25
+ const { done, value } = await reader.read()
26
+ if (done) break
27
+ yield Buffer.from(value)
28
+ }
29
+ } finally {
30
+ reader.releaseLock()
31
+ }
32
+ },
33
+ }
34
+ }
35
+
36
+ export const detectFileType = async (arrayBuffer: ArrayBuffer): Promise<string> => {
37
+ const bytes = [...new Uint8Array(arrayBuffer.slice(0, 4))]
38
+ .map((byte) => byte.toString(16).padStart(2, '0'))
39
+ .join('')
40
+ .toUpperCase()
41
+
42
+ // File type magic numbers and corresponding types
43
+ const magicNumbers = {
44
+ '89504E47': 'image/png',
45
+ FFD8FFE0: 'image/jpeg', // JPEG start of image marker
46
+ FFD8FFE1: 'image/jpeg', // JPEG EXIF
47
+ FFD8FFE2: 'image/jpeg', // JPEG EXIF
48
+ FFD8FFE3: 'image/jpeg', // JPEG EXIF
49
+ FFD8FFE8: 'image/jpeg', // JPEG SPIFF
50
+ FFD8FFDB: 'image/jpeg', // JPEG quantization table marker
51
+ FFD8FFEE: 'image/jpeg', // JPEG comment marker
52
+ '47494638': 'image/gif',
53
+ '25504446': 'application/pdf',
54
+ '504B0304': 'application/zip', // Also covers .docx, .xlsx, etc.
55
+ '1F8B08': 'application/gzip',
56
+ '504B34': 'application/jar',
57
+ '494433': 'audio/mp3',
58
+ '000001BA': 'video/mpeg',
59
+ '000001B3': 'video/mpeg',
60
+ '66747970': 'video/mp4', // Part of MP4 signature
61
+ '3C3F786D': 'image/svg+xml', // SVG XML declaration <?xml
62
+ '3C737667': 'image/svg+xml', // SVG starting with <svg
63
+ '252150532D': 'application/postscript', // EPS files start with %!PS-
64
+ '4D5A': 'application/exe', // Windows executable
65
+ CAFEBABE: 'application/java', // Java class file
66
+ D0CF11E0: 'application/msword', // Microsoft Office document
67
+ '377ABCAF271C': 'application/7z', // 7-Zip archive
68
+ '52617221': 'application/rar', // RAR archive
69
+ '424D': 'image/bmp', // Bitmap image
70
+ '49492A00': 'image/tiff', // TIFF image
71
+ '4D4D002A': 'image/tiff', // TIFF image
72
+ '1A45DFA3': 'video/webm', // WebM video
73
+ '00000100': 'image/x-icon', // ICO file
74
+ '4F676753': 'audio/ogg', // OGG audio
75
+ '52494646': 'audio/wav', // WAV audio
76
+ '2E524D46': 'audio/aiff', // AIFF audio
77
+ '00000020': 'video/quicktime', // QuickTime video
78
+ '3026B2758E66CF11': 'video/x-ms-wmv', // WMV video
79
+ '4D546864': 'audio/midi', // MIDI audio
80
+ '1F9D': 'application/tar-compressed', // TAR compressed file
81
+ '1FA0': 'application/tar-compressed', // TAR compressed file
82
+ '7573746172': 'application/tar', // TAR archive
83
+ '3C21444F43545950452068746D6C3E': 'text/html', // HTML document
84
+ '3C48544D4C3E': 'text/html', // HTML document
85
+ '3C3F786D6C20': 'application/xml', // XML document
86
+ '3C3F786D6C': 'application/xml', // XML document
87
+ '49443303': 'audio/mpeg', // MP3 audio
88
+ '38425053': 'application/psd', // Adobe Photoshop file
89
+ '7B5C727466': 'application/rtf', // RTF document
90
+ '3C21454E54495459': 'text/html', // HTML document
91
+ '4D5A9000': 'application/exe', // Windows executable
92
+ }
93
+
94
+ // Check the magic number against known file types
95
+ for (const [signature, type] of Object.entries(magicNumbers)) {
96
+ if (bytes.startsWith(signature)) {
97
+ return type
98
+ }
99
+ }
100
+
101
+ return 'application/octet-stream' // File type not recognized
102
+ }
103
+
104
+ // Enhanced decryption function that handles both decryption and decompression
105
+ export const decryptFileData = async (password: string, fileData: FileData): Promise<FileData> => {
106
+ try {
107
+ const stream = new ReadableStream({
108
+ start(controller) {
109
+ controller.enqueue(fileData.dataArrayBuffer)
110
+ controller.close()
111
+ },
112
+ })
113
+
114
+ let iterable = asyncFromStream(stream)
115
+ iterable = decryptFile(iterable, password, {
116
+ algorithm: EncryptionAlgorithm.AES_256_GCM,
117
+ })
118
+ iterable = decompressFile(iterable, {
119
+ algorithm: CompressionAlgorithm.ZLIB,
120
+ })
121
+
122
+ const processedChunks: Buffer[] = []
123
+ for await (const chunk of iterable) {
124
+ processedChunks.push(chunk)
125
+ }
126
+ const combined = new Uint8Array(processedChunks.reduce((acc, chunk) => acc + chunk.length, 0))
127
+ let offset = 0
128
+ for (const chunk of processedChunks) {
129
+ combined.set(chunk, offset)
130
+ offset += chunk.length
131
+ }
132
+ fileData.dataArrayBuffer = combined.buffer
133
+ fileData.isEncrypted = false
134
+ return fileData
135
+ } catch (error) {
136
+ throw new Error((error as Error).message)
137
+ }
138
+ }
139
+
140
+ // Helper function to determine if a file can be displayed directly via URL
141
+ export const canDisplayDirectly = (metadata: OffchainMetadata): boolean => {
142
+ if (metadata.uploadOptions?.encryption) return false // Encrypted files need decryption
143
+
144
+ const extension = metadata.name?.split('.').pop()?.toLowerCase() || ''
145
+ const mimeType = 'mimeType' in metadata ? (metadata.mimeType as string)?.toLowerCase() || '' : ''
146
+
147
+ // Media files that can be displayed directly
148
+ const directDisplayTypes = [
149
+ // Images
150
+ 'image/',
151
+ // Videos
152
+ 'video/',
153
+ // Audio
154
+ 'audio/',
155
+ // PDFs
156
+ 'application/pdf',
157
+ ]
158
+
159
+ const directDisplayExtensions = [
160
+ // Images
161
+ 'jpg',
162
+ 'jpeg',
163
+ 'png',
164
+ 'gif',
165
+ 'svg',
166
+ 'webp',
167
+ 'bmp',
168
+ 'ico',
169
+ // Videos
170
+ 'mp4',
171
+ 'webm',
172
+ 'avi',
173
+ 'mov',
174
+ 'mkv',
175
+ 'flv',
176
+ 'wmv',
177
+ // Audio
178
+ 'mp3',
179
+ 'wav',
180
+ 'ogg',
181
+ 'flac',
182
+ 'm4a',
183
+ 'aac',
184
+ // PDFs
185
+ 'pdf',
186
+ ]
187
+
188
+ return (
189
+ directDisplayTypes.some((type) => mimeType.startsWith(type)) ||
190
+ directDisplayExtensions.includes(extension)
191
+ )
192
+ }
193
+
194
+ // Helper function to determine if a file needs content parsing
195
+ export const needsContentParsing = (metadata: OffchainMetadata): boolean => {
196
+ const extension = metadata.name?.split('.').pop()?.toLowerCase() || ''
197
+ const mimeType = 'mimeType' in metadata ? (metadata.mimeType as string)?.toLowerCase() || '' : ''
198
+
199
+ // Text files and JSON files need content parsing
200
+ const textTypes = ['text/']
201
+ const textExtensions = [
202
+ 'js',
203
+ 'jsx',
204
+ 'ts',
205
+ 'tsx',
206
+ 'html',
207
+ 'css',
208
+ 'py',
209
+ 'java',
210
+ 'rb',
211
+ 'go',
212
+ 'rust',
213
+ 'php',
214
+ 'txt',
215
+ 'md',
216
+ 'xml',
217
+ 'csv',
218
+ 'json',
219
+ ]
220
+
221
+ return (
222
+ textTypes.some((type) => mimeType.startsWith(type)) ||
223
+ textExtensions.includes(extension) ||
224
+ mimeType === 'application/json'
225
+ )
226
+ }
227
+
228
+ export const processFileData = async (fileData: FileData) => {
229
+ const fileType = await detectFileType(fileData.dataArrayBuffer)
230
+ const blob = new Blob([fileData.dataArrayBuffer], { type: fileType })
231
+ return blob
232
+ }