@calmlens/sdk 0.16.3 → 0.18.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.
Files changed (114) hide show
  1. package/README.md +425 -181
  2. package/bin/calmlens.js +16016 -0
  3. package/bin/package.json +1 -0
  4. package/cjs/BrowserUpload-BT091eOB.d.ts +11 -0
  5. package/cjs/BrowserUpload-CQwXEGFv.js +10 -0
  6. package/cjs/CalmLensAppClient-CHQGu-Do.js +89 -0
  7. package/cjs/CalmLensAppClient-CV_K26Ce.d.ts +40 -0
  8. package/cjs/CalmLensConnection-B8Bu2j3y.js +659 -0
  9. package/cjs/CalmLensServerClient-C5o2A8MW.js +645 -0
  10. package/cjs/CalmLensServerClient-JtP687a3.d.ts +235 -0
  11. package/cjs/CalmLensTypes-9Vt16pc7.d.ts +40400 -0
  12. package/cjs/Classification-94kSYWZw-CMnQKW13.d.ts +5873 -0
  13. package/cjs/CryptoUtils-B_x8lMIx-YBW1-bvL.js +42 -0
  14. package/cjs/FileIcons-Ob003MdS.js +175 -0
  15. package/cjs/Jurisdiction-FusEXb4U-zVs21tDv.js +91 -0
  16. package/cjs/ModerationContext-9lGm-Mna-CcmbRnOy.d.ts +67 -0
  17. package/cjs/ModerationContextLimits-DelWfmiJ.js +1311 -0
  18. package/cjs/ReactUpload-CAyCsyG6.d.ts +17 -0
  19. package/cjs/ReactUpload-Cfg5JYUq.js +54 -0
  20. package/cjs/TestTriggers-By07LR31-CmK0yTyR.d.ts +271 -0
  21. package/cjs/TestTriggers-DTc_iMJF-BjMPq4_3.js +1327 -0
  22. package/cjs/UploadController-BGO1eyzb.js +618 -0
  23. package/{esm/index-C9Xw1aid.d.ts → cjs/WebhookPayload-2aL31v0J.d.ts} +131 -155
  24. package/cjs/app.d.ts +7 -44
  25. package/cjs/app.js +11 -89
  26. package/cjs/elements.d.ts +88 -0
  27. package/cjs/elements.js +555 -0
  28. package/cjs/file-icons.d.ts +67 -0
  29. package/cjs/file-icons.js +10 -0
  30. package/cjs/index-CI4BzN4W.d.ts +252 -0
  31. package/cjs/index.d.ts +6 -4
  32. package/cjs/index.js +9 -4
  33. package/cjs/react-native.d.ts +56 -0
  34. package/cjs/react-native.js +495 -0
  35. package/cjs/react.d.ts +30 -0
  36. package/cjs/react.js +370 -0
  37. package/cjs/server.d.ts +12 -191
  38. package/cjs/server.js +18 -378
  39. package/cjs/test-inputs.d.ts +2 -2
  40. package/cjs/test-inputs.js +9 -264
  41. package/cjs/testing.d.ts +285 -190
  42. package/cjs/testing.js +2393 -1203
  43. package/cjs/ui-CC1GOoMg.js +583 -0
  44. package/cjs/ui-wIiMfqMB.d.ts +526 -0
  45. package/cjs/ui.d.ts +2 -0
  46. package/cjs/ui.js +27 -0
  47. package/cjs/vue.d.ts +490 -0
  48. package/cjs/vue.js +419 -0
  49. package/esm/BrowserUpload-BT091eOB.d.ts +11 -0
  50. package/esm/BrowserUpload-D2xBWqhm.js +5 -0
  51. package/esm/CalmLensAppClient-CV_K26Ce.d.ts +40 -0
  52. package/esm/CalmLensAppClient-DuFas_dW.js +84 -0
  53. package/esm/CalmLensConnection-MxwwLXdl.js +582 -0
  54. package/esm/CalmLensServerClient-D80_mKpW.js +586 -0
  55. package/esm/CalmLensServerClient-JtP687a3.d.ts +235 -0
  56. package/esm/CalmLensTypes-9Vt16pc7.d.ts +40400 -0
  57. package/esm/Classification-94kSYWZw-CMnQKW13.d.ts +5873 -0
  58. package/esm/CryptoUtils-B_x8lMIx-DhuWGzqn.js +25 -0
  59. package/esm/FileIcons-M6dzjFxd.js +128 -0
  60. package/esm/Jurisdiction-FusEXb4U-1ov6m5yE.js +46 -0
  61. package/esm/ModerationContext-9lGm-Mna-CcmbRnOy.d.ts +67 -0
  62. package/esm/ModerationContextLimits-BaX2QnU8.js +1114 -0
  63. package/esm/ReactUpload-CAyCsyG6.d.ts +17 -0
  64. package/esm/ReactUpload-CL2XC8WS.js +37 -0
  65. package/esm/TestTriggers-By07LR31-CmK0yTyR.d.ts +271 -0
  66. package/esm/TestTriggers-DTc_iMJF-HfB533Gx.js +1267 -0
  67. package/esm/UploadController-qjoYGCwx.js +589 -0
  68. package/{cjs/index-C9Xw1aid.d.ts → esm/WebhookPayload-2aL31v0J.d.ts} +131 -155
  69. package/esm/app.d.ts +7 -44
  70. package/esm/app.js +3 -86
  71. package/esm/elements.d.ts +88 -0
  72. package/esm/elements.js +528 -0
  73. package/esm/file-icons.d.ts +67 -0
  74. package/esm/file-icons.js +1 -0
  75. package/esm/index-CI4BzN4W.d.ts +252 -0
  76. package/esm/index.d.ts +6 -4
  77. package/esm/index.js +1 -1
  78. package/esm/react-native.d.ts +56 -0
  79. package/esm/react-native.js +467 -0
  80. package/esm/react.d.ts +30 -0
  81. package/esm/react.js +343 -0
  82. package/esm/server.d.ts +12 -191
  83. package/esm/server.js +3 -369
  84. package/esm/styles.css +147 -0
  85. package/esm/svelte/CalmLensMedia.svelte +134 -0
  86. package/esm/svelte/CalmLensUploader.svelte +175 -0
  87. package/esm/svelte/Icon.svelte +7 -0
  88. package/esm/svelte/index.d.ts +37 -0
  89. package/esm/svelte/index.js +6 -0
  90. package/esm/test-inputs.d.ts +2 -2
  91. package/esm/test-inputs.js +1 -256
  92. package/esm/testing.d.ts +285 -190
  93. package/esm/testing.js +2329 -1122
  94. package/esm/ui-CCY3yXFn.js +458 -0
  95. package/esm/ui-wIiMfqMB.d.ts +526 -0
  96. package/esm/ui.d.ts +2 -0
  97. package/esm/ui.js +3 -0
  98. package/esm/vue.d.ts +490 -0
  99. package/esm/vue.js +392 -0
  100. package/package.json +118 -13
  101. package/cjs/CalmLensConnection-BNUHp_AQ.js +0 -234
  102. package/cjs/CalmLensUtils-o171ownK.js +0 -699
  103. package/cjs/CalmLensVersion-DwhgxQK5.js +0 -480
  104. package/cjs/CalmLensWebhooks-BKrq30Yp.js +0 -201
  105. package/cjs/Classification-BNhgwGEm-DmA9VEuE.d.ts +0 -4806
  106. package/cjs/TestTriggers-CRKtulyp-vrzEq2wm.d.ts +0 -44
  107. package/cjs/WebhookPayload-CJoIkGBD.d.ts +0 -11753
  108. package/esm/CalmLensConnection-XTqmlwzG.js +0 -193
  109. package/esm/CalmLensUtils-DsF8Aa_x.js +0 -574
  110. package/esm/CalmLensVersion-DcifRKsx.js +0 -415
  111. package/esm/CalmLensWebhooks-BvGyk5d3.js +0 -148
  112. package/esm/Classification-BNhgwGEm-DmA9VEuE.d.ts +0 -4806
  113. package/esm/TestTriggers-CRKtulyp-vrzEq2wm.d.ts +0 -44
  114. package/esm/WebhookPayload-CJoIkGBD.d.ts +0 -11753
