@de-otio/trellis 0.10.11 → 0.12.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/dist/env.d.ts +232 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +221 -0
- package/dist/env.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lambda/media-completion-worker.d.ts +175 -0
- package/dist/lambda/media-completion-worker.d.ts.map +1 -0
- package/dist/lambda/media-completion-worker.js +373 -0
- package/dist/lambda/media-completion-worker.js.map +1 -0
- package/dist/lambda/media-processing-worker.d.ts +172 -1
- package/dist/lambda/media-processing-worker.d.ts.map +1 -1
- package/dist/lambda/media-processing-worker.js +343 -49
- package/dist/lambda/media-processing-worker.js.map +1 -1
- package/dist/lib/app.d.ts.map +1 -1
- package/dist/lib/app.js +5 -0
- package/dist/lib/app.js.map +1 -1
- package/dist/lib/encrypted-settings/config.d.ts +13 -0
- package/dist/lib/encrypted-settings/config.d.ts.map +1 -0
- package/dist/lib/encrypted-settings/config.js +19 -0
- package/dist/lib/encrypted-settings/config.js.map +1 -0
- package/dist/lib/encrypted-settings/encrypted-settings-handler.d.ts +57 -0
- package/dist/lib/encrypted-settings/encrypted-settings-handler.d.ts.map +1 -0
- package/dist/lib/encrypted-settings/encrypted-settings-handler.js +178 -0
- package/dist/lib/encrypted-settings/encrypted-settings-handler.js.map +1 -0
- package/dist/lib/encrypted-settings/encrypted-settings-store.d.ts +110 -0
- package/dist/lib/encrypted-settings/encrypted-settings-store.d.ts.map +1 -0
- package/dist/lib/encrypted-settings/encrypted-settings-store.js +103 -0
- package/dist/lib/encrypted-settings/encrypted-settings-store.js.map +1 -0
- package/dist/lib/encrypted-settings/types.d.ts +26 -0
- package/dist/lib/encrypted-settings/types.d.ts.map +1 -0
- package/dist/lib/encrypted-settings/types.js +27 -0
- package/dist/lib/encrypted-settings/types.js.map +1 -0
- package/dist/lib/exif-stripper.d.ts +37 -22
- package/dist/lib/exif-stripper.d.ts.map +1 -1
- package/dist/lib/exif-stripper.js +101 -41
- package/dist/lib/exif-stripper.js.map +1 -1
- package/dist/lib/media/cas-keys.d.ts +63 -0
- package/dist/lib/media/cas-keys.d.ts.map +1 -0
- package/dist/lib/media/cas-keys.js +102 -0
- package/dist/lib/media/cas-keys.js.map +1 -0
- package/dist/lib/media/classify-worker-error.d.ts +48 -0
- package/dist/lib/media/classify-worker-error.d.ts.map +1 -0
- package/dist/lib/media/classify-worker-error.js +319 -0
- package/dist/lib/media/classify-worker-error.js.map +1 -0
- package/dist/lib/media/dedupe-key.d.ts +29 -0
- package/dist/lib/media/dedupe-key.d.ts.map +1 -0
- package/dist/lib/media/dedupe-key.js +49 -0
- package/dist/lib/media/dedupe-key.js.map +1 -0
- package/dist/lib/media/duration-cap.d.ts +30 -0
- package/dist/lib/media/duration-cap.d.ts.map +1 -0
- package/dist/lib/media/duration-cap.js +37 -0
- package/dist/lib/media/duration-cap.js.map +1 -0
- package/dist/lib/media/ffmpeg-args.d.ts +83 -0
- package/dist/lib/media/ffmpeg-args.d.ts.map +1 -0
- package/dist/lib/media/ffmpeg-args.js +119 -0
- package/dist/lib/media/ffmpeg-args.js.map +1 -0
- package/dist/lib/media/media-ports.d.ts +126 -0
- package/dist/lib/media/media-ports.d.ts.map +1 -0
- package/dist/lib/media/media-ports.js +129 -0
- package/dist/lib/media/media-ports.js.map +1 -0
- package/dist/lib/media/media-upsert.d.ts +55 -0
- package/dist/lib/media/media-upsert.d.ts.map +1 -0
- package/dist/lib/media/media-upsert.js +38 -0
- package/dist/lib/media/media-upsert.js.map +1 -0
- package/dist/lib/media/moderation-provider.d.ts +111 -0
- package/dist/lib/media/moderation-provider.d.ts.map +1 -0
- package/dist/lib/media/moderation-provider.js +130 -0
- package/dist/lib/media/moderation-provider.js.map +1 -0
- package/dist/lib/media/moderation-resolved-payload.d.ts +48 -0
- package/dist/lib/media/moderation-resolved-payload.d.ts.map +1 -0
- package/dist/lib/media/moderation-resolved-payload.js +37 -0
- package/dist/lib/media/moderation-resolved-payload.js.map +1 -0
- package/dist/lib/media/moderation-status.d.ts +98 -0
- package/dist/lib/media/moderation-status.d.ts.map +1 -0
- package/dist/lib/media/moderation-status.js +122 -0
- package/dist/lib/media/moderation-status.js.map +1 -0
- package/dist/lib/media/processing-types.d.ts +45 -0
- package/dist/lib/media/processing-types.d.ts.map +1 -0
- package/dist/lib/media/processing-types.js +9 -0
- package/dist/lib/media/processing-types.js.map +1 -0
- package/dist/lib/media/promote-decision.d.ts +64 -0
- package/dist/lib/media/promote-decision.d.ts.map +1 -0
- package/dist/lib/media/promote-decision.js +76 -0
- package/dist/lib/media/promote-decision.js.map +1 -0
- package/dist/lib/media/quota-check.d.ts +22 -0
- package/dist/lib/media/quota-check.d.ts.map +1 -0
- package/dist/lib/media/quota-check.js +42 -0
- package/dist/lib/media/quota-check.js.map +1 -0
- package/dist/lib/media/quota-types.d.ts +15 -0
- package/dist/lib/media/quota-types.d.ts.map +1 -0
- package/dist/lib/media/quota-types.js +9 -0
- package/dist/lib/media/quota-types.js.map +1 -0
- package/dist/lib/media/route-upload.d.ts +58 -0
- package/dist/lib/media/route-upload.d.ts.map +1 -0
- package/dist/lib/media/route-upload.js +80 -0
- package/dist/lib/media/route-upload.js.map +1 -0
- package/dist/lib/media/serve-gate.d.ts +51 -0
- package/dist/lib/media/serve-gate.d.ts.map +1 -0
- package/dist/lib/media/serve-gate.js +68 -0
- package/dist/lib/media/serve-gate.js.map +1 -0
- package/dist/lib/media/tenant-resolution.d.ts +42 -0
- package/dist/lib/media/tenant-resolution.d.ts.map +1 -0
- package/dist/lib/media/tenant-resolution.js +45 -0
- package/dist/lib/media/tenant-resolution.js.map +1 -0
- package/dist/lib/media/text-moderation.d.ts +28 -0
- package/dist/lib/media/text-moderation.d.ts.map +1 -0
- package/dist/lib/media/text-moderation.js +62 -0
- package/dist/lib/media/text-moderation.js.map +1 -0
- package/dist/lib/media/track-verdict.d.ts +45 -0
- package/dist/lib/media/track-verdict.d.ts.map +1 -0
- package/dist/lib/media/track-verdict.js +52 -0
- package/dist/lib/media/track-verdict.js.map +1 -0
- package/dist/lib/media/transcript-moderation.d.ts +47 -0
- package/dist/lib/media/transcript-moderation.d.ts.map +1 -0
- package/dist/lib/media/transcript-moderation.js +70 -0
- package/dist/lib/media/transcript-moderation.js.map +1 -0
- package/dist/lib/media-handler.d.ts.map +1 -1
- package/dist/lib/media-handler.js +15 -9
- package/dist/lib/media-handler.js.map +1 -1
- package/dist/lib/notification-handler.d.ts +11 -4
- package/dist/lib/notification-handler.d.ts.map +1 -1
- package/dist/lib/notification-handler.js +161 -29
- package/dist/lib/notification-handler.js.map +1 -1
- package/dist/lib/post-handler.d.ts.map +1 -1
- package/dist/lib/post-handler.js +4 -1
- package/dist/lib/post-handler.js.map +1 -1
- package/dist/lib/realtime/block-store.d.ts +61 -0
- package/dist/lib/realtime/block-store.d.ts.map +1 -0
- package/dist/lib/realtime/block-store.js +0 -0
- package/dist/lib/realtime/block-store.js.map +1 -0
- package/dist/lib/realtime/channel.d.ts +34 -0
- package/dist/lib/realtime/channel.d.ts.map +1 -0
- package/dist/lib/realtime/channel.js +100 -0
- package/dist/lib/realtime/channel.js.map +1 -0
- package/dist/lib/realtime/delivery-policy.d.ts +51 -0
- package/dist/lib/realtime/delivery-policy.d.ts.map +1 -0
- package/dist/lib/realtime/delivery-policy.js +98 -0
- package/dist/lib/realtime/delivery-policy.js.map +1 -0
- package/dist/lib/realtime/index.d.ts +21 -0
- package/dist/lib/realtime/index.d.ts.map +1 -0
- package/dist/lib/realtime/index.js +39 -0
- package/dist/lib/realtime/index.js.map +1 -0
- package/dist/lib/realtime/no-op-transport.d.ts +10 -0
- package/dist/lib/realtime/no-op-transport.d.ts.map +1 -0
- package/dist/lib/realtime/no-op-transport.js +44 -0
- package/dist/lib/realtime/no-op-transport.js.map +1 -0
- package/dist/lib/realtime/poll-transport.d.ts +11 -0
- package/dist/lib/realtime/poll-transport.d.ts.map +1 -0
- package/dist/lib/realtime/poll-transport.js +68 -0
- package/dist/lib/realtime/poll-transport.js.map +1 -0
- package/dist/lib/realtime/push-notifier.d.ts +39 -0
- package/dist/lib/realtime/push-notifier.d.ts.map +1 -0
- package/dist/lib/realtime/push-notifier.js +76 -0
- package/dist/lib/realtime/push-notifier.js.map +1 -0
- package/dist/lib/realtime/realtime-transport.d.ts +2 -0
- package/dist/lib/realtime/realtime-transport.d.ts.map +1 -0
- package/dist/lib/realtime/realtime-transport.js +23 -0
- package/dist/lib/realtime/realtime-transport.js.map +1 -0
- package/dist/lib/realtime/setting-store.d.ts +30 -0
- package/dist/lib/realtime/setting-store.d.ts.map +1 -0
- package/dist/lib/realtime/setting-store.js +0 -0
- package/dist/lib/realtime/setting-store.js.map +1 -0
- package/dist/lib/realtime/types.d.ts +200 -0
- package/dist/lib/realtime/types.d.ts.map +1 -0
- package/dist/lib/realtime/types.js +61 -0
- package/dist/lib/realtime/types.js.map +1 -0
- package/dist/lib/routes/index.d.ts.map +1 -1
- package/dist/lib/routes/index.js +3 -0
- package/dist/lib/routes/index.js.map +1 -1
- package/dist/lib/routes/media.d.ts +21 -0
- package/dist/lib/routes/media.d.ts.map +1 -1
- package/dist/lib/routes/media.js +584 -483
- package/dist/lib/routes/media.js.map +1 -1
- package/dist/lib/routes/settings.d.ts +17 -0
- package/dist/lib/routes/settings.d.ts.map +1 -0
- package/dist/lib/routes/settings.js +187 -0
- package/dist/lib/routes/settings.js.map +1 -0
- package/dist/lib/services/image-normalizer.d.ts +64 -6
- package/dist/lib/services/image-normalizer.d.ts.map +1 -1
- package/dist/lib/services/image-normalizer.js +88 -6
- package/dist/lib/services/image-normalizer.js.map +1 -1
- package/dist/lib/services/media-upload-service.d.ts +2 -2
- package/dist/lib/services/media-upload-service.d.ts.map +1 -1
- package/dist/lib/services/media-upload-service.js +22 -21
- package/dist/lib/services/media-upload-service.js.map +1 -1
- package/dist/lib/tenant-scope.d.ts.map +1 -1
- package/dist/lib/tenant-scope.js +18 -1
- package/dist/lib/tenant-scope.js.map +1 -1
- package/package.json +23 -22
- package/prisma/migrations/20260620051144_add_encrypted_user_settings/migration.sql +24 -0
- package/prisma/migrations/20260620120000_add_blocked_users/migration.sql +29 -0
- package/prisma/migrations/20260625000000_media_tenant_scope_and_moderation_status/migration.sql +49 -0
- package/prisma/migrations/20260625000001_p0b_moderation_jobs/migration.sql +73 -0
- package/prisma/schema.prisma +133 -15
- package/src/lambda/media-completion-worker.ts +567 -0
- package/src/lambda/media-processing-worker.ts +508 -59
|
@@ -1,18 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* EXIF
|
|
2
|
+
* EXIF strip verification helper (T6).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The byte-level strip is a CONSEQUENCE of T7's re-encode: sharp re-encoding
|
|
5
|
+
* without `.withMetadata()` drops all embedded metadata (EXIF, GPS, ICC,
|
|
6
|
+
* maker-notes). This module provides the POST-encode assertion that the strip
|
|
7
|
+
* actually happened — used defensively at runtime and as the verification
|
|
8
|
+
* contract in tests.
|
|
6
9
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
10
|
+
* The old placeholder `stripEXIF()` is preserved below with a narrowed
|
|
11
|
+
* signature so existing callers compile; it delegates to the re-encode and is
|
|
12
|
+
* deprecated. New code must call `assertNoExif` after `reencodeImage`.
|
|
9
13
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - Prevents location data, device info, and timestamps from being embedded in images
|
|
14
|
-
* - Helps protect user privacy, especially for at-risk users
|
|
14
|
+
* GPS coordinates are NOT persisted: `gpsLatitude`/`gpsLongitude` columns
|
|
15
|
+
* were removed in T8's schema migration. Nothing in this file or the upload
|
|
16
|
+
* handler writes those fields.
|
|
15
17
|
*/
|
|
18
|
+
/**
|
|
19
|
+
* Parse the EXIF/IPTC/GPS/ICC metadata embedded in `imageBytes` and return
|
|
20
|
+
* it as a flat object. Returns `undefined` when exifr finds nothing.
|
|
21
|
+
*
|
|
22
|
+
* Exported for use in tests (assert the object is empty/undefined after
|
|
23
|
+
* re-encode) and as an optional runtime defensive check.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseMetadata(imageBytes: ArrayBuffer | Buffer | Uint8Array): Promise<Record<string, unknown> | undefined>;
|
|
26
|
+
/**
|
|
27
|
+
* Assert that `imageBytes` contains NO privacy-sensitive embedded metadata
|
|
28
|
+
* (EXIF GPS, ICC, maker-notes, camera info). Benign PNG structural fields
|
|
29
|
+
* (ImageWidth, ColorType, etc.) are excluded from this check since exifr
|
|
30
|
+
* parses them from the PNG format header, not from EXIF APP1 segments.
|
|
31
|
+
*
|
|
32
|
+
* Call this on the OUTPUT of `reencodeImage` to confirm the re-encode dropped
|
|
33
|
+
* all user/device metadata. Useful both in tests and as an optional defensive
|
|
34
|
+
* runtime check.
|
|
35
|
+
*
|
|
36
|
+
* @throws `Error` when privacy-sensitive metadata is present.
|
|
37
|
+
*/
|
|
38
|
+
export declare function assertNoExif(imageBytes: ArrayBuffer | Buffer | Uint8Array): Promise<void>;
|
|
39
|
+
/** @deprecated Legacy config interface — no longer has any effect. */
|
|
16
40
|
export interface EXIFStripperConfig {
|
|
17
41
|
enabled: boolean;
|
|
18
42
|
removeLocation: boolean;
|
|
@@ -20,18 +44,9 @@ export interface EXIFStripperConfig {
|
|
|
20
44
|
removeTimestamp: boolean;
|
|
21
45
|
}
|
|
22
46
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* to remove EXIF data from images before storing them in R2.
|
|
27
|
-
*
|
|
28
|
-
* NOTE: This is currently a placeholder implementation.
|
|
29
|
-
* When implementing, use a library like 'piexifjs' or 'exifr' to actually
|
|
30
|
-
* remove EXIF data from the image buffer.
|
|
31
|
-
*
|
|
32
|
-
* @param imageBuffer - Image buffer to process
|
|
33
|
-
* @param config - Stripping configuration
|
|
34
|
-
* @returns Processed image buffer (with EXIF removed if enabled)
|
|
47
|
+
* @deprecated The strip now happens as a side-effect of `reencodeImage`
|
|
48
|
+
* (T7). This function is a no-op passthrough kept solely so the existing
|
|
49
|
+
* test suite compiles without change. Do not call in new code.
|
|
35
50
|
*/
|
|
36
51
|
export declare function stripEXIF(imageBuffer: ArrayBuffer, config?: EXIFStripperConfig): Promise<ArrayBuffer>;
|
|
37
52
|
//# sourceMappingURL=exif-stripper.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exif-stripper.d.ts","sourceRoot":"","sources":["../../src/lib/exif-stripper.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"exif-stripper.d.ts","sourceRoot":"","sources":["../../src/lib/exif-stripper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,WAAW,GAAG,MAAM,GAAG,UAAU,GAC5C,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CA2B9C;AAiCD;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,WAAW,GAAG,MAAM,GAAG,UAAU,GAC5C,OAAO,CAAC,IAAI,CAAC,CAgBf;AAQD,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,WAAW,EAAE,WAAW,EACxB,MAAM,GAAE,kBAKP,GACA,OAAO,CAAC,WAAW,CAAC,CAKtB"}
|
|
@@ -1,59 +1,119 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* EXIF
|
|
2
|
+
* EXIF strip verification helper (T6).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The byte-level strip is a CONSEQUENCE of T7's re-encode: sharp re-encoding
|
|
5
|
+
* without `.withMetadata()` drops all embedded metadata (EXIF, GPS, ICC,
|
|
6
|
+
* maker-notes). This module provides the POST-encode assertion that the strip
|
|
7
|
+
* actually happened — used defensively at runtime and as the verification
|
|
8
|
+
* contract in tests.
|
|
6
9
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
10
|
+
* The old placeholder `stripEXIF()` is preserved below with a narrowed
|
|
11
|
+
* signature so existing callers compile; it delegates to the re-encode and is
|
|
12
|
+
* deprecated. New code must call `assertNoExif` after `reencodeImage`.
|
|
9
13
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - Prevents location data, device info, and timestamps from being embedded in images
|
|
14
|
-
* - Helps protect user privacy, especially for at-risk users
|
|
14
|
+
* GPS coordinates are NOT persisted: `gpsLatitude`/`gpsLongitude` columns
|
|
15
|
+
* were removed in T8's schema migration. Nothing in this file or the upload
|
|
16
|
+
* handler writes those fields.
|
|
15
17
|
*/
|
|
18
|
+
import exifr from "exifr";
|
|
16
19
|
/**
|
|
17
|
-
*
|
|
20
|
+
* Parse the EXIF/IPTC/GPS/ICC metadata embedded in `imageBytes` and return
|
|
21
|
+
* it as a flat object. Returns `undefined` when exifr finds nothing.
|
|
18
22
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
23
|
+
* Exported for use in tests (assert the object is empty/undefined after
|
|
24
|
+
* re-encode) and as an optional runtime defensive check.
|
|
25
|
+
*/
|
|
26
|
+
export async function parseMetadata(imageBytes) {
|
|
27
|
+
const buf = imageBytes instanceof Buffer
|
|
28
|
+
? imageBytes
|
|
29
|
+
: Buffer.from(imageBytes instanceof ArrayBuffer
|
|
30
|
+
? imageBytes
|
|
31
|
+
: imageBytes.buffer);
|
|
32
|
+
try {
|
|
33
|
+
// Parse all segments: EXIF (including GPS), IPTC, ICC, XMP.
|
|
34
|
+
const parsed = await exifr.parse(buf, {
|
|
35
|
+
gps: true,
|
|
36
|
+
icc: true,
|
|
37
|
+
iptc: true,
|
|
38
|
+
xmp: true,
|
|
39
|
+
makerNote: true,
|
|
40
|
+
userComment: true,
|
|
41
|
+
});
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// If exifr cannot parse the buffer it throws (unknown format, truncated
|
|
46
|
+
// data, etc.). Treat as "no metadata found" — the bytes are already
|
|
47
|
+
// re-encoded clean raster pixels.
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Keys that are considered benign structural PNG chunk fields — not EXIF or
|
|
53
|
+
* privacy-sensitive metadata. exifr parses these from the PNG IHDR/iCCP chunks,
|
|
54
|
+
* but they are format metadata, not user metadata.
|
|
55
|
+
*/
|
|
56
|
+
const PNG_STRUCTURAL_KEYS = new Set([
|
|
57
|
+
"ImageWidth",
|
|
58
|
+
"ImageHeight",
|
|
59
|
+
"BitDepth",
|
|
60
|
+
"ColorType",
|
|
61
|
+
"Compression",
|
|
62
|
+
"Filter",
|
|
63
|
+
"Interlace",
|
|
64
|
+
]);
|
|
65
|
+
/**
|
|
66
|
+
* Privacy-sensitive metadata keys that must NOT appear in re-encoded output.
|
|
67
|
+
* Any of these keys present after re-encode indicates `.withMetadata()` was
|
|
68
|
+
* mistakenly added or sharp is re-adding metadata.
|
|
69
|
+
*/
|
|
70
|
+
const SENSITIVE_EXIF_PREFIXES = [
|
|
71
|
+
"GPS", // GPS coordinates
|
|
72
|
+
"Make", // camera maker (maker-notes gateway)
|
|
73
|
+
"Model", // camera model
|
|
74
|
+
"Software",
|
|
75
|
+
"DateTime",
|
|
76
|
+
"Orientation",
|
|
77
|
+
"ExifIFD",
|
|
78
|
+
"InteropIFD",
|
|
79
|
+
];
|
|
80
|
+
/**
|
|
81
|
+
* Assert that `imageBytes` contains NO privacy-sensitive embedded metadata
|
|
82
|
+
* (EXIF GPS, ICC, maker-notes, camera info). Benign PNG structural fields
|
|
83
|
+
* (ImageWidth, ColorType, etc.) are excluded from this check since exifr
|
|
84
|
+
* parses them from the PNG format header, not from EXIF APP1 segments.
|
|
21
85
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
86
|
+
* Call this on the OUTPUT of `reencodeImage` to confirm the re-encode dropped
|
|
87
|
+
* all user/device metadata. Useful both in tests and as an optional defensive
|
|
88
|
+
* runtime check.
|
|
25
89
|
*
|
|
26
|
-
* @
|
|
27
|
-
|
|
28
|
-
|
|
90
|
+
* @throws `Error` when privacy-sensitive metadata is present.
|
|
91
|
+
*/
|
|
92
|
+
export async function assertNoExif(imageBytes) {
|
|
93
|
+
const metadata = await parseMetadata(imageBytes);
|
|
94
|
+
if (metadata === undefined)
|
|
95
|
+
return;
|
|
96
|
+
const sensitiveKeys = Object.keys(metadata).filter((k) => !PNG_STRUCTURAL_KEYS.has(k) &&
|
|
97
|
+
SENSITIVE_EXIF_PREFIXES.some((prefix) => k.startsWith(prefix)));
|
|
98
|
+
if (sensitiveKeys.length > 0) {
|
|
99
|
+
throw new Error(`Re-encoded image still contains privacy-sensitive metadata (keys: ${sensitiveKeys.join(", ")}). ` +
|
|
100
|
+
"Ensure reencodeImage is called without .withMetadata().");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* @deprecated The strip now happens as a side-effect of `reencodeImage`
|
|
105
|
+
* (T7). This function is a no-op passthrough kept solely so the existing
|
|
106
|
+
* test suite compiles without change. Do not call in new code.
|
|
29
107
|
*/
|
|
30
108
|
export async function stripEXIF(imageBuffer, config = {
|
|
31
109
|
enabled: true,
|
|
32
110
|
removeLocation: true,
|
|
33
111
|
removeDeviceInfo: true,
|
|
34
|
-
removeTimestamp: false,
|
|
112
|
+
removeTimestamp: false,
|
|
35
113
|
}) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// TODO: Implement actual EXIF stripping using a library
|
|
40
|
-
// FUTURE IMPLEMENTATION:
|
|
41
|
-
// 1. Install library: npm install piexifjs or npm install exifr
|
|
42
|
-
// 2. Parse EXIF data from image buffer
|
|
43
|
-
// 3. Remove location data if removeLocation=true
|
|
44
|
-
// 4. Remove device info if removeDeviceInfo=true
|
|
45
|
-
// 5. Remove timestamp if removeTimestamp=true
|
|
46
|
-
// 6. Reconstruct image buffer without EXIF data
|
|
47
|
-
//
|
|
48
|
-
// Example with piexifjs:
|
|
49
|
-
// import piexif from 'piexifjs';
|
|
50
|
-
// const exifObj = piexif.load(imageBuffer);
|
|
51
|
-
// if (config.removeLocation) delete exifObj['GPS'];
|
|
52
|
-
// if (config.removeDeviceInfo) delete exifObj['0th'][piexif.ImageIFD.Make];
|
|
53
|
-
// const newBuffer = piexif.dump(exifObj);
|
|
54
|
-
// return newBuffer;
|
|
55
|
-
// For now, return buffer as-is (no breaking changes)
|
|
56
|
-
// This allows the utility to exist and be called without affecting current behavior
|
|
114
|
+
// Pass-through: the strip is done by reencodeImage (T7). The config
|
|
115
|
+
// parameter is accepted to keep the call-site signature stable.
|
|
116
|
+
void config;
|
|
57
117
|
return imageBuffer;
|
|
58
118
|
}
|
|
59
119
|
//# sourceMappingURL=exif-stripper.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exif-stripper.js","sourceRoot":"","sources":["../../src/lib/exif-stripper.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"exif-stripper.js","sourceRoot":"","sources":["../../src/lib/exif-stripper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,UAA6C;IAE7C,MAAM,GAAG,GACP,UAAU,YAAY,MAAM;QAC1B,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,MAAM,CAAC,IAAI,CACT,UAAU,YAAY,WAAW;YAC/B,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,UAAU,CAAC,MAAM,CACtB,CAAC;IAER,IAAI,CAAC;QACH,4DAA4D;QAC5D,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE;YACpC,GAAG,EAAE,IAAI;YACT,GAAG,EAAE,IAAI;YACT,IAAI,EAAE,IAAI;YACV,GAAG,EAAE,IAAI;YACT,SAAS,EAAE,IAAI;YACf,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,OAAO,MAA6C,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;QACxE,oEAAoE;QACpE,kCAAkC;QAClC,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAClC,YAAY;IACZ,aAAa;IACb,UAAU;IACV,WAAW;IACX,aAAa;IACb,QAAQ;IACR,WAAW;CACZ,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,uBAAuB,GAAG;IAC9B,KAAK,EAAK,kBAAkB;IAC5B,MAAM,EAAI,qCAAqC;IAC/C,OAAO,EAAG,eAAe;IACzB,UAAU;IACV,UAAU;IACV,aAAa;IACb,SAAS;IACT,YAAY;CACb,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAA6C;IAE7C,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC;IACjD,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO;IAEnC,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAChD,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3B,uBAAuB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CACjE,CAAC;IAEF,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,qEAAqE,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;YAChG,yDAAyD,CAC5D,CAAC;IACJ,CAAC;AACH,CAAC;AAgBD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,WAAwB,EACxB,SAA6B;IAC3B,OAAO,EAAE,IAAI;IACb,cAAc,EAAE,IAAI;IACpB,gBAAgB,EAAE,IAAI;IACtB,eAAe,EAAE,KAAK;CACvB;IAED,oEAAoE;IACpE,gEAAgE;IAChE,KAAK,MAAM,CAAC;IACZ,OAAO,WAAW,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical tenant-scoped CAS key builder.
|
|
3
|
+
*
|
|
4
|
+
* Provides the ONE canonical key scheme (D18) for content-addressed storage,
|
|
5
|
+
* replacing multiple conflicting schemes. All storage paths MUST go through
|
|
6
|
+
* these builders — never hand-roll a key string elsewhere.
|
|
7
|
+
*
|
|
8
|
+
* Keys are pure functions: no I/O, no clock, no external dependencies.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Allowed derivative presets for a CAS object.
|
|
12
|
+
* Only values in this union are accepted by casKey(); all other strings are rejected.
|
|
13
|
+
*/
|
|
14
|
+
export type CasPreset = "thumbnail" | "optimized";
|
|
15
|
+
export type CasKeyError = {
|
|
16
|
+
kind: "invalid_hash";
|
|
17
|
+
raw: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "invalid_tenant_id";
|
|
20
|
+
raw: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "invalid_upload_id";
|
|
23
|
+
raw: string;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "invalid_preset";
|
|
26
|
+
raw: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Validate and lowercase-normalize an inbound content hash.
|
|
30
|
+
*
|
|
31
|
+
* Used at BOTH the upload boundary AND the serve entry point before any key
|
|
32
|
+
* construction or DB lookup. Lowercase normalization prevents a split CAS
|
|
33
|
+
* keyspace (two keys for the same bytes) caused by mixed-case hex.
|
|
34
|
+
*
|
|
35
|
+
* @returns The lowercase-normalized 64-char hex string, or a typed error.
|
|
36
|
+
*/
|
|
37
|
+
export declare function validateContentHash(raw: string): string | CasKeyError;
|
|
38
|
+
/**
|
|
39
|
+
* Build the canonical CAS storage key for a tenant-scoped object.
|
|
40
|
+
*
|
|
41
|
+
* Overload without preset: `cas/{tenantId}/{sha256}`
|
|
42
|
+
* Overload with preset: `cas/{tenantId}/{sha256}/{preset}`
|
|
43
|
+
*
|
|
44
|
+
* All inputs are validated against anchored allowlist regexes. The hash is
|
|
45
|
+
* always lowercase-normalized so that the CAS keyspace is consistent.
|
|
46
|
+
*
|
|
47
|
+
* @returns The key string, or a typed error describing which input was invalid.
|
|
48
|
+
*/
|
|
49
|
+
export declare function casKey(tenantId: string, sha256: string): string | CasKeyError;
|
|
50
|
+
export declare function casKey(tenantId: string, sha256: string, preset: CasPreset): string | CasKeyError;
|
|
51
|
+
/**
|
|
52
|
+
* Build the pending-upload staging key for a tenant-scoped upload.
|
|
53
|
+
*
|
|
54
|
+
* `pending/{tenantId}/{uploadId}`
|
|
55
|
+
*
|
|
56
|
+
* @returns The key string, or a typed error describing which input was invalid.
|
|
57
|
+
*/
|
|
58
|
+
export declare function pendingKey(tenantId: string, uploadId: string): string | CasKeyError;
|
|
59
|
+
/** Returns true when the result is a CasKeyError (not a key string). */
|
|
60
|
+
export declare function isCasKeyError(result: string | CasKeyError): result is CasKeyError;
|
|
61
|
+
/** Returns the set of valid preset values (for exhaustiveness checks). */
|
|
62
|
+
export declare function allPresets(): readonly CasPreset[];
|
|
63
|
+
//# sourceMappingURL=cas-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cas-keys.d.ts","sourceRoot":"","sources":["../../../src/lib/media/cas-keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,WAAW,CAAC;AAsClD,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAM5C;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,WAAW,CAMtB;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,MAAM,CACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,MAAM,GAAG,WAAW,CAAC;AACxB,wBAAgB,MAAM,CACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,GAChB,MAAM,GAAG,WAAW,CAAC;AA6BxB;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,WAAW,CAUtB;AAMD,wEAAwE;AACxE,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,GAAG,WAAW,GAC3B,MAAM,IAAI,WAAW,CAEvB;AAED,0EAA0E;AAC1E,wBAAgB,UAAU,IAAI,SAAS,SAAS,EAAE,CAEjD"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical tenant-scoped CAS key builder.
|
|
3
|
+
*
|
|
4
|
+
* Provides the ONE canonical key scheme (D18) for content-addressed storage,
|
|
5
|
+
* replacing multiple conflicting schemes. All storage paths MUST go through
|
|
6
|
+
* these builders — never hand-roll a key string elsewhere.
|
|
7
|
+
*
|
|
8
|
+
* Keys are pure functions: no I/O, no clock, no external dependencies.
|
|
9
|
+
*/
|
|
10
|
+
const CAS_PRESETS = new Set([
|
|
11
|
+
"thumbnail",
|
|
12
|
+
"optimized",
|
|
13
|
+
]);
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Validation regexes (anchored allowlists — NOT blocklists)
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/**
|
|
18
|
+
* SHA-256 hex digest: exactly 64 lowercase hex characters.
|
|
19
|
+
* Anchored so that path-traversal payloads, encoded dots, or extra characters
|
|
20
|
+
* never pass validation.
|
|
21
|
+
*/
|
|
22
|
+
const SHA256_RE = /^[0-9a-f]{64}$/;
|
|
23
|
+
/**
|
|
24
|
+
* CUID v1 format as generated by Prisma's @default(cuid()).
|
|
25
|
+
* Format: 'c' followed by 24 lowercase alphanumeric characters (base-36 alphabet).
|
|
26
|
+
* Total length: 25 characters.
|
|
27
|
+
* See prisma/schema.prisma (id @default(cuid()) fields).
|
|
28
|
+
*
|
|
29
|
+
* Anchored to prevent traversal/injection: only [a-z0-9] within a fixed length band.
|
|
30
|
+
*/
|
|
31
|
+
const TENANT_ID_RE = /^c[a-z0-9]{24}$/;
|
|
32
|
+
/**
|
|
33
|
+
* Upload session / pending key ID: we accept the same CUID format.
|
|
34
|
+
* Prisma UploadSession.id is also @default(cuid()).
|
|
35
|
+
*/
|
|
36
|
+
const UPLOAD_ID_RE = /^c[a-z0-9]{24}$/;
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// validateContentHash
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
/**
|
|
41
|
+
* Validate and lowercase-normalize an inbound content hash.
|
|
42
|
+
*
|
|
43
|
+
* Used at BOTH the upload boundary AND the serve entry point before any key
|
|
44
|
+
* construction or DB lookup. Lowercase normalization prevents a split CAS
|
|
45
|
+
* keyspace (two keys for the same bytes) caused by mixed-case hex.
|
|
46
|
+
*
|
|
47
|
+
* @returns The lowercase-normalized 64-char hex string, or a typed error.
|
|
48
|
+
*/
|
|
49
|
+
export function validateContentHash(raw) {
|
|
50
|
+
const normalized = raw.toLowerCase();
|
|
51
|
+
if (!SHA256_RE.test(normalized)) {
|
|
52
|
+
return { kind: "invalid_hash", raw };
|
|
53
|
+
}
|
|
54
|
+
return normalized;
|
|
55
|
+
}
|
|
56
|
+
export function casKey(tenantId, sha256, preset) {
|
|
57
|
+
if (!TENANT_ID_RE.test(tenantId)) {
|
|
58
|
+
return { kind: "invalid_tenant_id", raw: tenantId };
|
|
59
|
+
}
|
|
60
|
+
const normalized = sha256.toLowerCase();
|
|
61
|
+
if (!SHA256_RE.test(normalized)) {
|
|
62
|
+
return { kind: "invalid_hash", raw: sha256 };
|
|
63
|
+
}
|
|
64
|
+
if (preset !== undefined) {
|
|
65
|
+
if (!CAS_PRESETS.has(preset)) {
|
|
66
|
+
return { kind: "invalid_preset", raw: preset };
|
|
67
|
+
}
|
|
68
|
+
return `cas/${tenantId}/${normalized}/${preset}`;
|
|
69
|
+
}
|
|
70
|
+
return `cas/${tenantId}/${normalized}`;
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// pendingKey
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
/**
|
|
76
|
+
* Build the pending-upload staging key for a tenant-scoped upload.
|
|
77
|
+
*
|
|
78
|
+
* `pending/{tenantId}/{uploadId}`
|
|
79
|
+
*
|
|
80
|
+
* @returns The key string, or a typed error describing which input was invalid.
|
|
81
|
+
*/
|
|
82
|
+
export function pendingKey(tenantId, uploadId) {
|
|
83
|
+
if (!TENANT_ID_RE.test(tenantId)) {
|
|
84
|
+
return { kind: "invalid_tenant_id", raw: tenantId };
|
|
85
|
+
}
|
|
86
|
+
if (!UPLOAD_ID_RE.test(uploadId)) {
|
|
87
|
+
return { kind: "invalid_upload_id", raw: uploadId };
|
|
88
|
+
}
|
|
89
|
+
return `pending/${tenantId}/${uploadId}`;
|
|
90
|
+
}
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Type guard helpers
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
/** Returns true when the result is a CasKeyError (not a key string). */
|
|
95
|
+
export function isCasKeyError(result) {
|
|
96
|
+
return typeof result === "object";
|
|
97
|
+
}
|
|
98
|
+
/** Returns the set of valid preset values (for exhaustiveness checks). */
|
|
99
|
+
export function allPresets() {
|
|
100
|
+
return ["thumbnail", "optimized"];
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=cas-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cas-keys.js","sourceRoot":"","sources":["../../../src/lib/media/cas-keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAYH,MAAM,WAAW,GAAwB,IAAI,GAAG,CAAY;IAC1D,WAAW;IACX,WAAW;CACZ,CAAC,CAAC;AAEH,8EAA8E;AAC9E,4DAA4D;AAC5D,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAEnC;;;;;;;GAOG;AACH,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAEvC;;;GAGG;AACH,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAYvC,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,GAAW;IAEX,MAAM,UAAU,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IACrC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAChC,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC;IACvC,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AA0BD,MAAM,UAAU,MAAM,CACpB,QAAgB,EAChB,MAAc,EACd,MAAkB;IAElB,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,IAAI,EAAE,mBAAmB,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;IACtD,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IACxC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAChC,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IAC/C,CAAC;IAED,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;QACjD,CAAC;QACD,OAAO,OAAO,QAAQ,IAAI,UAAU,IAAI,MAAM,EAAE,CAAC;IACnD,CAAC;IAED,OAAO,OAAO,QAAQ,IAAI,UAAU,EAAE,CAAC;AACzC,CAAC;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACxB,QAAgB,EAChB,QAAgB;IAEhB,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,IAAI,EAAE,mBAAmB,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;IACtD,CAAC;IAED,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,IAAI,EAAE,mBAAmB,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;IACtD,CAAC;IAED,OAAO,WAAW,QAAQ,IAAI,QAAQ,EAAE,CAAC;AAC3C,CAAC;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,wEAAwE;AACxE,MAAM,UAAU,aAAa,CAC3B,MAA4B;IAE5B,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC;AACpC,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,UAAU;IACxB,OAAO,CAAC,WAAW,EAAE,WAAW,CAAU,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* classify-worker-error.ts — pure functional core, no I/O, no AWS SDK.
|
|
3
|
+
*
|
|
4
|
+
* Classifies any error thrown by a media-processing worker into one of two
|
|
5
|
+
* treatment buckets:
|
|
6
|
+
*
|
|
7
|
+
* "poison" — the media itself (or the job payload) is bad. A retry cannot
|
|
8
|
+
* help: the same bytes will fail the same way. The caller must
|
|
9
|
+
* ACK the message and move the object to moderationStatus=REVIEW
|
|
10
|
+
* for human triage, avoiding an infinite DLQ loop.
|
|
11
|
+
*
|
|
12
|
+
* "retryable" — a transient infrastructure fault: throttling, timeout, 5xx
|
|
13
|
+
* upstream, network blip, eventual-consistency AccessDenied.
|
|
14
|
+
* The caller lets SQS retry naturally; the 3-strike DLQ + alert
|
|
15
|
+
* acts as the upper bound.
|
|
16
|
+
*
|
|
17
|
+
* DEFAULT RULE (documented here — the only operative "fallback threshold"):
|
|
18
|
+
* When the error is unknown or cannot be classified with confidence, the
|
|
19
|
+
* function returns "retryable". Rationale: an unrecognised error is more
|
|
20
|
+
* likely to be a transient infra fault than a permanent media defect, and the
|
|
21
|
+
* DLQ + 3-strike alert provides a bounded safety net. Silently sending
|
|
22
|
+
* ambiguous errors to REVIEW would suppress alerts and hide systemic infra
|
|
23
|
+
* problems. This default is deliberately fail-OPEN for retry but
|
|
24
|
+
* fail-CLOSED for approval — REVIEW is never returned from this module; the
|
|
25
|
+
* caller owns the status mapping.
|
|
26
|
+
*
|
|
27
|
+
* PURITY GUARANTEE: this module has no I/O, no AWS SDK dependency, no
|
|
28
|
+
* Date.now/Math.random. node:crypto is NOT used. All inputs produce
|
|
29
|
+
* deterministic outputs.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* The two exclusive classifications.
|
|
33
|
+
*
|
|
34
|
+
* - `"poison"` — stop retrying; ACK + route to REVIEW.
|
|
35
|
+
* - `"retryable"` — let SQS retry; DLQ + alert is the backstop.
|
|
36
|
+
*/
|
|
37
|
+
export type ErrorClass = "poison" | "retryable";
|
|
38
|
+
/**
|
|
39
|
+
* Classifies `err` as `"poison"` or `"retryable"`.
|
|
40
|
+
*
|
|
41
|
+
* - TOTAL: never throws for any input (including null, undefined, non-Error objects).
|
|
42
|
+
* - FAIL-CLOSED on unknown: returns `"retryable"` when the error cannot be
|
|
43
|
+
* classified with confidence (DLQ + alert is the bounded backstop).
|
|
44
|
+
* - Poison wins over retryable when signals conflict (conservative for serving
|
|
45
|
+
* safety: a bad payload that also triggers throttle is still bad).
|
|
46
|
+
*/
|
|
47
|
+
export declare function classifyWorkerError(err: unknown): ErrorClass;
|
|
48
|
+
//# sourceMappingURL=classify-worker-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify-worker-error.d.ts","sourceRoot":"","sources":["../../../src/lib/media/classify-worker-error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,WAAW,CAAC;AAgLhD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CA+B5D"}
|