@modainteract/moda-interact-shared 5.11.1 → 5.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.
@@ -0,0 +1,976 @@
1
+ import {
2
+ ConversationPreparedProductImageSchema,
3
+ ConversationProductImageSourceSchema,
4
+ stableJson
5
+ } from "../../../chunk-SOAGFZ7A.js";
6
+ import "../../../chunk-W5Y5ZLGN.js";
7
+ import "../../../chunk-GCCT2BDE.js";
8
+ import "../../../chunk-4KSODB2R.js";
9
+ import "../../../chunk-AUS3WQIW.js";
10
+
11
+ // src/commerce/presentation/node/contracts.ts
12
+ var SHOP_BRAND_CANVAS = Object.freeze({ width: 1125, height: 600 });
13
+ var SHOP_BRAND_MAX_SOURCE_BYTES = 10 * 1024 * 1024;
14
+ var SHOP_BRAND_MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
15
+ var SHOP_BRAND_TARGET_OUTPUT_BYTES = 1024 * 1024;
16
+ var SHOP_BRAND_MAX_INPUT_PIXELS = 4e7;
17
+ var ShopBrandMediaError = class extends Error {
18
+ constructor(code, retryable = false, providerMessage) {
19
+ super(code);
20
+ this.code = code;
21
+ this.retryable = retryable;
22
+ this.providerMessage = providerMessage;
23
+ this.name = "ShopBrandMediaError";
24
+ }
25
+ code;
26
+ retryable;
27
+ providerMessage;
28
+ };
29
+ function boundedMediaProviderMessage(error) {
30
+ const raw = error instanceof Error ? error.message : typeof error === "string" ? error : void 0;
31
+ if (!raw) return void 0;
32
+ const message = [...raw].map((character) => {
33
+ const code = character.codePointAt(0);
34
+ return code <= 31 || code === 127 ? " " : character;
35
+ }).join("").replace(/\bhttps?:\/\/[^\s,;]+/gi, "[URL REDACTED]").replace(/(authorization\s*[:=]\s*)(?:bearer\s+)?[^\s,;]+/gi, "$1[REDACTED]").replace(/\b(bearer\s+)[A-Za-z0-9._~+/-]+=*/gi, "$1[REDACTED]").replace(/((?:access.?key(?:.?id)?|secret.?key|session.?token|security.?token|token|password|signature|credential|api.?key)\s*[:=]\s*)[^\s,;&"'<>]+/gi, "$1[REDACTED]").replace(/[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}/g, "[EMAIL REDACTED]").replace(/\b(?:\+?\d[\d ().-]{7,}\d)\b/g, "[PHONE REDACTED]").replace(/\s+/g, " ").trim().slice(0, 300);
36
+ return message || void 0;
37
+ }
38
+
39
+ // src/commerce/presentation/node/network-policy.ts
40
+ import { lookup as dnsLookup } from "dns/promises";
41
+ import { BlockList, isIP } from "net";
42
+ var MERCHANT_KNOWLEDGE_ACQUISITION_ERROR_CODES = [
43
+ "INVALID_URL",
44
+ "DENIED_DESTINATION",
45
+ "DNS_NO_ADDRESSES",
46
+ "DNS_TEMPORARY_FAILURE",
47
+ "DNS_FAILURE",
48
+ "TOO_MANY_REDIRECTS",
49
+ "INVALID_REDIRECT",
50
+ "REQUEST_DEADLINE",
51
+ "CONNECTION_PEER_MISMATCH",
52
+ "HTTP_STATUS_TRANSIENT",
53
+ "HTTP_STATUS_PERMANENT",
54
+ "UNSUPPORTED_MEDIA_TYPE",
55
+ "UNSUPPORTED_CONTENT_ENCODING",
56
+ "BODY_TOO_LARGE",
57
+ "RESPONSE_READ_FAILED"
58
+ ];
59
+ var MerchantKnowledgeAcquisitionError = class extends Error {
60
+ constructor(code, message, retryable = false) {
61
+ super(message);
62
+ this.code = code;
63
+ this.retryable = retryable;
64
+ this.name = "MerchantKnowledgeAcquisitionError";
65
+ }
66
+ code;
67
+ retryable;
68
+ };
69
+ var GLOBAL_IPV4 = new BlockList();
70
+ GLOBAL_IPV4.addSubnet("0.0.0.0", 0, "ipv4");
71
+ var PUBLIC_IPV6_ALLOCATION_CIDRS = [
72
+ ["2001:200::", 23],
73
+ ["2001:400::", 23],
74
+ ["2001:600::", 23],
75
+ ["2001:800::", 22],
76
+ ["2001:c00::", 23],
77
+ ["2001:e00::", 23],
78
+ ["2001:1200::", 23],
79
+ ["2001:1400::", 22],
80
+ ["2001:1800::", 23],
81
+ ["2001:1a00::", 23],
82
+ ["2001:1c00::", 22],
83
+ ["2001:2000::", 19],
84
+ ["2001:4000::", 23],
85
+ ["2001:4200::", 23],
86
+ ["2001:4400::", 23],
87
+ ["2001:4600::", 23],
88
+ ["2001:4800::", 23],
89
+ ["2001:4a00::", 23],
90
+ ["2001:4c00::", 23],
91
+ ["2001:5000::", 20],
92
+ ["2001:8000::", 19],
93
+ ["2001:a000::", 20],
94
+ ["2001:b000::", 20],
95
+ ["2003::", 18],
96
+ ["2400::", 12],
97
+ ["2410::", 12],
98
+ ["2600::", 12],
99
+ ["2610::", 23],
100
+ ["2620::", 23],
101
+ ["2630::", 12],
102
+ ["2800::", 12],
103
+ ["2a00::", 12],
104
+ ["2a10::", 12],
105
+ ["2c00::", 12]
106
+ ];
107
+ var PUBLIC_IPV6_ALLOCATIONS = new BlockList();
108
+ for (const [network, prefix] of PUBLIC_IPV6_ALLOCATION_CIDRS) {
109
+ PUBLIC_IPV6_ALLOCATIONS.addSubnet(network, prefix, "ipv6");
110
+ }
111
+ var NON_PUBLIC_SPECIAL_USE_CIDRS = [
112
+ ["0.0.0.0", 8, "ipv4"],
113
+ ["10.0.0.0", 8, "ipv4"],
114
+ ["100.64.0.0", 10, "ipv4"],
115
+ ["127.0.0.0", 8, "ipv4"],
116
+ ["169.254.0.0", 16, "ipv4"],
117
+ ["172.16.0.0", 12, "ipv4"],
118
+ ["192.0.0.0", 24, "ipv4"],
119
+ ["192.0.2.0", 24, "ipv4"],
120
+ ["192.31.196.0", 24, "ipv4"],
121
+ ["192.52.193.0", 24, "ipv4"],
122
+ ["192.88.99.0", 24, "ipv4"],
123
+ ["192.168.0.0", 16, "ipv4"],
124
+ ["192.175.48.0", 24, "ipv4"],
125
+ ["198.18.0.0", 15, "ipv4"],
126
+ ["198.51.100.0", 24, "ipv4"],
127
+ ["203.0.113.0", 24, "ipv4"],
128
+ ["224.0.0.0", 4, "ipv4"],
129
+ ["240.0.0.0", 4, "ipv4"],
130
+ ["64:ff9b::", 96, "ipv6"],
131
+ ["64:ff9b:1::", 48, "ipv6"],
132
+ ["100::", 64, "ipv6"],
133
+ ["2001::", 23, "ipv6"],
134
+ ["2001:db8::", 32, "ipv6"],
135
+ ["2002::", 16, "ipv6"],
136
+ ["2620:4f:8000::", 48, "ipv6"],
137
+ ["3fff::", 20, "ipv6"],
138
+ ["5f00::", 16, "ipv6"]
139
+ ];
140
+ var NON_PUBLIC_SPECIAL_USE = new BlockList();
141
+ for (const [network, prefix, family] of NON_PUBLIC_SPECIAL_USE_CIDRS) {
142
+ NON_PUBLIC_SPECIAL_USE.addSubnet(network, prefix, family);
143
+ }
144
+ var IPV4_MAPPED_IPV6 = new BlockList();
145
+ IPV4_MAPPED_IPV6.addSubnet("::ffff:0:0", 96, "ipv6");
146
+ var TEMPORARY_DNS_CODES = /* @__PURE__ */ new Set(["EAI_AGAIN", "ETIMEOUT", "ETIMEDOUT"]);
147
+ function errorCode(error) {
148
+ if (typeof error !== "object" || error === null || !("code" in error)) {
149
+ return void 0;
150
+ }
151
+ return typeof error.code === "string" ? error.code : void 0;
152
+ }
153
+ function hostnameWithoutBrackets(hostname) {
154
+ return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
155
+ }
156
+ function normalizeIpAddress(address) {
157
+ const family = isIP(address);
158
+ if (family === 4) return address;
159
+ if (family !== 6) return null;
160
+ try {
161
+ const canonical = new URL(`http://[${address}]/`).hostname.slice(1, -1);
162
+ if (!IPV4_MAPPED_IPV6.check(canonical, "ipv6")) return canonical;
163
+ const groups = canonical.split(":");
164
+ const first = Number.parseInt(groups.at(-2), 16);
165
+ const second = Number.parseInt(groups.at(-1), 16);
166
+ return [first >> 8, first & 255, second >> 8, second & 255].join(".");
167
+ } catch {
168
+ return null;
169
+ }
170
+ }
171
+ function isPublicIpAddress(address) {
172
+ if (address.includes("%")) return false;
173
+ const family = isIP(address);
174
+ if (family === 0) return false;
175
+ if (family === 6 && IPV4_MAPPED_IPV6.check(address, "ipv6")) {
176
+ const normalized = normalizeIpAddress(address);
177
+ return normalized !== null && isPublicIpAddress(normalized);
178
+ }
179
+ const allowed = family === 4 ? GLOBAL_IPV4.check(address, "ipv4") : PUBLIC_IPV6_ALLOCATIONS.check(address, "ipv6");
180
+ return allowed && !NON_PUBLIC_SPECIAL_USE.check(address, family === 4 ? "ipv4" : "ipv6");
181
+ }
182
+ async function resolveAllDnsAnswers(hostname) {
183
+ const addresses = await dnsLookup(hostname, { all: true, verbatim: true });
184
+ return addresses.map(({ address, family }) => ({
185
+ address,
186
+ family: family === 4 ? 4 : 6
187
+ }));
188
+ }
189
+ async function resolveAndValidateDestination(hostnameInput, resolver = resolveAllDnsAnswers) {
190
+ const hostname = hostnameWithoutBrackets(hostnameInput);
191
+ const literalFamily = isIP(hostname);
192
+ if (literalFamily !== 0) {
193
+ if (!isPublicIpAddress(hostname)) {
194
+ throw new MerchantKnowledgeAcquisitionError(
195
+ "DENIED_DESTINATION",
196
+ "The requested destination is not globally routable."
197
+ );
198
+ }
199
+ return {
200
+ address: normalizeIpAddress(hostname) ?? hostname,
201
+ family: literalFamily
202
+ };
203
+ }
204
+ let answers;
205
+ try {
206
+ answers = await resolver(hostname);
207
+ } catch (error) {
208
+ const code = errorCode(error);
209
+ if (code && TEMPORARY_DNS_CODES.has(code)) {
210
+ throw new MerchantKnowledgeAcquisitionError(
211
+ "DNS_TEMPORARY_FAILURE",
212
+ "Temporary DNS resolution failure.",
213
+ true
214
+ );
215
+ }
216
+ throw new MerchantKnowledgeAcquisitionError(
217
+ "DNS_FAILURE",
218
+ "DNS resolution failed."
219
+ );
220
+ }
221
+ if (answers.length === 0) {
222
+ throw new MerchantKnowledgeAcquisitionError(
223
+ "DNS_NO_ADDRESSES",
224
+ "The destination has no DNS addresses."
225
+ );
226
+ }
227
+ const validated = answers.map(({ address, family }) => {
228
+ const actualFamily = isIP(address);
229
+ if (family !== 4 && family !== 6 || actualFamily !== family || !isPublicIpAddress(address)) {
230
+ throw new MerchantKnowledgeAcquisitionError(
231
+ "DENIED_DESTINATION",
232
+ "The requested destination is not globally routable."
233
+ );
234
+ }
235
+ return {
236
+ address,
237
+ family
238
+ };
239
+ });
240
+ validated.sort(
241
+ (left, right) => left.family - right.family || (left.address < right.address ? -1 : left.address > right.address ? 1 : 0)
242
+ );
243
+ return validated[0];
244
+ }
245
+ function addressesMatch(left, right) {
246
+ const normalizedLeft = normalizeIpAddress(left);
247
+ const normalizedRight = normalizeIpAddress(right);
248
+ return normalizedLeft !== null && normalizedLeft === normalizedRight;
249
+ }
250
+ function createPinnedLookup(address) {
251
+ return (_hostname, options, callback) => {
252
+ if (options.all) {
253
+ callback(null, [{ address: address.address, family: address.family }]);
254
+ return;
255
+ }
256
+ callback(null, address.address, address.family);
257
+ };
258
+ }
259
+
260
+ // src/commerce/presentation/node/safe-fetch.ts
261
+ import https from "https";
262
+ import { isIP as isIP2 } from "net";
263
+ var MAX_URL_LENGTH = 2048;
264
+ var MAX_REDIRECTS = 3;
265
+ var DEADLINE_MS = 1e4;
266
+ var REDIRECTS = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
267
+ var TRANSIENT_HTTP = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
268
+ var TRANSIENT_NETWORK = /* @__PURE__ */ new Set(["ECONNRESET", "EAI_AGAIN", "ETIMEDOUT", "ETIMEOUT", "ECONNREFUSED", "ENETUNREACH"]);
269
+ var REQUEST_HEADERS = Object.freeze({
270
+ Accept: "image/avif,image/webp,image/png,image/jpeg,image/gif,image/svg+xml,*/*;q=0.2",
271
+ "Accept-Encoding": "identity",
272
+ "User-Agent": "ModaInteract-ShopBrandMedia/1.0"
273
+ });
274
+ function safeUrl(input) {
275
+ if (typeof input !== "string" || input.length > MAX_URL_LENGTH || /[\s\p{Cc}]/u.test(input)) {
276
+ throw new ShopBrandMediaError("UNSAFE_SOURCE");
277
+ }
278
+ try {
279
+ const url = new URL(input);
280
+ if (url.protocol !== "https:" || url.username || url.password || url.hash || url.port && url.port !== "443" || !url.hostname) {
281
+ throw new Error("unapproved-url");
282
+ }
283
+ return url;
284
+ } catch {
285
+ throw new ShopBrandMediaError("UNSAFE_SOURCE");
286
+ }
287
+ }
288
+ function header(headers, name) {
289
+ const value = headers[name];
290
+ return typeof value === "string" ? value : void 0;
291
+ }
292
+ function networkCode(error) {
293
+ return error !== null && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : void 0;
294
+ }
295
+ function sanitiseNetworkError(error) {
296
+ if (error instanceof ShopBrandMediaError) return error;
297
+ if (error instanceof MerchantKnowledgeAcquisitionError) {
298
+ if (error.code === "DENIED_DESTINATION") return new ShopBrandMediaError("UNSAFE_SOURCE", false, error.code);
299
+ if (error.code === "DNS_TEMPORARY_FAILURE") return new ShopBrandMediaError("DNS_TEMPORARY_FAILURE", true, error.code);
300
+ return new ShopBrandMediaError("DNS_FAILURE", error.retryable, error.code);
301
+ }
302
+ const code = networkCode(error);
303
+ return new ShopBrandMediaError(
304
+ "SOURCE_UNAVAILABLE",
305
+ TRANSIENT_NETWORK.has(code ?? ""),
306
+ boundedMediaProviderMessage(error)
307
+ );
308
+ }
309
+ function cancelled(signal) {
310
+ return signal.reason instanceof ShopBrandMediaError ? signal.reason : new ShopBrandMediaError("SOURCE_TIMEOUT", true);
311
+ }
312
+ function deadline(promise, signal) {
313
+ if (signal.aborted) return Promise.reject(cancelled(signal));
314
+ return new Promise((resolve, reject) => {
315
+ const onAbort = () => reject(cancelled(signal));
316
+ signal.addEventListener("abort", onAbort, { once: true });
317
+ promise.then(
318
+ (value) => {
319
+ signal.removeEventListener("abort", onAbort);
320
+ resolve(value);
321
+ },
322
+ (error) => {
323
+ signal.removeEventListener("abort", onAbort);
324
+ reject(error);
325
+ }
326
+ );
327
+ });
328
+ }
329
+ function pinnedHttpsRequest(input, signal) {
330
+ return new Promise((resolve, reject) => {
331
+ let settled = false;
332
+ const fail = (error) => {
333
+ if (settled) return;
334
+ settled = true;
335
+ reject(error);
336
+ };
337
+ const request = https.request({
338
+ hostname: input.hostname,
339
+ port: 443,
340
+ path: input.path,
341
+ method: "GET",
342
+ headers: input.headers,
343
+ lookup: createPinnedLookup(input.address),
344
+ agent: false,
345
+ minVersion: "TLSv1.2",
346
+ signal,
347
+ ...input.servername ? { servername: input.servername } : {}
348
+ }, (response) => {
349
+ const peerAddress = response.socket.remoteAddress;
350
+ if (!peerAddress || !addressesMatch(peerAddress, input.address.address)) {
351
+ response.destroy();
352
+ fail(new ShopBrandMediaError("UNSAFE_SOURCE"));
353
+ request.destroy();
354
+ return;
355
+ }
356
+ if (settled) {
357
+ response.destroy();
358
+ return;
359
+ }
360
+ settled = true;
361
+ resolve({
362
+ statusCode: response.statusCode ?? 0,
363
+ headers: response.headers,
364
+ body: response,
365
+ peerAddress
366
+ });
367
+ });
368
+ request.once("socket", (socket) => {
369
+ socket.once("secureConnect", () => {
370
+ if (!socket.remoteAddress || !addressesMatch(socket.remoteAddress, input.address.address)) {
371
+ fail(new ShopBrandMediaError("UNSAFE_SOURCE"));
372
+ request.destroy();
373
+ }
374
+ });
375
+ });
376
+ request.once("error", fail);
377
+ request.end();
378
+ });
379
+ }
380
+ async function boundedBody(response, signal, maxBytes) {
381
+ const rawSize = header(response.headers, "content-length");
382
+ if (rawSize !== void 0 && (!/^\d+$/.test(rawSize) || Number(rawSize) > maxBytes)) {
383
+ response.body.destroy();
384
+ throw new ShopBrandMediaError("SOURCE_TOO_LARGE");
385
+ }
386
+ const encoding = header(response.headers, "content-encoding");
387
+ if (encoding !== void 0 && encoding.toLowerCase().trim() !== "identity") {
388
+ response.body.destroy();
389
+ throw new ShopBrandMediaError("UNSUPPORTED_ENCODING");
390
+ }
391
+ const chunks = [];
392
+ let length = 0;
393
+ const onAbort = () => response.body.destroy(cancelled(signal));
394
+ signal.addEventListener("abort", onAbort, { once: true });
395
+ try {
396
+ for await (const chunk of response.body) {
397
+ if (signal.aborted) throw cancelled(signal);
398
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
399
+ length += bytes.length;
400
+ if (length > maxBytes) {
401
+ response.body.destroy();
402
+ throw new ShopBrandMediaError("SOURCE_TOO_LARGE");
403
+ }
404
+ chunks.push(bytes);
405
+ }
406
+ if (signal.aborted) throw cancelled(signal);
407
+ return Buffer.concat(chunks, length);
408
+ } catch (error) {
409
+ if (signal.aborted) throw cancelled(signal);
410
+ if (error instanceof ShopBrandMediaError) throw error;
411
+ throw sanitiseNetworkError(error);
412
+ } finally {
413
+ signal.removeEventListener("abort", onAbort);
414
+ }
415
+ }
416
+ function createSafeImageFetcher(options = {}) {
417
+ const resolveAll = options.resolveAll ?? resolveAllDnsAnswers;
418
+ const request = options.request ?? pinnedHttpsRequest;
419
+ const maxRedirects = options.maxRedirects ?? MAX_REDIRECTS;
420
+ const maxBytes = options.maxBytes ?? SHOP_BRAND_MAX_SOURCE_BYTES;
421
+ return {
422
+ async fetch(sourceUrl, externalSignal) {
423
+ const controller = new AbortController();
424
+ const abortFromCaller = () => controller.abort(externalSignal?.reason);
425
+ if (externalSignal?.aborted) abortFromCaller();
426
+ else externalSignal?.addEventListener("abort", abortFromCaller, { once: true });
427
+ const timer = setTimeout(() => controller.abort(new ShopBrandMediaError("SOURCE_TIMEOUT", true)), DEADLINE_MS);
428
+ try {
429
+ let next = safeUrl(sourceUrl);
430
+ for (let redirects = 0; ; redirects += 1) {
431
+ const host = next.hostname.startsWith("[") ? next.hostname.slice(1, -1) : next.hostname;
432
+ let address;
433
+ try {
434
+ address = await deadline(resolveAndValidateDestination(next.hostname, resolveAll), controller.signal);
435
+ } catch (error) {
436
+ throw sanitiseNetworkError(error);
437
+ }
438
+ const input = {
439
+ hostname: host,
440
+ path: `${next.pathname}${next.search}`,
441
+ address,
442
+ headers: REQUEST_HEADERS,
443
+ ...isIP2(host) === 0 ? { servername: host } : {}
444
+ };
445
+ let response;
446
+ try {
447
+ response = await deadline(request(input, controller.signal), controller.signal);
448
+ } catch (error) {
449
+ throw sanitiseNetworkError(error);
450
+ }
451
+ if (!addressesMatch(response.peerAddress, address.address)) {
452
+ response.body.destroy();
453
+ throw new ShopBrandMediaError("UNSAFE_SOURCE");
454
+ }
455
+ if (REDIRECTS.has(response.statusCode)) {
456
+ response.body.destroy();
457
+ if (redirects >= maxRedirects) throw new ShopBrandMediaError("UNSAFE_SOURCE");
458
+ const location = header(response.headers, "location");
459
+ if (!location) throw new ShopBrandMediaError("UNSAFE_SOURCE");
460
+ try {
461
+ next = safeUrl(new URL(location, next).toString());
462
+ } catch {
463
+ throw new ShopBrandMediaError("UNSAFE_SOURCE");
464
+ }
465
+ continue;
466
+ }
467
+ if (response.statusCode !== 200) {
468
+ response.body.destroy();
469
+ throw new ShopBrandMediaError(TRANSIENT_HTTP.has(response.statusCode) ? "SOURCE_UNAVAILABLE" : "SOURCE_HTTP_FAILURE", TRANSIENT_HTTP.has(response.statusCode));
470
+ }
471
+ const bytes = await boundedBody(response, controller.signal, maxBytes);
472
+ if (!bytes.length) throw new ShopBrandMediaError("INVALID_IMAGE");
473
+ const declaredContentType = header(response.headers, "content-type")?.split(";", 1)[0]?.trim().toLowerCase() ?? null;
474
+ return { bytes, declaredContentType };
475
+ }
476
+ } catch (error) {
477
+ if (controller.signal.aborted) throw cancelled(controller.signal);
478
+ if (error instanceof ShopBrandMediaError) throw error;
479
+ throw sanitiseNetworkError(error);
480
+ } finally {
481
+ clearTimeout(timer);
482
+ externalSignal?.removeEventListener("abort", abortFromCaller);
483
+ }
484
+ }
485
+ };
486
+ }
487
+
488
+ // src/commerce/presentation/node/image-transformer.ts
489
+ import sharp2 from "sharp";
490
+
491
+ // src/commerce/presentation/node/metadata-verifier.ts
492
+ import { createHash } from "crypto";
493
+ import sharp from "sharp";
494
+ async function verifyEncodedBrandImage(bytes, contentType) {
495
+ if (!bytes.length || bytes.length >= SHOP_BRAND_MAX_OUTPUT_BYTES || contentType !== "image/jpeg" && contentType !== "image/png") {
496
+ throw new ShopBrandMediaError("OUTPUT_TOO_LARGE");
497
+ }
498
+ try {
499
+ const actual = await sharp(bytes, {
500
+ limitInputPixels: SHOP_BRAND_CANVAS.width * SHOP_BRAND_CANVAS.height + 1,
501
+ failOn: "error",
502
+ animated: false
503
+ }).metadata();
504
+ const format = contentType === "image/jpeg" ? "jpeg" : "png";
505
+ if (actual.format !== format || actual.width !== SHOP_BRAND_CANVAS.width || actual.height !== SHOP_BRAND_CANVAS.height || (actual.pages ?? 1) !== 1 || actual.exif || actual.xmp || actual.iptc || actual.icc || actual.orientation && actual.orientation !== 1) {
506
+ throw new ShopBrandMediaError("OUTPUT_VERIFICATION_FAILED");
507
+ }
508
+ return {
509
+ bytes,
510
+ contentType,
511
+ width: actual.width,
512
+ height: actual.height,
513
+ sizeBytes: bytes.length,
514
+ contentSha256: createHash("sha256").update(bytes).digest("hex")
515
+ };
516
+ } catch {
517
+ throw new ShopBrandMediaError("OUTPUT_VERIFICATION_FAILED");
518
+ }
519
+ }
520
+
521
+ // src/commerce/presentation/node/image-transformer.ts
522
+ var SUPPORTED_SOURCES = /* @__PURE__ */ new Set(["jpeg", "png", "webp", "avif", "heif", "gif", "svg", "tiff"]);
523
+ function assertSelfContainedSvg(bytes) {
524
+ const markup = bytes.toString("utf8");
525
+ if (/(?:<!DOCTYPE|<!ENTITY|<\s*(?:script|image|use|foreignObject|iframe|object|embed|style|feImage)\b|(?:xlink:)?href\s*=|url\s*\(|@import\b|\bon[a-z]+\s*=)/i.test(markup)) {
526
+ throw new ShopBrandMediaError("INVALID_IMAGE");
527
+ }
528
+ }
529
+ function createShopBrandImageTransformer() {
530
+ return {
531
+ async transform(bytes, role) {
532
+ if (!Buffer.isBuffer(bytes) || !bytes.length || !["LOGO", "SQUARE_LOGO", "COVER_IMAGE"].includes(role)) {
533
+ throw new ShopBrandMediaError("INVALID_IMAGE");
534
+ }
535
+ let mime;
536
+ let output;
537
+ try {
538
+ if (bytes.subarray(0, 512).toString("utf8").replace(/^\uFEFF/, "").trimStart().startsWith("<")) {
539
+ assertSelfContainedSvg(bytes);
540
+ }
541
+ const source = sharp2(bytes, {
542
+ limitInputPixels: SHOP_BRAND_MAX_INPUT_PIXELS,
543
+ failOn: "error",
544
+ animated: false,
545
+ pages: 1
546
+ });
547
+ const info = await source.metadata();
548
+ if (!info.format || !SUPPORTED_SOURCES.has(info.format) || !info.width || !info.height) {
549
+ throw new ShopBrandMediaError("INVALID_IMAGE");
550
+ }
551
+ if (info.format === "svg") assertSelfContainedSvg(bytes);
552
+ if (info.width * info.height > SHOP_BRAND_MAX_INPUT_PIXELS) {
553
+ throw new ShopBrandMediaError("INPUT_PIXELS_EXCEEDED");
554
+ }
555
+ const transparent = Boolean(info.hasAlpha);
556
+ mime = transparent ? "image/png" : "image/jpeg";
557
+ const build = () => {
558
+ const image = sharp2(bytes, {
559
+ limitInputPixels: SHOP_BRAND_MAX_INPUT_PIXELS,
560
+ failOn: "error",
561
+ animated: false,
562
+ pages: 1
563
+ }).rotate().toColourspace("srgb").resize(SHOP_BRAND_CANVAS.width, SHOP_BRAND_CANVAS.height, {
564
+ fit: role === "COVER_IMAGE" ? "cover" : "contain",
565
+ position: "centre",
566
+ background: transparent ? { r: 255, g: 255, b: 255, alpha: 0 } : { r: 255, g: 255, b: 255, alpha: 1 },
567
+ withoutEnlargement: false
568
+ });
569
+ return transparent ? image : image.flatten({ background: "#ffffff" }).removeAlpha();
570
+ };
571
+ if (transparent) {
572
+ output = await build().png({ compressionLevel: 9, palette: false }).toBuffer();
573
+ if (output.length > SHOP_BRAND_TARGET_OUTPUT_BYTES) {
574
+ const compact = await build().png({
575
+ compressionLevel: 9,
576
+ palette: true,
577
+ quality: 100,
578
+ dither: 0
579
+ }).toBuffer();
580
+ if (compact.length < output.length && (compact.length <= SHOP_BRAND_TARGET_OUTPUT_BYTES || output.length >= SHOP_BRAND_MAX_OUTPUT_BYTES)) output = compact;
581
+ }
582
+ } else {
583
+ for (const quality of [86, 76, 66, 56, 46]) {
584
+ const encoded = await build().jpeg({
585
+ quality,
586
+ mozjpeg: true,
587
+ chromaSubsampling: "4:2:0"
588
+ }).toBuffer();
589
+ if (!output || encoded.length < output.length) output = encoded;
590
+ if (encoded.length <= SHOP_BRAND_TARGET_OUTPUT_BYTES) break;
591
+ }
592
+ }
593
+ } catch (error) {
594
+ if (error instanceof ShopBrandMediaError) throw error;
595
+ throw new ShopBrandMediaError("INVALID_IMAGE");
596
+ }
597
+ if (!output || output.length >= SHOP_BRAND_MAX_OUTPUT_BYTES) {
598
+ throw new ShopBrandMediaError("OUTPUT_TOO_LARGE");
599
+ }
600
+ return verifyEncodedBrandImage(output, mime);
601
+ }
602
+ };
603
+ }
604
+
605
+ // src/commerce/presentation/node/product-image-transformer.ts
606
+ import sharp3 from "sharp";
607
+ var WIDTH = 1125;
608
+ var HEIGHT = 600;
609
+ var QUALITY_STEPS = [86, 76, 66, 56, 46];
610
+ var SUPPORTED_SOURCES2 = /* @__PURE__ */ new Set(["jpeg", "png", "webp", "avif", "heif", "gif", "svg", "tiff"]);
611
+ function createOutreachProductImageTransformer() {
612
+ return {
613
+ async transform(bytes, signal) {
614
+ if (signal?.aborted) throw new ShopBrandMediaError("SOURCE_TIMEOUT", true);
615
+ if (!Buffer.isBuffer(bytes) || bytes.length === 0) throw new ShopBrandMediaError("INVALID_IMAGE");
616
+ try {
617
+ if (bytes.subarray(0, 512).toString("utf8").replace(/^\uFEFF/, "").trimStart().startsWith("<")) {
618
+ assertSelfContainedSvg(bytes);
619
+ }
620
+ const source = sharp3(bytes, {
621
+ limitInputPixels: false,
622
+ failOn: "error",
623
+ animated: false,
624
+ pages: 1
625
+ });
626
+ const cancelSource = () => {
627
+ source.destroy();
628
+ };
629
+ signal?.addEventListener("abort", cancelSource, { once: true });
630
+ let info;
631
+ try {
632
+ info = await source.metadata();
633
+ } finally {
634
+ signal?.removeEventListener("abort", cancelSource);
635
+ }
636
+ if (signal?.aborted) throw new ShopBrandMediaError("SOURCE_TIMEOUT", true);
637
+ if (!info.format || !SUPPORTED_SOURCES2.has(info.format) || !info.width || !info.height) {
638
+ throw new ShopBrandMediaError("INVALID_IMAGE");
639
+ }
640
+ if (info.width * info.height > SHOP_BRAND_MAX_INPUT_PIXELS) {
641
+ throw new ShopBrandMediaError("INPUT_PIXELS_EXCEEDED");
642
+ }
643
+ if (info.format === "svg") assertSelfContainedSvg(bytes);
644
+ let output;
645
+ for (const quality of QUALITY_STEPS) {
646
+ if (signal?.aborted) throw new ShopBrandMediaError("SOURCE_TIMEOUT", true);
647
+ const encoder = sharp3(bytes, {
648
+ limitInputPixels: SHOP_BRAND_MAX_INPUT_PIXELS,
649
+ failOn: "error",
650
+ animated: false,
651
+ pages: 1
652
+ }).rotate().toColourspace("srgb").resize(WIDTH, HEIGHT, {
653
+ fit: "contain",
654
+ position: "centre",
655
+ background: { r: 255, g: 255, b: 255, alpha: 1 },
656
+ withoutEnlargement: false
657
+ }).flatten({ background: "#ffffff" }).removeAlpha().jpeg({ quality, mozjpeg: true, chromaSubsampling: "4:2:0" });
658
+ const cancelEncoder = () => {
659
+ encoder.destroy();
660
+ };
661
+ signal?.addEventListener("abort", cancelEncoder, { once: true });
662
+ let encoded;
663
+ try {
664
+ encoded = await encoder.toBuffer();
665
+ } finally {
666
+ signal?.removeEventListener("abort", cancelEncoder);
667
+ }
668
+ if (signal?.aborted) throw new ShopBrandMediaError("SOURCE_TIMEOUT", true);
669
+ if (!output || encoded.length < output.length) output = encoded;
670
+ if (encoded.length <= 1024 * 1024) break;
671
+ }
672
+ if (!output || output.length >= SHOP_BRAND_MAX_OUTPUT_BYTES) {
673
+ throw new ShopBrandMediaError("OUTPUT_TOO_LARGE");
674
+ }
675
+ const verified = await verifyEncodedBrandImage(output, "image/jpeg");
676
+ if (signal?.aborted) throw new ShopBrandMediaError("SOURCE_TIMEOUT", true);
677
+ if (verified.contentType !== "image/jpeg" || verified.width !== WIDTH || verified.height !== HEIGHT || verified.bytes.subarray(0, 2).toString("hex") !== "ffd8") {
678
+ throw new ShopBrandMediaError("OUTPUT_VERIFICATION_FAILED");
679
+ }
680
+ return verified;
681
+ } catch (error) {
682
+ if (error instanceof ShopBrandMediaError) throw error;
683
+ throw new ShopBrandMediaError("INVALID_IMAGE", false, boundedMediaProviderMessage(error));
684
+ }
685
+ }
686
+ };
687
+ }
688
+
689
+ // src/commerce/presentation/node/r2-writer.ts
690
+ import { createHash as createHash2 } from "crypto";
691
+ import {
692
+ HeadObjectCommand,
693
+ PutObjectCommand,
694
+ S3Client
695
+ } from "@aws-sdk/client-s3";
696
+ var SHOP_BRAND_CACHE_CONTROL = "public, max-age=31536000, immutable";
697
+ function sanitizeR2ProviderMessage(error, credentials) {
698
+ if (!error || typeof error !== "object" || !("message" in error) || typeof error.message !== "string") return void 0;
699
+ let message = [...error.message].map((character) => {
700
+ const code = character.charCodeAt(0);
701
+ return code < 32 || code === 127 ? " " : character;
702
+ }).join("");
703
+ for (const secret of [credentials.secretAccessKey, credentials.accessKeyId]) {
704
+ if (secret) message = message.split(secret).join("[REDACTED]");
705
+ }
706
+ message = message.replace(/(authorization\s*[:=]\s*)(?:bearer\s+)?[^\s,;]+/gi, "$1[REDACTED]").replace(/\b(bearer\s+)[A-Za-z0-9._~+/-]+=*/gi, "$1[REDACTED]").replace(/((?:access.?key(?:.?id)?|secret.?key|session.?token|security.?token|token|password|signature|credential|api.?key)\s*[:=]\s*)[^\s,;&"'<>]+/gi, "$1[REDACTED]").replace(/\bhttps?:\/\/[^\s,;]+/gi, "[URL REDACTED]").replace(/\s+/g, " ").trim().slice(0, 300);
707
+ return message || void 0;
708
+ }
709
+ function createBrandR2S3Transport(credentials) {
710
+ const client = new S3Client({
711
+ endpoint: credentials.endpoint,
712
+ region: "auto",
713
+ forcePathStyle: true,
714
+ credentials: { accessKeyId: credentials.accessKeyId, secretAccessKey: credentials.secretAccessKey },
715
+ maxAttempts: 2
716
+ });
717
+ return {
718
+ close() {
719
+ client.destroy();
720
+ },
721
+ head(input, signal) {
722
+ return client.send(new HeadObjectCommand(input), signal ? { abortSignal: signal } : {});
723
+ },
724
+ async put(input, signal) {
725
+ await client.send(new PutObjectCommand(input), signal ? { abortSignal: signal } : {});
726
+ },
727
+ safeErrorMessage(error) {
728
+ return sanitizeR2ProviderMessage(error, credentials);
729
+ }
730
+ };
731
+ }
732
+ function httpStatus(error) {
733
+ if (!error || typeof error !== "object" || !("$metadata" in error)) return void 0;
734
+ const metadata = error.$metadata;
735
+ if (!metadata || typeof metadata !== "object" || !("httpStatusCode" in metadata)) return void 0;
736
+ return typeof metadata.httpStatusCode === "number" ? metadata.httpStatusCode : void 0;
737
+ }
738
+ function isMissing(error) {
739
+ if (httpStatus(error) === 404) return true;
740
+ return error !== null && typeof error === "object" && "name" in error && (error.name === "NotFound" || error.name === "NoSuchKey");
741
+ }
742
+ function r2Failure(error, transport) {
743
+ if (error instanceof ShopBrandMediaError) return error;
744
+ const status = httpStatus(error);
745
+ return new ShopBrandMediaError(
746
+ "R2_UNAVAILABLE",
747
+ status === void 0 || status >= 500 || status === 429 || status === 408 || status === 409,
748
+ transport.safeErrorMessage?.(error)
749
+ );
750
+ }
751
+ function validBucket(bucket) {
752
+ return /^(?!.*\.\.)(?!.*\.-|.*-\.)[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(bucket) && !/^\d+\.\d+\.\d+\.\d+$/.test(bucket);
753
+ }
754
+ async function putGuardedImmutableR2Object(input) {
755
+ const { bucket, objectKey, image } = input;
756
+ const dimensions = input.expectedDimensions ?? { width: 1125, height: 600 };
757
+ if (!validBucket(bucket) || !input.acceptsKey(objectKey) || !["image/jpeg", "image/png"].includes(image.contentType) || !/^[a-f0-9]{64}$/.test(image.contentSha256) || createHash2("sha256").update(image.bytes).digest("hex") !== image.contentSha256 || image.sizeBytes !== image.bytes.length || !Number.isSafeInteger(dimensions.width) || !Number.isSafeInteger(dimensions.height) || dimensions.width <= 0 || dimensions.height <= 0 || dimensions.width * dimensions.height > 4e7 || image.width !== dimensions.width || image.height !== dimensions.height) {
758
+ throw new ShopBrandMediaError("OUTPUT_VERIFICATION_FAILED");
759
+ }
760
+ const transport = typeof input.transportOrFactory === "function" ? await input.transportOrFactory() : input.transportOrFactory;
761
+ const head = async () => {
762
+ try {
763
+ return input.signal ? await transport.head({ Bucket: bucket, Key: objectKey }, input.signal) : await transport.head({ Bucket: bucket, Key: objectKey });
764
+ } catch (error) {
765
+ if (isMissing(error)) return null;
766
+ throw r2Failure(error, transport);
767
+ }
768
+ };
769
+ let existing = await head();
770
+ if (existing === null) {
771
+ try {
772
+ const putInput = {
773
+ Bucket: bucket,
774
+ Key: objectKey,
775
+ Body: image.bytes,
776
+ ContentType: image.contentType,
777
+ ContentLength: image.sizeBytes,
778
+ CacheControl: SHOP_BRAND_CACHE_CONTROL,
779
+ Metadata: { sha256: image.contentSha256 },
780
+ IfNoneMatch: "*"
781
+ };
782
+ if (input.signal) await transport.put(putInput, input.signal);
783
+ else await transport.put(putInput);
784
+ } catch (error) {
785
+ if (httpStatus(error) !== 412 && httpStatus(error) !== 409) throw r2Failure(error, transport);
786
+ }
787
+ existing = await head();
788
+ }
789
+ if (existing === null) throw new ShopBrandMediaError("R2_UNAVAILABLE", true);
790
+ if (existing.ContentType !== image.contentType || existing.ContentLength !== image.sizeBytes || existing.Metadata?.sha256 !== image.contentSha256 || existing.CacheControl !== SHOP_BRAND_CACHE_CONTROL) {
791
+ throw new ShopBrandMediaError("R2_OBJECT_MISMATCH");
792
+ }
793
+ }
794
+
795
+ // src/commerce/presentation/node/public-probe.ts
796
+ import { createHash as createHash3 } from "crypto";
797
+ function createShopBrandPublicMediaProbe(fetcher) {
798
+ return {
799
+ async verify({ url, image }, signal) {
800
+ try {
801
+ const result = await fetcher.fetch(url, signal);
802
+ if (result.declaredContentType !== image.contentType || result.bytes.length !== image.sizeBytes || createHash3("sha256").update(result.bytes).digest("hex") !== image.contentSha256) {
803
+ throw new ShopBrandMediaError("PUBLIC_MEDIA_MISMATCH", true);
804
+ }
805
+ } catch (error) {
806
+ if (error instanceof ShopBrandMediaError && error.code === "PUBLIC_MEDIA_MISMATCH") throw error;
807
+ throw new ShopBrandMediaError(
808
+ "PUBLIC_MEDIA_UNAVAILABLE",
809
+ true,
810
+ (error instanceof ShopBrandMediaError ? error.providerMessage : void 0) ?? boundedMediaProviderMessage(error)
811
+ );
812
+ }
813
+ }
814
+ };
815
+ }
816
+
817
+ // src/commerce/presentation/node/product-image.ts
818
+ import { createHash as createHash4 } from "crypto";
819
+ import { isIP as isIP3 } from "net";
820
+ var KEY = /^conversation-products\/v1\/[a-f0-9]{64}\/[a-f0-9]{64}\/[a-f0-9]{64}\.jpg$/;
821
+ var isConversationProductObjectKey = (key) => KEY.test(key);
822
+ var sha256 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
823
+ function conversationProductObjectKey(source, contentSha256) {
824
+ const parsed = ConversationProductImageSourceSchema.parse(source);
825
+ if (!/^[a-f0-9]{64}$/.test(contentSha256)) throw new ShopBrandMediaError("OUTPUT_VERIFICATION_FAILED");
826
+ return `conversation-products/v1/${sha256(parsed.shopId)}/${sha256(stableJson({ productId: parsed.productId, variantId: parsed.variantId }))}/${contentSha256}.jpg`;
827
+ }
828
+ function publicOrigin(value) {
829
+ try {
830
+ const url = new URL(value), host = url.hostname.replace(/^\[|\]$/g, "");
831
+ if (url.protocol !== "https:" || url.username || url.password || url.port || url.pathname !== "/" || url.search || url.hash || value !== url.origin && value !== url.origin + "/" || host === "localhost" || host.endsWith(".localhost") || isIP3(host) && !isPublicIpAddress(host)) return null;
832
+ return url.origin;
833
+ } catch {
834
+ return null;
835
+ }
836
+ }
837
+ var MediaDeadline = class extends Error {
838
+ };
839
+ function bounded(operation, signal) {
840
+ if (signal.aborted) return Promise.reject(new MediaDeadline());
841
+ return new Promise((resolve, reject) => {
842
+ const abort = () => reject(new MediaDeadline());
843
+ signal.addEventListener("abort", abort, { once: true });
844
+ Promise.resolve().then(() => {
845
+ if (signal.aborted) throw new MediaDeadline();
846
+ return operation();
847
+ }).then(
848
+ (value) => {
849
+ signal.removeEventListener("abort", abort);
850
+ if (signal.aborted) reject(new MediaDeadline());
851
+ else resolve(value);
852
+ },
853
+ (error) => {
854
+ signal.removeEventListener("abort", abort);
855
+ reject(error);
856
+ }
857
+ );
858
+ });
859
+ }
860
+ async function prepareConversationProductImage(input, deps = {}) {
861
+ const now = deps.now ?? Date.now, start = now();
862
+ const duration = () => {
863
+ const elapsed = now() - start;
864
+ return Number.isFinite(elapsed) ? Math.max(0, Math.min(5e3, elapsed)) : 0;
865
+ };
866
+ let stage = "CONFIGURATION";
867
+ const omitted = (reason) => ({ kind: "OMITTED", reason, stage, durationMs: duration() });
868
+ const budget = input.deadlineAt === void 0 ? 5e3 : Math.min(5e3, input.deadlineAt - start - 1e3);
869
+ if (!Number.isFinite(start) || !Number.isFinite(budget) || budget <= 0 || input.signal?.aborted) return omitted("MEDIA_DEADLINE");
870
+ const source = ConversationProductImageSourceSchema.safeParse(input.source);
871
+ if (!source.success) return omitted("UNSAFE_IMAGE_SOURCE");
872
+ const origin = publicOrigin(input.publicOrigin);
873
+ if (!origin || typeof input.bucketName !== "string" || !validBucket(input.bucketName) || typeof input.r2?.accessKeyId !== "string" || !input.r2.accessKeyId || typeof input.r2.secretAccessKey !== "string" || !input.r2.secretAccessKey) return omitted("CONFIGURATION_UNAVAILABLE");
874
+ try {
875
+ const endpoint = new URL(input.r2.endpoint);
876
+ if (endpoint.protocol !== "https:" || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) return omitted("CONFIGURATION_UNAVAILABLE");
877
+ } catch {
878
+ return omitted("CONFIGURATION_UNAVAILABLE");
879
+ }
880
+ const controller = new AbortController();
881
+ const abort = () => controller.abort();
882
+ input.signal?.addEventListener("abort", abort, { once: true });
883
+ if (input.signal?.aborted) abort();
884
+ const timer = setTimeout(abort, budget);
885
+ const checkDeadline = () => {
886
+ if (now() >= start + budget) abort();
887
+ if (controller.signal.aborted) throw new MediaDeadline();
888
+ };
889
+ let transport;
890
+ try {
891
+ checkDeadline();
892
+ stage = "SOURCE";
893
+ const fetched = await bounded(() => (deps.fetcher ?? createSafeImageFetcher()).fetch(source.data.sourceUrl, controller.signal), controller.signal);
894
+ checkDeadline();
895
+ stage = "TRANSFORM";
896
+ const image = await bounded(() => (deps.transformer ?? createOutreachProductImageTransformer()).transform(fetched.bytes, controller.signal), controller.signal);
897
+ checkDeadline();
898
+ const objectKey = conversationProductObjectKey(source.data, image.contentSha256);
899
+ const prepared = ConversationPreparedProductImageSchema.parse({
900
+ schemaVersion: 1,
901
+ shopId: source.data.shopId,
902
+ productId: source.data.productId,
903
+ variantId: source.data.variantId,
904
+ publicUrl: `${origin}/${objectKey}`,
905
+ objectKey,
906
+ contentSha256: image.contentSha256,
907
+ contentType: image.contentType,
908
+ byteLength: image.sizeBytes,
909
+ width: image.width,
910
+ height: image.height
911
+ });
912
+ stage = "R2";
913
+ if (controller.signal.aborted) throw new MediaDeadline();
914
+ transport = (deps.transportFactory ?? createBrandR2S3Transport)(input.r2);
915
+ checkDeadline();
916
+ await bounded(() => putGuardedImmutableR2Object({
917
+ transportOrFactory: transport,
918
+ bucket: input.bucketName,
919
+ objectKey,
920
+ image,
921
+ acceptsKey: (key) => isConversationProductObjectKey(key) && key === objectKey,
922
+ signal: controller.signal
923
+ }), controller.signal);
924
+ checkDeadline();
925
+ stage = "PUBLIC_PROBE";
926
+ const probe = deps.probe ?? createShopBrandPublicMediaProbe(createSafeImageFetcher({ maxRedirects: 0, maxBytes: 5 * 1024 * 1024 - 1 }));
927
+ await bounded(() => probe.verify({ url: prepared.publicUrl, image }, controller.signal), controller.signal);
928
+ checkDeadline();
929
+ stage = "READY";
930
+ return { kind: "READY", image: prepared, stage, durationMs: duration() };
931
+ } catch (error) {
932
+ if (controller.signal.aborted || error instanceof MediaDeadline || now() >= start + budget) return omitted("MEDIA_DEADLINE");
933
+ if (stage === "SOURCE") return omitted(error instanceof ShopBrandMediaError && error.code === "UNSAFE_SOURCE" ? "UNSAFE_IMAGE_SOURCE" : "SOURCE_UNAVAILABLE");
934
+ if (stage === "TRANSFORM") return omitted("IMAGE_TRANSFORM_FAILED");
935
+ return omitted(stage === "PUBLIC_PROBE" ? "PUBLIC_IMAGE_UNAVAILABLE" : "R2_UNAVAILABLE");
936
+ } finally {
937
+ clearTimeout(timer);
938
+ input.signal?.removeEventListener("abort", abort);
939
+ try {
940
+ transport?.close?.();
941
+ } catch {
942
+ }
943
+ }
944
+ }
945
+ export {
946
+ MERCHANT_KNOWLEDGE_ACQUISITION_ERROR_CODES,
947
+ MerchantKnowledgeAcquisitionError,
948
+ SHOP_BRAND_CACHE_CONTROL,
949
+ SHOP_BRAND_CANVAS,
950
+ SHOP_BRAND_MAX_INPUT_PIXELS,
951
+ SHOP_BRAND_MAX_OUTPUT_BYTES,
952
+ SHOP_BRAND_MAX_SOURCE_BYTES,
953
+ SHOP_BRAND_TARGET_OUTPUT_BYTES,
954
+ ShopBrandMediaError,
955
+ addressesMatch,
956
+ assertSelfContainedSvg,
957
+ boundedMediaProviderMessage,
958
+ conversationProductObjectKey,
959
+ createBrandR2S3Transport,
960
+ createOutreachProductImageTransformer,
961
+ createPinnedLookup,
962
+ createSafeImageFetcher,
963
+ createShopBrandImageTransformer,
964
+ createShopBrandPublicMediaProbe,
965
+ isConversationProductObjectKey,
966
+ isPublicIpAddress,
967
+ validBucket as isValidMediaBucket,
968
+ normalizeIpAddress,
969
+ prepareConversationProductImage,
970
+ putGuardedImmutableR2Object,
971
+ resolveAllDnsAnswers,
972
+ resolveAndValidateDestination,
973
+ sanitizeR2ProviderMessage,
974
+ verifyEncodedBrandImage
975
+ };
976
+ //# sourceMappingURL=index.js.map