@@ -1,699 +0,0 @@
1
- const require_CalmLensVersion = require("./CalmLensVersion-DwhgxQK5.js");
2
- //#region src/contract/SharedConstants-Dn7ZJF5S.js
3
- const MAX_IMAGE_UPLOAD_BYTES = /* @__PURE__ */ (() => 20971520)();
4
- const MIN_MULTIPART_IMAGE_UPLOAD_BYTES = /* @__PURE__ */ (() => 10485760)();
5
- const REJECTED_UPLOAD_MIME_TYPE_SET = /* @__PURE__ */ new Set([
6
- "application/octet-stream",
7
- "multipart/form-data",
8
- "multipart/mixed",
9
- "multipart/alternative",
10
- "multipart/related",
11
- "multipart/signed",
12
- "multipart/encrypted"
13
- ]);
14
- const normalizeUploadMimeType = (mimeType) => mimeType.toLowerCase().split(";")[0]?.trim() || mimeType.toLowerCase().trim();
15
- const isRejectedUploadMimeType = (mimeType) => REJECTED_UPLOAD_MIME_TYPE_SET.has(normalizeUploadMimeType(mimeType));
16
- const getRejectedUploadMimeTypeError = (mimeType) => `${normalizeUploadMimeType(mimeType)} is not a valid upload file type. Uploads must use a concrete file MIME type that CalmLens can store and process, such as image/png, image/jpeg, application/pdf, or text/plain.`;
17
- const DOMAIN = "calmlens.com";
18
- //#endregion
19
- //#region src/contract/AssetPayload.js
20
- const uint8ArrayInputToNumbers = (value) => {
21
- if (Array.isArray(value)) return value;
22
- return Array.from(value);
23
- };
24
- const bufferInputToBytes = (value) => {
25
- if (value instanceof ArrayBuffer) return new Uint8Array(value);
26
- return Uint8Array.from(value);
27
- };
28
- const BASE64_PART_BOUNDARY_ERROR = "Invalid base64 part. For multipart uploads, split the original bytes first, then base64-encode each part. Do not split one full base64 string into substrings.";
29
- const MULTIPART_PART_TOO_SMALL_ERROR = `Multipart upload parts must be at least 5MiB except the final part.`;
30
- const getRawBase64 = (value) => {
31
- const markerIndex = value.indexOf(";base64,");
32
- if (markerIndex === -1) return value;
33
- return value.slice(markerIndex + 8);
34
- };
35
- const validateBase64PayloadString = (payload, options) => {
36
- const raw = getRawBase64(payload.base64).replace(/\s/g, "");
37
- if (options?.multipart && raw.length % 4 !== 0) return {
38
- ok: false,
39
- error: BASE64_PART_BOUNDARY_ERROR
40
- };
41
- return { ok: true };
42
- };
43
- const isReadableStream = (value) => typeof value === "object" && value !== null && "getReader" in value && typeof value.getReader === "function";
44
- const assetToData = (payload) => {
45
- if ("plainText" in payload && payload.plainText !== void 0) return new TextEncoder().encode(payload.plainText);
46
- if ("data" in payload && payload.data) return payload.data;
47
- if ("buffer" in payload) {
48
- if (payload.buffer instanceof ArrayBuffer) return payload.buffer;
49
- return bufferInputToBytes(payload.buffer);
50
- }
51
- if (payload.uint8Array !== void 0) return Uint8Array.from(uint8ArrayInputToNumbers(payload.uint8Array));
52
- if (payload.base64 === void 0) throw new Error("Invalid asset payload");
53
- const raw = getRawBase64(payload.base64);
54
- if (typeof Buffer !== "undefined") return Uint8Array.from(Buffer.from(raw, "base64"));
55
- const binary = atob(raw);
56
- return Uint8Array.from(binary, (char) => char.charCodeAt(0));
57
- };
58
- const assetDataToUint8Array = async (data) => {
59
- if (data instanceof Response) return new Uint8Array(await data.arrayBuffer());
60
- if (isReadableStream(data)) return new Uint8Array(await new Response(data).arrayBuffer());
61
- if (data instanceof Blob) return new Uint8Array(await data.arrayBuffer());
62
- if (data instanceof ArrayBuffer) return new Uint8Array(data);
63
- return data;
64
- };
65
- const getAssetPayloadByteLength = (payload) => {
66
- if ("plainText" in payload && payload.plainText !== void 0) return new TextEncoder().encode(payload.plainText).byteLength;
67
- if ("data" in payload && payload.data) {
68
- if (payload.data instanceof Blob) return payload.data.size;
69
- if (payload.data instanceof Response) {
70
- const contentLength = payload.data.headers.get("content-length");
71
- return contentLength ? Number(contentLength) : void 0;
72
- }
73
- return;
74
- }
75
- if ("buffer" in payload && payload.buffer) return payload.buffer.byteLength;
76
- if (payload.uint8Array !== void 0) {
77
- if (Array.isArray(payload.uint8Array)) return payload.uint8Array.length;
78
- return payload.uint8Array.byteLength;
79
- }
80
- if (payload.base64 === void 0) return;
81
- const raw = getRawBase64(payload.base64).replace(/\s/g, "");
82
- const padding = raw.endsWith("==") ? 2 : raw.endsWith("=") ? 1 : 0;
83
- return Math.max(0, Math.floor(raw.length * 3 / 4) - padding);
84
- };
85
- const validateAssetPayload = (payload, options) => {
86
- if ("base64" in payload && payload.base64 !== void 0) {
87
- const validation = validateBase64PayloadString(payload, options);
88
- if (!validation.ok) return {
89
- ok: false,
90
- error: validation.error
91
- };
92
- }
93
- const byteLength = getAssetPayloadByteLength(payload);
94
- if (options?.multipart && !options.isFinal && byteLength !== void 0 && byteLength < 5242880) return {
95
- ok: false,
96
- error: MULTIPART_PART_TOO_SMALL_ERROR
97
- };
98
- return { ok: true };
99
- };
100
- //#endregion
101
- //#region src/client/CalmLensTransport.ts
102
- /** Retry delays grow from this base, doubling per attempt, with jitter. */
103
- const RETRY_BASE_DELAY_MS = 500;
104
- const RETRY_MAX_DELAY_MS = 8e3;
105
- /** A `Retry-After` longer than this is returned to the caller instead of waited out. */
106
- const RETRY_AFTER_MAX_MS = 6e4;
107
- const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
108
- 429,
109
- 502,
110
- 503,
111
- 504
112
- ]);
113
- const timeoutReason = () => {
114
- const reason = /* @__PURE__ */ new Error("The operation timed out.");
115
- reason.name = "TimeoutError";
116
- return reason;
117
- };
118
- /**
119
- * Links the callers' signals and a per-attempt deadline. `AbortSignal.any` holds its sources
120
- * weakly, so a long-lived caller signal doesn't collect a listener per streamed response;
121
- * runtimes without it get explicit listeners that `release` removes.
122
- */
123
- const createAttemptSignal = (signals, timeoutMs) => {
124
- const callers = signals.filter((signal) => signal !== void 0);
125
- const deadline = new AbortController();
126
- let timedOut = false;
127
- let signal;
128
- let onAbort;
129
- if (typeof AbortSignal.any === "function") signal = AbortSignal.any([...callers, deadline.signal]);
130
- else {
131
- const controller = new AbortController();
132
- signal = controller.signal;
133
- onAbort = (event) => controller.abort(event.target.reason);
134
- for (const source of [...callers, deadline.signal]) if (source.aborted) controller.abort(source.reason);
135
- else source.addEventListener("abort", onAbort, { once: true });
136
- }
137
- let timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
138
- timedOut = true;
139
- deadline.abort(timeoutReason());
140
- }, timeoutMs);
141
- return {
142
- signal,
143
- isTimedOut: () => timedOut,
144
- isCancelled: () => callers.some((caller) => caller.aborted),
145
- clearDeadline: () => {
146
- if (timer !== void 0) clearTimeout(timer);
147
- timer = void 0;
148
- },
149
- release: () => {
150
- if (timer !== void 0) clearTimeout(timer);
151
- timer = void 0;
152
- if (onAbort) for (const caller of callers) caller.removeEventListener("abort", onAbort);
153
- }
154
- };
155
- };
156
- /**
157
- * A redirect the SDK must not follow: it would send the API key to another origin. Browsers report
158
- * a manual redirect as an opaque response; other runtimes return the 3xx with its Location.
159
- */
160
- const isRedirect = (response) => response.type === "opaqueredirect" || response.status >= 300 && response.status < 400 && response.headers.has("location");
161
- /** Turns anything thrown while sending or reading a request into a `CalmLensError`. */
162
- const toFailure = (cause, attempt) => {
163
- if (require_CalmLensVersion.CalmLensError.isCalmLensError(cause)) return cause;
164
- if (attempt.isCancelled()) return new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.CANCELLED, {
165
- message: "Request cancelled",
166
- cause
167
- });
168
- if (attempt.isTimedOut()) return new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.TIMEOUT, {
169
- message: "Request timed out",
170
- cause
171
- });
172
- return new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.TRANSPORT_ERROR, {
173
- message: "Request failed",
174
- cause
175
- });
176
- };
177
- const sleep = (ms, signal) => new Promise((resolve, reject) => {
178
- if (signal?.aborted) {
179
- reject(new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.CANCELLED, { message: "Request cancelled" }));
180
- return;
181
- }
182
- const onAbort = () => {
183
- clearTimeout(timer);
184
- reject(new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.CANCELLED, { message: "Request cancelled" }));
185
- };
186
- const timer = setTimeout(() => {
187
- signal?.removeEventListener("abort", onAbort);
188
- resolve();
189
- }, ms);
190
- signal?.addEventListener("abort", onAbort, { once: true });
191
- });
192
- /** Exponential backoff with jitter: half the delay is fixed, the other half random. */
193
- const getRetryDelayMs = (attempt, random = Math.random) => {
194
- const delay = Math.min(RETRY_MAX_DELAY_MS, RETRY_BASE_DELAY_MS * 2 ** attempt);
195
- return Math.round(delay / 2 + random() * (delay / 2));
196
- };
197
- /** Parses `Retry-After` as seconds or an HTTP date, in seconds from `now`. */
198
- const parseRetryAfter = (value, now = Date.now()) => {
199
- if (!value) return void 0;
200
- const trimmed = value.trim();
201
- if (/^\d+$/.test(trimmed)) return Number(trimmed);
202
- const date = Date.parse(trimmed);
203
- if (Number.isNaN(date)) return void 0;
204
- return Math.max(0, Math.ceil((date - now) / 1e3));
205
- };
206
- /** Bodies that can be sent again unchanged. Streams are consumed by the first attempt. */
207
- const isReplayableBody = (body) => body === void 0 || body === null || typeof body === "string" || body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof URLSearchParams;
208
- const isApiErrorPayload = (value) => !!value && typeof value === "object" && typeof value.code === "string";
209
- /** Reads at most `limit` bytes of a body, failing with `sdk/response-too-large` beyond that. */
210
- const readBody = async (response, limit) => {
211
- const declared = Number(response.headers.get("content-length"));
212
- const tooLarge = () => new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.RESPONSE_TOO_LARGE, {
213
- message: `Response body exceeds ${limit} bytes`,
214
- status: response.status,
215
- requestId: response.headers.get("x-request-id") ?? void 0,
216
- headers: response.headers
217
- });
218
- if (Number.isFinite(declared) && declared > limit) {
219
- await response.body?.cancel().catch(() => {});
220
- throw tooLarge();
221
- }
222
- if (!response.body) return /* @__PURE__ */ new Uint8Array(0);
223
- const reader = response.body.getReader();
224
- const chunks = [];
225
- let total = 0;
226
- for (;;) {
227
- const { done, value } = await reader.read();
228
- if (done) break;
229
- total += value.byteLength;
230
- if (total > limit) {
231
- await reader.cancel().catch(() => {});
232
- throw tooLarge();
233
- }
234
- chunks.push(value);
235
- }
236
- const bytes = new Uint8Array(total);
237
- let offset = 0;
238
- for (const chunk of chunks) {
239
- bytes.set(chunk, offset);
240
- offset += chunk.byteLength;
241
- }
242
- return bytes;
243
- };
244
- const responseInfo = (response) => ({
245
- status: response.status,
246
- requestId: response.headers.get("x-request-id") ?? void 0,
247
- headers: response.headers,
248
- retryAfter: parseRetryAfter(response.headers.get("retry-after"))
249
- });
250
- /** The `CalmLensError` for a non-2xx response whose body has been read. */
251
- const toHttpError = (response, bytes) => {
252
- let data;
253
- try {
254
- data = JSON.parse(new TextDecoder().decode(bytes));
255
- } catch {}
256
- if (isApiErrorPayload(data)) {
257
- const { code, message, details, traceId } = data;
258
- return new require_CalmLensVersion.CalmLensError(code, {
259
- message: message ?? code,
260
- ...responseInfo(response),
261
- details: details || traceId ? {
262
- ...details,
263
- ...traceId ? { traceId } : {}
264
- } : void 0
265
- });
266
- }
267
- return new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.HTTP_ERROR, {
268
- message: `HTTP ${response.status}`,
269
- ...responseInfo(response)
270
- });
271
- };
272
- /** Sends requests with the client's credentials, deadlines, retries and response limits. */
273
- var CalmLensTransport = class {
274
- constructor(settings) {
275
- this.settings = settings;
276
- }
277
- headersFor(request) {
278
- const headers = new Headers(request.init?.headers);
279
- for (const [name, value] of Object.entries(request.headers ?? {})) headers.set(name, value);
280
- if (!headers.has("accept") && request.deadline === "body") headers.set("accept", "application/json");
281
- if (this.settings.apiKey) headers.set("x-api-key", this.settings.apiKey);
282
- if (!require_CalmLensVersion.isBrowserRuntime()) headers.set("user-agent", require_CalmLensVersion.getUserAgent());
283
- if (request.idempotencyKey) headers.set("idempotency-key", request.idempotencyKey);
284
- return Object.fromEntries(headers.entries());
285
- }
286
- canRetry(request) {
287
- return this.settings.maxRetries > 0 && (request.method === "GET" || request.method === "HEAD" || !!request.idempotencyKey) && isReplayableBody(request.body);
288
- }
289
- /**
290
- * Sends a request and returns the response, retrying transport failures, 429, 502–504 and
291
- * `idempotency/request-in-progress` when the request is safe to repeat. Non-2xx responses
292
- * throw unless `allowHttpErrors` is set. Call `release` once the body has been read.
293
- */
294
- async send(request, { allowHttpErrors = false } = {}) {
295
- const fetchImpl = request.fetch ?? this.settings.fetch;
296
- const retryable = this.canRetry(request);
297
- const headers = this.headersFor(request);
298
- for (let attemptNumber = 0;; attemptNumber++) {
299
- const canRetryAgain = retryable && attemptNumber < this.settings.maxRetries;
300
- const attempt = createAttemptSignal([this.settings.signal, request.init?.signal ?? void 0], request.timeoutMs);
301
- let response;
302
- try {
303
- if (attempt.signal.aborted) throw attempt.signal.reason;
304
- response = await fetchImpl(request.url, {
305
- ...request.init,
306
- method: request.method,
307
- headers,
308
- body: request.body,
309
- redirect: "manual",
310
- signal: attempt.signal,
311
- duplex: "half"
312
- });
313
- if (isRedirect(response)) {
314
- await response.body?.cancel().catch(() => {});
315
- throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.TRANSPORT_ERROR, { message: `Unexpected redirect (HTTP ${response.status})` });
316
- }
317
- } catch (cause) {
318
- attempt.release();
319
- const failure = toFailure(cause, attempt);
320
- if (canRetryAgain && failure.code === require_CalmLensVersion.SDK_ERROR_CODES.TRANSPORT_ERROR) {
321
- await sleep(getRetryDelayMs(attemptNumber), this.settings.signal);
322
- continue;
323
- }
324
- throw failure;
325
- }
326
- if (request.deadline === "headers") attempt.clearDeadline();
327
- if (response.ok) return {
328
- response,
329
- attempt
330
- };
331
- if (allowHttpErrors) {
332
- const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
333
- const delayMs = retryAfter !== void 0 ? retryAfter * 1e3 : getRetryDelayMs(attemptNumber);
334
- if (!canRetryAgain || !RETRYABLE_STATUSES.has(response.status) || delayMs > RETRY_AFTER_MAX_MS) return {
335
- response,
336
- attempt
337
- };
338
- attempt.release();
339
- await response.body?.cancel().catch(() => {});
340
- await sleep(delayMs, this.settings.signal);
341
- continue;
342
- }
343
- let error;
344
- try {
345
- error = toHttpError(response, await readBody(response, this.settings.maxResponseBytes));
346
- } catch (cause) {
347
- error = toFailure(cause, attempt);
348
- } finally {
349
- attempt.release();
350
- }
351
- const retryDelayMs = error.retryAfter !== void 0 ? error.retryAfter * 1e3 : getRetryDelayMs(attemptNumber);
352
- if (canRetryAgain && retryDelayMs <= RETRY_AFTER_MAX_MS && (error.status !== void 0 && RETRYABLE_STATUSES.has(error.status) || error.code === require_CalmLensVersion.API_ERROR_CODES.IDEMPOTENT_REQUEST_IN_PROGRESS)) {
353
- await sleep(retryDelayMs, this.settings.signal);
354
- continue;
355
- }
356
- throw error;
357
- }
358
- }
359
- /** Sends a request and parses its JSON response body. */
360
- async json(request) {
361
- const { response, attempt } = await this.send(request);
362
- try {
363
- const bytes = await readBody(response, this.settings.maxResponseBytes);
364
- if (bytes.byteLength === 0 && (response.status === 204 || request.method === "DELETE")) return;
365
- try {
366
- return JSON.parse(new TextDecoder().decode(bytes));
367
- } catch (cause) {
368
- throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.INVALID_RESPONSE, {
369
- message: `Invalid JSON response (HTTP ${response.status})`,
370
- ...responseInfo(response),
371
- cause
372
- });
373
- }
374
- } catch (cause) {
375
- throw toFailure(cause, attempt);
376
- } finally {
377
- attempt.release();
378
- }
379
- }
380
- };
381
- //#endregion
382
- //#region src/client/CalmLensUtils.ts
383
- const assertHttpUrl = (name, value) => {
384
- if (value === void 0) return;
385
- let url;
386
- try {
387
- url = new URL(value);
388
- } catch {}
389
- if (!url || url.protocol !== "https:" && url.protocol !== "http:") throw require_CalmLensVersion.invalidConfig(`${name} must be an absolute http(s) URL`);
390
- };
391
- const assertInteger = (name, value, minimum) => {
392
- if (value !== void 0 && (!Number.isSafeInteger(value) || value < minimum)) throw require_CalmLensVersion.invalidConfig(minimum === 0 ? `${name} must be a non-negative integer` : `${name} must be a positive integer`);
393
- };
394
- const configPathSegment = (value, name) => {
395
- try {
396
- encodePathSegment(value, name);
397
- } catch (error) {
398
- throw require_CalmLensVersion.invalidConfig(error.message.replace(/^\[calmlens\] /, ""));
399
- }
400
- };
401
- /** Checks the options both clients share: where requests go and how they're sent. */
402
- const validateConnectionOptions = (options) => {
403
- if ("requestBackend" in options) throw require_CalmLensVersion.invalidConfig("requestBackend is no longer supported; pass a fetch implementation as fetch");
404
- validateRequestOptions(options);
405
- assertInteger("maxResponseBytes", options.maxResponseBytes, 1);
406
- for (const name of ["fetch", "uploadFetch"]) if (options[name] !== void 0 && typeof options[name] !== "function") throw require_CalmLensVersion.invalidConfig(`${name} must be a function`);
407
- assertHttpUrl("baseUrl", options.baseUrl);
408
- };
409
- /**
410
- * Checks server client options before the client sets anything up. `CalmLensServerClient` and
411
- * `MockCalmLensServerClient` share it so both reject the same configuration.
412
- */
413
- const validateServerClientOptions = (options) => {
414
- if (!options || typeof options !== "object") throw require_CalmLensVersion.invalidConfig("Client options are required");
415
- if (!options.apiKey || typeof options.apiKey !== "string") throw require_CalmLensVersion.invalidConfig("API key is required");
416
- if (!options.projectId) throw require_CalmLensVersion.invalidConfig("Project ID is required");
417
- configPathSegment(options.projectId, "Project ID");
418
- validateConnectionOptions(options);
419
- if (options.cdnFetch !== void 0 && typeof options.cdnFetch !== "function") throw require_CalmLensVersion.invalidConfig("cdnFetch must be a function");
420
- assertHttpUrl("cdnBaseUrl", options.cdnBaseUrl);
421
- };
422
- /** Checks app client options before the client sets anything up. */
423
- const validateAppClientOptions = (options) => {
424
- if (!options || typeof options !== "object") throw require_CalmLensVersion.invalidConfig("Client options are required");
425
- if ("apiKey" in options && options.apiKey !== void 0) throw require_CalmLensVersion.invalidConfig("CalmLensAppClient never takes an API key, which anyone using the browser or app could read. Upload through uploadPresetId, and keep the API key in CalmLensServerClient on your server.");
426
- if (!options.uploadPresetId) throw require_CalmLensVersion.invalidConfig("Upload preset ID is required");
427
- configPathSegment(options.uploadPresetId, "Upload preset ID");
428
- if (options.token !== void 0 && typeof options.token !== "string" && typeof options.token !== "function") throw require_CalmLensVersion.invalidConfig("token must be a string or a function that returns one");
429
- validateConnectionOptions(options);
430
- };
431
- /** Checks the options `withRequestOptions` accepts. */
432
- const validateRequestOptions = (options) => {
433
- assertInteger("timeoutMs", options.timeoutMs, 1);
434
- assertInteger("uploadTimeoutMs", options.uploadTimeoutMs, 1);
435
- assertInteger("maxRetries", options.maxRetries, 0);
436
- };
437
- const toTextAssetOptions = ({ text, name, ...options }) => {
438
- if (typeof text !== "string" || !text.trim()) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Text to moderate must be a non-empty string" });
439
- return {
440
- ...options,
441
- name: name ?? "text",
442
- content: {
443
- plainText: text,
444
- type: "text/plain"
445
- }
446
- };
447
- };
448
- /**
449
- * A random UUID v4. React Native has no `crypto` global, so this falls back to
450
- * `crypto.getRandomValues` and then to `Math.random`; the SDK only uses it for idempotency keys,
451
- * which need to be unique rather than secret.
452
- */
453
- const randomUuid = () => {
454
- const crypto = globalThis.crypto;
455
- if (typeof crypto?.randomUUID === "function") return crypto.randomUUID();
456
- const bytes = /* @__PURE__ */ new Uint8Array(16);
457
- if (typeof crypto?.getRandomValues === "function") crypto.getRandomValues(bytes);
458
- else for (let index = 0; index < bytes.length; index++) bytes[index] = Math.random() * 256;
459
- bytes[6] = bytes[6] & 15 | 64;
460
- bytes[8] = bytes[8] & 63 | 128;
461
- const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
462
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
463
- };
464
- /**
465
- * Encodes an identifier as one URL path segment. `.` and `..` are rejected because URL
466
- * resolution would treat them as relative segments and send the request to another route.
467
- */
468
- const encodePathSegment = (value, name) => {
469
- if (typeof value !== "string" || !value) throw require_CalmLensVersion.invalidArgument(`${name} is required`);
470
- if (value === "." || value === "..") throw require_CalmLensVersion.invalidArgument(`${name} cannot be "." or ".."`);
471
- return encodeURIComponent(value);
472
- };
473
- const TRANSFORM_TYPE_PATTERN = /^[A-Za-z0-9_]+$/;
474
- /** Percent-encodes everything except RFC 3986 unreserved characters (`A-Z a-z 0-9 - . _ ~`). */
475
- const encodeTransformComponent = (value) => encodeURIComponent(value).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
476
- const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
477
- const appendTransformOptions = (pairs, key, value) => {
478
- if (value === void 0 || value === null) return;
479
- if (isPlainObject(value)) {
480
- for (const child of Object.keys(value).sort()) appendTransformOptions(pairs, `${key}[${child}]`, value[child]);
481
- return;
482
- }
483
- let text;
484
- if (typeof value === "string") text = value;
485
- else if (typeof value === "boolean") text = String(value);
486
- else if (typeof value === "number" && Number.isFinite(value)) text = String(value);
487
- else throw require_CalmLensVersion.invalidArgument(`Transform option "${key}" must be a string, number, boolean or object`);
488
- pairs.push(`${encodeTransformComponent(key)}=${encodeTransformComponent(text)}`);
489
- };
490
- /**
491
- * Serializes structured transforms as a CDN selector, such as
492
- * `imageTransform[format=png,width=1024]`. Option keys are sorted so equal transforms share a
493
- * URL; nested keys (`border[color]`) and values are percent-encoded.
494
- */
495
- const serializeTransform = (transform) => {
496
- const steps = Array.isArray(transform) ? transform : [transform];
497
- if (steps.length === 0) throw require_CalmLensVersion.invalidArgument("Transform needs at least one step");
498
- return steps.map((step) => {
499
- if (!isPlainObject(step)) throw require_CalmLensVersion.invalidArgument("Transform steps must be objects");
500
- if (typeof step.type !== "string" || !TRANSFORM_TYPE_PATTERN.test(step.type)) throw require_CalmLensVersion.invalidArgument("Transform type must contain only letters, digits and underscores");
501
- if (step.options !== void 0 && step.options !== null && !isPlainObject(step.options)) throw require_CalmLensVersion.invalidArgument("Transform options must be an object");
502
- const pairs = [];
503
- for (const key of Object.keys(step.options ?? {}).sort()) appendTransformOptions(pairs, key, step.options[key]);
504
- return `${step.type}[${pairs.join(",")}]`;
505
- }).join(",");
506
- };
507
- /** Returns the selector string the API and CDN accept for a string or structured transform. */
508
- const transformSelector = (transform) => transform === void 0 || typeof transform === "string" ? transform : serializeTransform(transform);
509
- /** Appends a CDN lens (`/@name`) followed by any direct transform selector to an asset URL. */
510
- const appendCdnSelector = (url, options) => {
511
- if (options.lens) url = `${url}/@${encodePathSegment(options.lens, "Lens")}`;
512
- if (!options.transform) return url;
513
- if (typeof options.transform !== "string") return `${url}${options.lens ? "," : "/"}${serializeTransform(options.transform)}`;
514
- const transform = options.transform.replace(/^\/+/, "");
515
- if (transform.includes("..") || transform.includes("?") || transform.includes("#")) throw require_CalmLensVersion.invalidArgument("Transform cannot contain \"..\", \"?\" or \"#\"");
516
- return `${url}${options.lens ? "," : "/"}${encodeURI(transform).replace(/%5B/g, "[").replace(/%5D/g, "]")}`;
517
- };
518
- const appendQueryValue = (parts, key, value) => {
519
- if (value === void 0 || value === null) return;
520
- if (Array.isArray(value)) {
521
- for (const [index, item] of value.entries()) appendQueryValue(parts, `${key}[${index}]`, item);
522
- return;
523
- }
524
- if (typeof value === "object") {
525
- for (const [field, item] of Object.entries(value)) appendQueryValue(parts, `${key}[${field}]`, item);
526
- return;
527
- }
528
- parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
529
- };
530
- /**
531
- * Serialises a query object the way the API parses it (qs bracket notation), for example
532
- * `ordering[0][field]=createdAt&ordering[0][direction]=DESC`. Every key and value is
533
- * percent-encoded and missing values are omitted.
534
- */
535
- const stringifyQuery = (query) => {
536
- const parts = [];
537
- for (const [key, value] of Object.entries(query)) appendQueryValue(parts, key, value);
538
- return parts.join("&");
539
- };
540
- /**
541
- * Whether an asset has a moderation decision (`moderation.status` `approved` or `rejected`) or
542
- * failed processing; `assets.waitForDecision` returns at the first such state.
543
- */
544
- const isAssetDecided = (asset) => asset.moderation.status === "approved" || asset.moderation.status === "rejected" || asset.processing.status === "failed";
545
- const DECISION_POLL_FIRST_DELAY_MS = 500;
546
- const DECISION_POLL_MAX_DELAY_MS = 5e3;
547
- const DEFAULT_DECISION_TIMEOUT_MS = 6e4;
548
- /**
549
- * Retrieves an asset until it has a decision, waiting 0.5s between checks and 1.5 times longer
550
- * each time, up to 5s. Rejects with `sdk/timeout` once `timeoutMs` passes without one.
551
- */
552
- const pollForDecision = async (retrieve, { timeoutMs = DEFAULT_DECISION_TIMEOUT_MS, signal }, sleep, now = Date.now) => {
553
- if (!Number.isFinite(timeoutMs) || timeoutMs < 0) throw require_CalmLensVersion.invalidArgument("timeoutMs must be a non-negative number");
554
- const deadline = now() + timeoutMs;
555
- let delay = DECISION_POLL_FIRST_DELAY_MS;
556
- for (;;) {
557
- if (signal?.aborted) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.CANCELLED, { message: "Request cancelled" });
558
- const asset = await retrieve();
559
- if (isAssetDecided(asset)) return asset;
560
- const remaining = deadline - now();
561
- if (remaining <= 0) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.TIMEOUT, {
562
- message: `Asset ${asset.id} had no moderation decision after ${timeoutMs}ms (processing ${asset.processing.status}, moderation ${asset.moderation.status})`,
563
- details: {
564
- assetId: asset.id,
565
- processingStatus: asset.processing.status,
566
- moderationStatus: asset.moderation.status
567
- }
568
- });
569
- await sleep(Math.min(delay, remaining), signal);
570
- delay = Math.min(DECISION_POLL_MAX_DELAY_MS, delay * 1.5);
571
- }
572
- };
573
- //#endregion
574
- Object.defineProperty(exports, "CalmLensTransport", {
575
- enumerable: true,
576
- get: function() {
577
- return CalmLensTransport;
578
- }
579
- });
580
- Object.defineProperty(exports, "DOMAIN", {
581
- enumerable: true,
582
- get: function() {
583
- return DOMAIN;
584
- }
585
- });
586
- Object.defineProperty(exports, "MAX_IMAGE_UPLOAD_BYTES", {
587
- enumerable: true,
588
- get: function() {
589
- return MAX_IMAGE_UPLOAD_BYTES;
590
- }
591
- });
592
- Object.defineProperty(exports, "MIN_MULTIPART_IMAGE_UPLOAD_BYTES", {
593
- enumerable: true,
594
- get: function() {
595
- return MIN_MULTIPART_IMAGE_UPLOAD_BYTES;
596
- }
597
- });
598
- Object.defineProperty(exports, "appendCdnSelector", {
599
- enumerable: true,
600
- get: function() {
601
- return appendCdnSelector;
602
- }
603
- });
604
- Object.defineProperty(exports, "assetDataToUint8Array", {
605
- enumerable: true,
606
- get: function() {
607
- return assetDataToUint8Array;
608
- }
609
- });
610
- Object.defineProperty(exports, "assetToData", {
611
- enumerable: true,
612
- get: function() {
613
- return assetToData;
614
- }
615
- });
616
- Object.defineProperty(exports, "encodePathSegment", {
617
- enumerable: true,
618
- get: function() {
619
- return encodePathSegment;
620
- }
621
- });
622
- Object.defineProperty(exports, "getAssetPayloadByteLength", {
623
- enumerable: true,
624
- get: function() {
625
- return getAssetPayloadByteLength;
626
- }
627
- });
628
- Object.defineProperty(exports, "getRejectedUploadMimeTypeError", {
629
- enumerable: true,
630
- get: function() {
631
- return getRejectedUploadMimeTypeError;
632
- }
633
- });
634
- Object.defineProperty(exports, "isRejectedUploadMimeType", {
635
- enumerable: true,
636
- get: function() {
637
- return isRejectedUploadMimeType;
638
- }
639
- });
640
- Object.defineProperty(exports, "pollForDecision", {
641
- enumerable: true,
642
- get: function() {
643
- return pollForDecision;
644
- }
645
- });
646
- Object.defineProperty(exports, "randomUuid", {
647
- enumerable: true,
648
- get: function() {
649
- return randomUuid;
650
- }
651
- });
652
- Object.defineProperty(exports, "sleep", {
653
- enumerable: true,
654
- get: function() {
655
- return sleep;
656
- }
657
- });
658
- Object.defineProperty(exports, "stringifyQuery", {
659
- enumerable: true,
660
- get: function() {
661
- return stringifyQuery;
662
- }
663
- });
664
- Object.defineProperty(exports, "toTextAssetOptions", {
665
- enumerable: true,
666
- get: function() {
667
- return toTextAssetOptions;
668
- }
669
- });
670
- Object.defineProperty(exports, "transformSelector", {
671
- enumerable: true,
672
- get: function() {
673
- return transformSelector;
674
- }
675
- });
676
- Object.defineProperty(exports, "validateAppClientOptions", {
677
- enumerable: true,
678
- get: function() {
679
- return validateAppClientOptions;
680
- }
681
- });
682
- Object.defineProperty(exports, "validateAssetPayload", {
683
- enumerable: true,
684
- get: function() {
685
- return validateAssetPayload;
686
- }
687
- });
688
- Object.defineProperty(exports, "validateRequestOptions", {
689
- enumerable: true,
690
- get: function() {
691
- return validateRequestOptions;
692
- }
693
- });
694
- Object.defineProperty(exports, "validateServerClientOptions", {
695
- enumerable: true,
696
- get: function() {
697
- return validateServerClientOptions;
698
- }
699
- });