uploaderkit 1.0.0 → 2.0.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.
- package/LICENSE +21 -0
- package/NOTICE +16 -0
- package/README.es.md +1140 -0
- package/README.md +1135 -0
- package/dist/FileViewer-ChTf7-uY.d.cts +49 -0
- package/dist/FileViewer-nuD-GEdJ.d.ts +49 -0
- package/dist/adapters/gcs.cjs +83 -0
- package/dist/adapters/gcs.cjs.map +1 -0
- package/dist/adapters/gcs.d.cts +31 -0
- package/dist/adapters/gcs.d.ts +31 -0
- package/dist/adapters/gcs.js +81 -0
- package/dist/adapters/gcs.js.map +1 -0
- package/dist/adapters/memory.cjs +27 -0
- package/dist/adapters/memory.cjs.map +1 -0
- package/dist/adapters/memory.d.cts +18 -0
- package/dist/adapters/memory.d.ts +18 -0
- package/dist/adapters/memory.js +25 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/s3.cjs +75 -0
- package/dist/adapters/s3.cjs.map +1 -0
- package/dist/adapters/s3.d.cts +27 -0
- package/dist/adapters/s3.d.ts +27 -0
- package/dist/adapters/s3.js +73 -0
- package/dist/adapters/s3.js.map +1 -0
- package/dist/chunk-3FI44IOW.js +150 -0
- package/dist/chunk-3FI44IOW.js.map +1 -0
- package/dist/chunk-H7BRW5IY.js +308 -0
- package/dist/chunk-H7BRW5IY.js.map +1 -0
- package/dist/chunk-PDKAF4GX.js +707 -0
- package/dist/chunk-PDKAF4GX.js.map +1 -0
- package/dist/chunk-PTEX7F4R.js +309 -0
- package/dist/chunk-PTEX7F4R.js.map +1 -0
- package/dist/chunk-T5YZG6JW.js +511 -0
- package/dist/chunk-T5YZG6JW.js.map +1 -0
- package/dist/index.cjs +220 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -170
- package/dist/index.d.ts +57 -170
- package/dist/index.js +1 -324
- package/dist/index.js.map +1 -1
- package/dist/presets.cjs +1665 -0
- package/dist/presets.cjs.map +1 -0
- package/dist/presets.d.cts +100 -0
- package/dist/presets.d.ts +100 -0
- package/dist/presets.js +405 -0
- package/dist/presets.js.map +1 -0
- package/dist/react.cjs +981 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +106 -0
- package/dist/react.d.ts +106 -0
- package/dist/react.js +5 -0
- package/dist/react.js.map +1 -0
- package/dist/server/express.cjs +179 -0
- package/dist/server/express.cjs.map +1 -0
- package/dist/server/express.d.cts +64 -0
- package/dist/server/express.d.ts +64 -0
- package/dist/server/express.js +111 -0
- package/dist/server/express.js.map +1 -0
- package/dist/server/next.cjs +165 -0
- package/dist/server/next.cjs.map +1 -0
- package/dist/server/next.d.cts +38 -0
- package/dist/server/next.d.ts +38 -0
- package/dist/server/next.js +107 -0
- package/dist/server/next.js.map +1 -0
- package/dist/server.cjs +550 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +19 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.js +80 -0
- package/dist/server.js.map +1 -0
- package/dist/storage-CYkSHWZX.d.cts +133 -0
- package/dist/storage-Qc9epG0G.d.ts +133 -0
- package/dist/types-BSlJJwti.d.cts +341 -0
- package/dist/types-BSlJJwti.d.ts +341 -0
- package/dist/ui.cjs +2325 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.d.cts +331 -0
- package/dist/ui.d.ts +331 -0
- package/dist/ui.js +749 -0
- package/dist/ui.js.map +1 -0
- package/dist/useSlottedUploader-BzT5jV8c.d.cts +139 -0
- package/dist/useSlottedUploader-IkG5RhEO.d.ts +139 -0
- package/dist/useUploader-BiBdS-7y.d.cts +117 -0
- package/dist/useUploader-CQHpj_oI.d.ts +117 -0
- package/package.json +189 -3
- package/tailwind.css +80 -0
package/dist/index.js
CHANGED
|
@@ -1,326 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
var KB = 1024;
|
|
3
|
-
var MB = 1024 * KB;
|
|
4
|
-
var GB = 1024 * MB;
|
|
5
|
-
var MAGIC_NUMBERS = {
|
|
6
|
-
png: [137, 80, 78, 71, 13, 10, 26, 10],
|
|
7
|
-
jpg: [255, 216, 255],
|
|
8
|
-
jpeg: [255, 216, 255],
|
|
9
|
-
gif: [71, 73, 70, 56],
|
|
10
|
-
webp: [82, 73, 70, 70],
|
|
11
|
-
// RIFF; bytes 8-11 spell WEBP
|
|
12
|
-
ico: [0, 0, 1, 0],
|
|
13
|
-
pdf: [37, 80, 68, 70],
|
|
14
|
-
// %PDF
|
|
15
|
-
// OOXML files are ZIP containers, so docx/xlsx share the PK signature.
|
|
16
|
-
docx: [80, 75, 3, 4],
|
|
17
|
-
xlsx: [80, 75, 3, 4],
|
|
18
|
-
doc: [208, 207, 17, 224],
|
|
19
|
-
xls: [208, 207, 17, 224],
|
|
20
|
-
// DER/ASN.1 sequence header.
|
|
21
|
-
cer: [48, 130],
|
|
22
|
-
crt: [48, 130],
|
|
23
|
-
key: [48, 130],
|
|
24
|
-
pem: [45, 45, 45, 45, 45],
|
|
25
|
-
// -----
|
|
26
|
-
webm: [26, 69, 223, 163],
|
|
27
|
-
avi: [82, 73, 70, 70],
|
|
28
|
-
mp3: [73, 68, 51],
|
|
29
|
-
// ID3
|
|
30
|
-
wav: [82, 73, 70, 70],
|
|
31
|
-
ogg: [79, 103, 103, 83],
|
|
32
|
-
aac: [255, 241]
|
|
33
|
-
};
|
|
34
|
-
var MIME_TYPES = {
|
|
35
|
-
png: "image/png",
|
|
36
|
-
jpg: "image/jpeg",
|
|
37
|
-
jpeg: "image/jpeg",
|
|
38
|
-
svg: "image/svg+xml",
|
|
39
|
-
webp: "image/webp",
|
|
40
|
-
gif: "image/gif",
|
|
41
|
-
ico: "image/x-icon",
|
|
42
|
-
heic: "image/heic",
|
|
43
|
-
cer: "application/x-x509-ca-cert",
|
|
44
|
-
crt: "application/x-x509-ca-cert",
|
|
45
|
-
pem: "application/x-pem-file",
|
|
46
|
-
key: "application/x-pem-file",
|
|
47
|
-
pdf: "application/pdf",
|
|
48
|
-
doc: "application/msword",
|
|
49
|
-
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
50
|
-
xls: "application/vnd.ms-excel",
|
|
51
|
-
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
52
|
-
csv: "text/csv",
|
|
53
|
-
txt: "text/plain",
|
|
54
|
-
xml: "application/xml",
|
|
55
|
-
json: "application/json",
|
|
56
|
-
mp4: "video/mp4",
|
|
57
|
-
mov: "video/quicktime",
|
|
58
|
-
avi: "video/x-msvideo",
|
|
59
|
-
webm: "video/webm",
|
|
60
|
-
mp3: "audio/mpeg",
|
|
61
|
-
wav: "audio/wav",
|
|
62
|
-
ogg: "audio/ogg",
|
|
63
|
-
aac: "audio/aac"
|
|
64
|
-
};
|
|
65
|
-
var FILE_CATEGORY_CONFIG = {
|
|
66
|
-
image: {
|
|
67
|
-
extensions: ["png", "jpg", "jpeg", "svg", "webp", "gif"],
|
|
68
|
-
maxBytes: 5 * MB,
|
|
69
|
-
validateMagicNumbers: true,
|
|
70
|
-
label: "imagen",
|
|
71
|
-
accept: "image/*"
|
|
72
|
-
},
|
|
73
|
-
certificate: {
|
|
74
|
-
extensions: ["cer", "crt", "pem"],
|
|
75
|
-
maxBytes: 50 * KB,
|
|
76
|
-
validateMagicNumbers: true,
|
|
77
|
-
label: "certificado",
|
|
78
|
-
accept: ".cer,.crt,.pem"
|
|
79
|
-
},
|
|
80
|
-
key: {
|
|
81
|
-
extensions: ["key"],
|
|
82
|
-
maxBytes: 50 * KB,
|
|
83
|
-
validateMagicNumbers: true,
|
|
84
|
-
label: "llave privada",
|
|
85
|
-
accept: ".key"
|
|
86
|
-
},
|
|
87
|
-
pdf: {
|
|
88
|
-
extensions: ["pdf"],
|
|
89
|
-
maxBytes: 20 * MB,
|
|
90
|
-
validateMagicNumbers: true,
|
|
91
|
-
label: "PDF",
|
|
92
|
-
accept: ".pdf"
|
|
93
|
-
},
|
|
94
|
-
document: {
|
|
95
|
-
extensions: ["pdf", "doc", "docx", "xls", "xlsx", "csv", "txt"],
|
|
96
|
-
maxBytes: 20 * MB,
|
|
97
|
-
validateMagicNumbers: false,
|
|
98
|
-
label: "documento",
|
|
99
|
-
accept: ".pdf,.doc,.docx,.xls,.xlsx,.csv,.txt"
|
|
100
|
-
},
|
|
101
|
-
data: {
|
|
102
|
-
extensions: ["xml", "json"],
|
|
103
|
-
maxBytes: 5 * MB,
|
|
104
|
-
validateMagicNumbers: false,
|
|
105
|
-
label: "archivo de datos",
|
|
106
|
-
accept: ".xml,.json"
|
|
107
|
-
},
|
|
108
|
-
video: {
|
|
109
|
-
extensions: ["mp4", "mov", "avi", "webm"],
|
|
110
|
-
maxBytes: 100 * MB,
|
|
111
|
-
validateMagicNumbers: false,
|
|
112
|
-
label: "video",
|
|
113
|
-
accept: "video/*"
|
|
114
|
-
},
|
|
115
|
-
audio: {
|
|
116
|
-
extensions: ["mp3", "wav", "ogg", "aac"],
|
|
117
|
-
maxBytes: 20 * MB,
|
|
118
|
-
validateMagicNumbers: false,
|
|
119
|
-
label: "audio",
|
|
120
|
-
accept: "audio/*"
|
|
121
|
-
},
|
|
122
|
-
any: {
|
|
123
|
-
extensions: [],
|
|
124
|
-
maxBytes: 20 * MB,
|
|
125
|
-
validateMagicNumbers: false,
|
|
126
|
-
label: "archivo",
|
|
127
|
-
accept: "*/*"
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// src/file.ts
|
|
132
|
-
var getFileExtension = (fileName) => {
|
|
133
|
-
const dot = fileName.lastIndexOf(".");
|
|
134
|
-
if (dot < 1 || dot === fileName.length - 1) return "";
|
|
135
|
-
return fileName.slice(dot + 1).toLowerCase();
|
|
136
|
-
};
|
|
137
|
-
var getMimeType = (fileName) => {
|
|
138
|
-
const ext = getFileExtension(fileName);
|
|
139
|
-
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
140
|
-
};
|
|
141
|
-
var isKnownExtension = (value) => value in MIME_TYPES;
|
|
142
|
-
var formatFileSize = (bytes) => {
|
|
143
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
144
|
-
const units = ["KB", "MB", "GB", "TB"];
|
|
145
|
-
let size = bytes / 1024;
|
|
146
|
-
let unit = 0;
|
|
147
|
-
while (size >= 1024 && unit < units.length - 1) {
|
|
148
|
-
size /= 1024;
|
|
149
|
-
unit++;
|
|
150
|
-
}
|
|
151
|
-
return `${size.toFixed(size >= 10 ? 0 : 1)} ${units[unit]}`;
|
|
152
|
-
};
|
|
153
|
-
var toAcceptAttribute = (extensions) => extensions.length === 0 ? "*/*" : extensions.map((extension) => `.${extension}`).join(",");
|
|
154
|
-
var fromMulterFile = (file) => ({
|
|
155
|
-
name: file.originalname,
|
|
156
|
-
size: file.size,
|
|
157
|
-
type: file.mimetype,
|
|
158
|
-
arrayBuffer: async () => file.buffer.buffer.slice(
|
|
159
|
-
file.buffer.byteOffset,
|
|
160
|
-
file.buffer.byteOffset + file.buffer.byteLength
|
|
161
|
-
)
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// src/validation.ts
|
|
165
|
-
var ok = { valid: true };
|
|
166
|
-
var fail = (code, message) => ({ valid: false, code, message });
|
|
167
|
-
var validateExtension = (fileName, allowed) => {
|
|
168
|
-
if (allowed.length === 0) return ok;
|
|
169
|
-
const extension = getFileExtension(fileName);
|
|
170
|
-
if (!extension) {
|
|
171
|
-
return fail("extension-not-allowed", "El archivo no tiene extensi\xF3n");
|
|
172
|
-
}
|
|
173
|
-
if (!allowed.includes(extension)) {
|
|
174
|
-
return fail(
|
|
175
|
-
"extension-not-allowed",
|
|
176
|
-
`Formato no permitido. Se aceptan: ${allowed.join(", ")}`
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
return ok;
|
|
180
|
-
};
|
|
181
|
-
var validateSize = (size, maxBytes) => {
|
|
182
|
-
if (size === 0) return fail("empty-file", "El archivo est\xE1 vac\xEDo");
|
|
183
|
-
if (size > maxBytes) {
|
|
184
|
-
return fail(
|
|
185
|
-
"too-large",
|
|
186
|
-
`El archivo pesa ${formatFileSize(size)} y el m\xE1ximo es ${formatFileSize(maxBytes)}`
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
return ok;
|
|
190
|
-
};
|
|
191
|
-
var validateMagicNumbers = async (file) => {
|
|
192
|
-
const extension = getFileExtension(file.name);
|
|
193
|
-
if (!isKnownExtension(extension)) return ok;
|
|
194
|
-
const expected = MAGIC_NUMBERS[extension];
|
|
195
|
-
if (!expected) return ok;
|
|
196
|
-
const head = new Uint8Array(await file.arrayBuffer()).slice(
|
|
197
|
-
0,
|
|
198
|
-
expected.length
|
|
199
|
-
);
|
|
200
|
-
if (head.length < expected.length) {
|
|
201
|
-
return fail("magic-number-mismatch", "El archivo est\xE1 incompleto");
|
|
202
|
-
}
|
|
203
|
-
const matches = expected.every((byte, index) => head[index] === byte);
|
|
204
|
-
return matches ? ok : fail(
|
|
205
|
-
"magic-number-mismatch",
|
|
206
|
-
"El contenido del archivo no corresponde a su extensi\xF3n"
|
|
207
|
-
);
|
|
208
|
-
};
|
|
209
|
-
var validateFile = async (file, options = {}) => {
|
|
210
|
-
const {
|
|
211
|
-
maxBytes = Infinity,
|
|
212
|
-
allowedExtensions = [],
|
|
213
|
-
validateMagicNumbers: checkMagic = false,
|
|
214
|
-
customValidation
|
|
215
|
-
} = options;
|
|
216
|
-
const size = validateSize(file.size, maxBytes);
|
|
217
|
-
if (!size.valid) return size;
|
|
218
|
-
const extension = validateExtension(file.name, allowedExtensions);
|
|
219
|
-
if (!extension.valid) return extension;
|
|
220
|
-
if (checkMagic) {
|
|
221
|
-
const magic = await validateMagicNumbers(file);
|
|
222
|
-
if (!magic.valid) return magic;
|
|
223
|
-
}
|
|
224
|
-
if (customValidation) return customValidation(file);
|
|
225
|
-
return ok;
|
|
226
|
-
};
|
|
227
|
-
var validateFiles = async (files, options = {}) => Promise.all(
|
|
228
|
-
files.map(async (file) => ({
|
|
229
|
-
file,
|
|
230
|
-
result: await validateFile(file, options)
|
|
231
|
-
}))
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
// src/scopes.ts
|
|
235
|
-
var ScopeError = class extends Error {
|
|
236
|
-
name = "ScopeError";
|
|
237
|
-
};
|
|
238
|
-
var assertDefinition = (name, scope) => {
|
|
239
|
-
if (typeof scope.path !== "function") {
|
|
240
|
-
throw new ScopeError(`Scope "${name}": "path" must be a function`);
|
|
241
|
-
}
|
|
242
|
-
if (!Number.isFinite(scope.maxBytes) || scope.maxBytes <= 0) {
|
|
243
|
-
throw new ScopeError(
|
|
244
|
-
`Scope "${name}": "maxBytes" must be a positive number`
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
if (scope.accept.length === 0) {
|
|
248
|
-
throw new ScopeError(
|
|
249
|
-
`Scope "${name}": "accept" cannot be empty \u2014 list the extensions this scope takes`
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
if (scope.category && scope.category !== "any") {
|
|
253
|
-
const allowed = FILE_CATEGORY_CONFIG[scope.category].extensions;
|
|
254
|
-
const outside = scope.accept.filter(
|
|
255
|
-
(extension) => !allowed.includes(extension)
|
|
256
|
-
);
|
|
257
|
-
if (outside.length > 0) {
|
|
258
|
-
throw new ScopeError(
|
|
259
|
-
`Scope "${name}": ${outside.join(", ")} not allowed by category "${scope.category}"`
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
if (scope.compress) {
|
|
264
|
-
const imageOnly = FILE_CATEGORY_CONFIG.image.extensions;
|
|
265
|
-
const notImages = scope.accept.filter(
|
|
266
|
-
(extension) => !imageOnly.includes(extension)
|
|
267
|
-
);
|
|
268
|
-
if (notImages.length > 0) {
|
|
269
|
-
throw new ScopeError(
|
|
270
|
-
`Scope "${name}": "compress" only applies to images, but it accepts ${notImages.join(", ")}`
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
var defineScopes = (scopes) => {
|
|
276
|
-
for (const [name, scope] of Object.entries(scopes)) {
|
|
277
|
-
assertDefinition(name, scope);
|
|
278
|
-
}
|
|
279
|
-
const names = Object.keys(scopes);
|
|
280
|
-
const get = (name) => {
|
|
281
|
-
const scope = scopes[name];
|
|
282
|
-
if (!scope) {
|
|
283
|
-
throw new ScopeError(
|
|
284
|
-
`Unknown scope "${name}". Declared: ${names.join(", ")}`
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
return scope;
|
|
288
|
-
};
|
|
289
|
-
return {
|
|
290
|
-
scopes,
|
|
291
|
-
names,
|
|
292
|
-
get,
|
|
293
|
-
has: (name) => name in scopes,
|
|
294
|
-
accept: (name) => toAcceptAttribute(get(name).accept)
|
|
295
|
-
};
|
|
296
|
-
};
|
|
297
|
-
var validateForScope = async (registry, name, file) => {
|
|
298
|
-
const scope = registry.get(name);
|
|
299
|
-
const category = scope.category ? FILE_CATEGORY_CONFIG[scope.category] : void 0;
|
|
300
|
-
return validateFile(file, {
|
|
301
|
-
maxBytes: scope.maxBytes,
|
|
302
|
-
allowedExtensions: scope.accept,
|
|
303
|
-
validateMagicNumbers: category?.validateMagicNumbers ?? true
|
|
304
|
-
});
|
|
305
|
-
};
|
|
306
|
-
var assertProviderSupports = (registry, provider) => {
|
|
307
|
-
const needsSigning = registry.names.filter(
|
|
308
|
-
(name) => registry.get(name).visibility === "private"
|
|
309
|
-
);
|
|
310
|
-
if (needsSigning.length > 0 && !provider.capabilities.signedUrl) {
|
|
311
|
-
throw new ScopeError(
|
|
312
|
-
`Provider "${provider.name}" cannot sign URLs, but these scopes are private: ${needsSigning.join(", ")}`
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
};
|
|
316
|
-
var resolveKey = (registry, name, entityId, file) => {
|
|
317
|
-
const key = registry.get(name).path(entityId, file);
|
|
318
|
-
if (key.startsWith("/") || key.includes("..")) {
|
|
319
|
-
throw new ScopeError(`Scope "${name}" produced an unsafe key: ${key}`);
|
|
320
|
-
}
|
|
321
|
-
return key;
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
export { FILE_CATEGORY_CONFIG, GB, KB, MAGIC_NUMBERS, MB, MIME_TYPES, ScopeError, assertProviderSupports, defineScopes, formatFileSize, fromMulterFile, getFileExtension, getMimeType, isKnownExtension, resolveKey, toAcceptAttribute, validateExtension, validateFile, validateFiles, validateForScope, validateMagicNumbers, validateSize };
|
|
1
|
+
export { DEFAULT_LABELS, EN_LABELS, ES_LABELS, FILE_CATEGORY_CONFIG, GB, KB, MAGIC_NUMBERS, MB, MIME_TYPES, ScopeError, assertProviderSupports, defineScopes, formatFileSize, fromMulterFile, getFileExtension, getMimeType, hasStableKey, isKnownExtension, resolveKey, resolveLabels, resolveReplaceMode, resolveScopePrefix, sanitizeFileName, toAcceptAttribute, validateExtension, validateFile, validateFiles, validateForScope, validateMagicNumbers, validateSize } from './chunk-T5YZG6JW.js';
|
|
325
2
|
//# sourceMappingURL=index.js.map
|
|
326
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants.ts","../src/file.ts","../src/validation.ts","../src/scopes.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { FileCategory, FileCategoryConfig, FileExtension } from './types'\n\nexport const KB = 1024\nexport const MB = 1024 * KB\nexport const GB = 1024 * MB\n\n/**\n * Leading bytes that identify a format regardless of its extension. The check\n * that uses them is the only defence against an executable renamed to `.pdf`,\n * since both the extension and the browser-reported MIME come from the client.\n */\nexport const MAGIC_NUMBERS: Partial<Record<FileExtension, number[]>> = {\n\tpng: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],\n\tjpg: [0xff, 0xd8, 0xff],\n\tjpeg: [0xff, 0xd8, 0xff],\n\tgif: [0x47, 0x49, 0x46, 0x38],\n\twebp: [0x52, 0x49, 0x46, 0x46], // RIFF; bytes 8-11 spell WEBP\n\tico: [0x00, 0x00, 0x01, 0x00],\n\n\tpdf: [0x25, 0x50, 0x44, 0x46], // %PDF\n\n\t// OOXML files are ZIP containers, so docx/xlsx share the PK signature.\n\tdocx: [0x50, 0x4b, 0x03, 0x04],\n\txlsx: [0x50, 0x4b, 0x03, 0x04],\n\tdoc: [0xd0, 0xcf, 0x11, 0xe0],\n\txls: [0xd0, 0xcf, 0x11, 0xe0],\n\n\t// DER/ASN.1 sequence header.\n\tcer: [0x30, 0x82],\n\tcrt: [0x30, 0x82],\n\tkey: [0x30, 0x82],\n\tpem: [0x2d, 0x2d, 0x2d, 0x2d, 0x2d], // -----\n\n\twebm: [0x1a, 0x45, 0xdf, 0xa3],\n\tavi: [0x52, 0x49, 0x46, 0x46],\n\tmp3: [0x49, 0x44, 0x33], // ID3\n\twav: [0x52, 0x49, 0x46, 0x46],\n\togg: [0x4f, 0x67, 0x67, 0x53],\n\taac: [0xff, 0xf1],\n}\n\nexport const MIME_TYPES: Record<FileExtension, string> = {\n\tpng: 'image/png',\n\tjpg: 'image/jpeg',\n\tjpeg: 'image/jpeg',\n\tsvg: 'image/svg+xml',\n\twebp: 'image/webp',\n\tgif: 'image/gif',\n\tico: 'image/x-icon',\n\theic: 'image/heic',\n\n\tcer: 'application/x-x509-ca-cert',\n\tcrt: 'application/x-x509-ca-cert',\n\tpem: 'application/x-pem-file',\n\tkey: 'application/x-pem-file',\n\n\tpdf: 'application/pdf',\n\tdoc: 'application/msword',\n\tdocx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n\txls: 'application/vnd.ms-excel',\n\txlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n\tcsv: 'text/csv',\n\ttxt: 'text/plain',\n\n\txml: 'application/xml',\n\tjson: 'application/json',\n\n\tmp4: 'video/mp4',\n\tmov: 'video/quicktime',\n\tavi: 'video/x-msvideo',\n\twebm: 'video/webm',\n\n\tmp3: 'audio/mpeg',\n\twav: 'audio/wav',\n\togg: 'audio/ogg',\n\taac: 'audio/aac',\n}\n\n/**\n * Defaults per category. A scope always narrows these; it can never widen the\n * extension list, which is what keeps `any` from leaking into a strict scope.\n */\nexport const FILE_CATEGORY_CONFIG: Record<FileCategory, FileCategoryConfig> = {\n\timage: {\n\t\textensions: ['png', 'jpg', 'jpeg', 'svg', 'webp', 'gif'],\n\t\tmaxBytes: 5 * MB,\n\t\tvalidateMagicNumbers: true,\n\t\tlabel: 'imagen',\n\t\taccept: 'image/*',\n\t},\n\tcertificate: {\n\t\textensions: ['cer', 'crt', 'pem'],\n\t\tmaxBytes: 50 * KB,\n\t\tvalidateMagicNumbers: true,\n\t\tlabel: 'certificado',\n\t\taccept: '.cer,.crt,.pem',\n\t},\n\tkey: {\n\t\textensions: ['key'],\n\t\tmaxBytes: 50 * KB,\n\t\tvalidateMagicNumbers: true,\n\t\tlabel: 'llave privada',\n\t\taccept: '.key',\n\t},\n\tpdf: {\n\t\textensions: ['pdf'],\n\t\tmaxBytes: 20 * MB,\n\t\tvalidateMagicNumbers: true,\n\t\tlabel: 'PDF',\n\t\taccept: '.pdf',\n\t},\n\tdocument: {\n\t\textensions: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'txt'],\n\t\tmaxBytes: 20 * MB,\n\t\tvalidateMagicNumbers: false,\n\t\tlabel: 'documento',\n\t\taccept: '.pdf,.doc,.docx,.xls,.xlsx,.csv,.txt',\n\t},\n\tdata: {\n\t\textensions: ['xml', 'json'],\n\t\tmaxBytes: 5 * MB,\n\t\tvalidateMagicNumbers: false,\n\t\tlabel: 'archivo de datos',\n\t\taccept: '.xml,.json',\n\t},\n\tvideo: {\n\t\textensions: ['mp4', 'mov', 'avi', 'webm'],\n\t\tmaxBytes: 100 * MB,\n\t\tvalidateMagicNumbers: false,\n\t\tlabel: 'video',\n\t\taccept: 'video/*',\n\t},\n\taudio: {\n\t\textensions: ['mp3', 'wav', 'ogg', 'aac'],\n\t\tmaxBytes: 20 * MB,\n\t\tvalidateMagicNumbers: false,\n\t\tlabel: 'audio',\n\t\taccept: 'audio/*',\n\t},\n\tany: {\n\t\textensions: [],\n\t\tmaxBytes: 20 * MB,\n\t\tvalidateMagicNumbers: false,\n\t\tlabel: 'archivo',\n\t\taccept: '*/*',\n\t},\n}\n","import { MIME_TYPES } from './constants'\nimport type { FileExtension, FileLike } from './types'\n\n/** Lower-case extension without the dot, or `''` when the name has none. */\nexport const getFileExtension = (fileName: string): string => {\n\tconst dot = fileName.lastIndexOf('.')\n\tif (dot < 1 || dot === fileName.length - 1) return ''\n\treturn fileName.slice(dot + 1).toLowerCase()\n}\n\nexport const getMimeType = (fileName: string): string => {\n\tconst ext = getFileExtension(fileName) as FileExtension\n\treturn MIME_TYPES[ext] ?? 'application/octet-stream'\n}\n\nexport const isKnownExtension = (value: string): value is FileExtension =>\n\tvalue in MIME_TYPES\n\nexport const formatFileSize = (bytes: number): string => {\n\tif (bytes < 1024) return `${bytes} B`\n\tconst units = ['KB', 'MB', 'GB', 'TB']\n\tlet size = bytes / 1024\n\tlet unit = 0\n\twhile (size >= 1024 && unit < units.length - 1) {\n\t\tsize /= 1024\n\t\tunit++\n\t}\n\treturn `${size.toFixed(size >= 10 ? 0 : 1)} ${units[unit]}`\n}\n\n/** Value for an `<input accept=\"…\">` built from an extension list. */\nexport const toAcceptAttribute = (extensions: FileExtension[]): string =>\n\textensions.length === 0\n\t\t? '*/*'\n\t\t: extensions.map(extension => `.${extension}`).join(',')\n\n/**\n * Adapts a multer memory-storage file to `FileLike` so server code validates\n * with the very same function the browser ran.\n */\nexport const fromMulterFile = (file: {\n\toriginalname: string\n\tsize: number\n\tmimetype: string\n\tbuffer: Uint8Array\n}): FileLike => ({\n\tname: file.originalname,\n\tsize: file.size,\n\ttype: file.mimetype,\n\tarrayBuffer: async () =>\n\t\tfile.buffer.buffer.slice(\n\t\t\tfile.buffer.byteOffset,\n\t\t\tfile.buffer.byteOffset + file.buffer.byteLength\n\t\t) as ArrayBuffer,\n})\n","import { MAGIC_NUMBERS } from './constants'\nimport { formatFileSize, getFileExtension, isKnownExtension } from './file'\nimport type {\n\tFileExtension,\n\tFileLike,\n\tValidationOptions,\n\tValidationResult,\n} from './types'\n\nconst ok: ValidationResult = { valid: true }\n\nconst fail = (\n\tcode: Exclude<ValidationResult, { valid: true }>['code'],\n\tmessage: string\n): ValidationResult => ({ valid: false, code, message })\n\nexport const validateExtension = (\n\tfileName: string,\n\tallowed: FileExtension[]\n): ValidationResult => {\n\tif (allowed.length === 0) return ok\n\n\tconst extension = getFileExtension(fileName)\n\tif (!extension) {\n\t\treturn fail('extension-not-allowed', 'El archivo no tiene extensión')\n\t}\n\tif (!allowed.includes(extension as FileExtension)) {\n\t\treturn fail(\n\t\t\t'extension-not-allowed',\n\t\t\t`Formato no permitido. Se aceptan: ${allowed.join(', ')}`\n\t\t)\n\t}\n\treturn ok\n}\n\nexport const validateSize = (\n\tsize: number,\n\tmaxBytes: number\n): ValidationResult => {\n\tif (size === 0) return fail('empty-file', 'El archivo está vacío')\n\tif (size > maxBytes) {\n\t\treturn fail(\n\t\t\t'too-large',\n\t\t\t`El archivo pesa ${formatFileSize(size)} y el máximo es ${formatFileSize(maxBytes)}`\n\t\t)\n\t}\n\treturn ok\n}\n\n/**\n * Compares the file's leading bytes against the signature its extension\n * claims. Extensions without a known signature pass — absence of a signature\n * is not evidence of tampering.\n */\nexport const validateMagicNumbers = async (\n\tfile: FileLike\n): Promise<ValidationResult> => {\n\tconst extension = getFileExtension(file.name)\n\tif (!isKnownExtension(extension)) return ok\n\n\tconst expected = MAGIC_NUMBERS[extension]\n\tif (!expected) return ok\n\n\tconst head = new Uint8Array(await file.arrayBuffer()).slice(\n\t\t0,\n\t\texpected.length\n\t)\n\tif (head.length < expected.length) {\n\t\treturn fail('magic-number-mismatch', 'El archivo está incompleto')\n\t}\n\n\tconst matches = expected.every((byte, index) => head[index] === byte)\n\treturn matches\n\t\t? ok\n\t\t: fail(\n\t\t\t\t'magic-number-mismatch',\n\t\t\t\t'El contenido del archivo no corresponde a su extensión'\n\t\t\t)\n}\n\n/**\n * Runs the checks cheapest-first so a 2 GB file is rejected on its size before\n * anything reads its bytes.\n */\nexport const validateFile = async (\n\tfile: FileLike,\n\toptions: ValidationOptions = {}\n): Promise<ValidationResult> => {\n\tconst {\n\t\tmaxBytes = Infinity,\n\t\tallowedExtensions = [],\n\t\tvalidateMagicNumbers: checkMagic = false,\n\t\tcustomValidation,\n\t} = options\n\n\tconst size = validateSize(file.size, maxBytes)\n\tif (!size.valid) return size\n\n\tconst extension = validateExtension(file.name, allowedExtensions)\n\tif (!extension.valid) return extension\n\n\tif (checkMagic) {\n\t\tconst magic = await validateMagicNumbers(file)\n\t\tif (!magic.valid) return magic\n\t}\n\n\tif (customValidation) return customValidation(file)\n\n\treturn ok\n}\n\nexport type FileValidation = {\n\tfile: FileLike\n\tresult: ValidationResult\n}\n\n/** Validates every file and reports each result; never short-circuits. */\nexport const validateFiles = async (\n\tfiles: FileLike[],\n\toptions: ValidationOptions = {}\n): Promise<FileValidation[]> =>\n\tPromise.all(\n\t\tfiles.map(async file => ({\n\t\t\tfile,\n\t\t\tresult: await validateFile(file, options),\n\t\t}))\n\t)\n","import { FILE_CATEGORY_CONFIG } from './constants'\nimport { toAcceptAttribute } from './file'\nimport type {\n\tFileLike,\n\tScopeConfig,\n\tScopeRegistry,\n\tStorageProvider,\n\tValidationResult,\n} from './types'\nimport { validateFile } from './validation'\n\n/** Thrown for a definition or wiring mistake — never for a user's bad file. */\nexport class ScopeError extends Error {\n\toverride name = 'ScopeError'\n}\n\nconst assertDefinition = (name: string, scope: ScopeConfig): void => {\n\tif (typeof scope.path !== 'function') {\n\t\tthrow new ScopeError(`Scope \"${name}\": \"path\" must be a function`)\n\t}\n\tif (!Number.isFinite(scope.maxBytes) || scope.maxBytes <= 0) {\n\t\tthrow new ScopeError(\n\t\t\t`Scope \"${name}\": \"maxBytes\" must be a positive number`\n\t\t)\n\t}\n\tif (scope.accept.length === 0) {\n\t\tthrow new ScopeError(\n\t\t\t`Scope \"${name}\": \"accept\" cannot be empty — list the extensions this scope takes`\n\t\t)\n\t}\n\n\t// A scope narrows its category, never widens it. Otherwise a 'pdf' scope\n\t// could quietly start taking executables.\n\tif (scope.category && scope.category !== 'any') {\n\t\tconst allowed = FILE_CATEGORY_CONFIG[scope.category].extensions\n\t\tconst outside = scope.accept.filter(\n\t\t\textension => !allowed.includes(extension)\n\t\t)\n\t\tif (outside.length > 0) {\n\t\t\tthrow new ScopeError(\n\t\t\t\t`Scope \"${name}\": ${outside.join(', ')} not allowed by category \"${scope.category}\"`\n\t\t\t)\n\t\t}\n\t}\n\n\tif (scope.compress) {\n\t\tconst imageOnly = FILE_CATEGORY_CONFIG.image.extensions\n\t\tconst notImages = scope.accept.filter(\n\t\t\textension => !imageOnly.includes(extension)\n\t\t)\n\t\tif (notImages.length > 0) {\n\t\t\tthrow new ScopeError(\n\t\t\t\t`Scope \"${name}\": \"compress\" only applies to images, but it accepts ${notImages.join(', ')}`\n\t\t\t)\n\t\t}\n\t}\n}\n\n/**\n * Declares every destination an app can write to. The returned registry is\n * imported by the client (to configure inputs and pre-validate) and by the\n * server (to authorize and re-validate), which is what keeps the two in sync.\n *\n * Definitions are checked eagerly: a malformed scope throws at import time,\n * not on the first upload.\n */\nexport const defineScopes = <T extends Record<string, ScopeConfig>>(\n\tscopes: T\n): ScopeRegistry<T> => {\n\tfor (const [name, scope] of Object.entries(scopes)) {\n\t\tassertDefinition(name, scope)\n\t}\n\n\tconst names = Object.keys(scopes) as (keyof T & string)[]\n\n\tconst get = (name: string): ScopeConfig => {\n\t\tconst scope = scopes[name as keyof T]\n\t\tif (!scope) {\n\t\t\tthrow new ScopeError(\n\t\t\t\t`Unknown scope \"${name}\". Declared: ${names.join(', ')}`\n\t\t\t)\n\t\t}\n\t\treturn scope\n\t}\n\n\treturn {\n\t\tscopes,\n\t\tnames,\n\t\tget,\n\t\thas: name => name in scopes,\n\t\taccept: name => toAcceptAttribute(get(name).accept),\n\t}\n}\n\n/**\n * The one validation call. Client runs it for feedback, server runs it for\n * safety, both against the same scope.\n */\nexport const validateForScope = async <T extends Record<string, ScopeConfig>>(\n\tregistry: ScopeRegistry<T>,\n\tname: string,\n\tfile: FileLike\n): Promise<ValidationResult> => {\n\tconst scope = registry.get(name)\n\tconst category = scope.category\n\t\t? FILE_CATEGORY_CONFIG[scope.category]\n\t\t: undefined\n\n\treturn validateFile(file, {\n\t\tmaxBytes: scope.maxBytes,\n\t\tallowedExtensions: scope.accept,\n\t\tvalidateMagicNumbers: category?.validateMagicNumbers ?? true,\n\t})\n}\n\n/**\n * Fails fast when a provider cannot honour what the scopes promise — a private\n * scope on a provider that cannot sign URLs is a runtime 403 waiting to happen.\n */\nexport const assertProviderSupports = <T extends Record<string, ScopeConfig>>(\n\tregistry: ScopeRegistry<T>,\n\tprovider: StorageProvider\n): void => {\n\tconst needsSigning = registry.names.filter(\n\t\tname => registry.get(name).visibility === 'private'\n\t)\n\n\tif (needsSigning.length > 0 && !provider.capabilities.signedUrl) {\n\t\tthrow new ScopeError(\n\t\t\t`Provider \"${provider.name}\" cannot sign URLs, but these scopes are private: ${needsSigning.join(', ')}`\n\t\t)\n\t}\n}\n\n/** Resolves the storage key for an upload. */\nexport const resolveKey = <T extends Record<string, ScopeConfig>>(\n\tregistry: ScopeRegistry<T>,\n\tname: string,\n\tentityId: string,\n\tfile: FileLike\n): string => {\n\tconst key = registry.get(name).path(entityId, file)\n\tif (key.startsWith('/') || key.includes('..')) {\n\t\tthrow new ScopeError(`Scope \"${name}\" produced an unsafe key: ${key}`)\n\t}\n\treturn key\n}\n"]}
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|