@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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/utils/file.d.ts +15 -0
- package/dist/utils/file.d.ts.map +1 -0
- package/dist/utils/file.js +248 -0
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/utils/file.ts +232 -0
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
@@ -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.
|
|
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.
|
|
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": "
|
|
55
|
+
"gitHead": "39e5b7764ef03b5e010bba41fdd716f9c412170c"
|
|
56
56
|
}
|
package/src/index.ts
CHANGED
|
@@ -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
|
+
}
|