@calmlens/sdk 0.12.0 → 0.14.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 (37) hide show
  1. package/README.md +54 -37
  2. package/cjs/CalmLensConnection-Bth5fJyp.js +234 -0
  3. package/cjs/CalmLensUtils-DNdmYeX6.js +699 -0
  4. package/cjs/{CalmLensError-CKwMKV0S.js → CalmLensVersion-Bs0DreBV.js} +114 -1
  5. package/cjs/{CalmLensWebhooks-BTPxKvcD.js → CalmLensWebhooks-6thkvr_F.js} +9 -9
  6. package/cjs/WebhookPayload-vy8HNMaK.d.ts +15834 -0
  7. package/cjs/app.d.ts +3 -3
  8. package/cjs/app.js +10 -10
  9. package/cjs/{index-BgmWj3KL.d.ts → index-Dz340UQ9.d.ts} +29 -1
  10. package/cjs/index.d.ts +3 -3
  11. package/cjs/index.js +4 -5
  12. package/cjs/server.d.ts +16 -7
  13. package/cjs/server.js +35 -21
  14. package/cjs/testing.d.ts +21 -42
  15. package/cjs/testing.js +181 -94
  16. package/esm/CalmLensConnection-DrCOL9h9.js +193 -0
  17. package/esm/{CalmLensUtils-Cx32j2zt.js → CalmLensUtils-BQo79TA1.js} +316 -2
  18. package/esm/{CalmLensError-CihGG26K.js → CalmLensVersion-DPnL_Ebv.js} +91 -2
  19. package/esm/{CalmLensWebhooks-DShmAal4.js → CalmLensWebhooks-CWlD-f_P.js} +2 -2
  20. package/esm/WebhookPayload-vy8HNMaK.d.ts +15834 -0
  21. package/esm/app.d.ts +3 -3
  22. package/esm/app.js +4 -4
  23. package/esm/{index-BgmWj3KL.d.ts → index-Dz340UQ9.d.ts} +29 -1
  24. package/esm/index.d.ts +3 -3
  25. package/esm/index.js +1 -3
  26. package/esm/server.d.ts +16 -7
  27. package/esm/server.js +24 -10
  28. package/esm/testing.d.ts +21 -42
  29. package/esm/testing.js +152 -65
  30. package/package.json +1 -1
  31. package/cjs/CalmLensConnection-CYtBP-vu.js +0 -516
  32. package/cjs/CalmLensUtils-Dg3s4loH.js +0 -367
  33. package/cjs/WebhookPayload-Cgb32aSH.d.ts +0 -10187
  34. package/cjs/src-Dsc8Pn6v.js +0 -45
  35. package/esm/CalmLensConnection-wLNy0OIP.js +0 -475
  36. package/esm/WebhookPayload-Cgb32aSH.d.ts +0 -10187
  37. package/esm/src-CJhrILvM.js +0 -22
@@ -11,6 +11,7 @@ const API_ERROR_CODES = {
11
11
  MULTIPART_IMAGE_TOO_LARGE: "upload/multipart-image-too-large",
12
12
  IMAGE_EXCEEDED_MAX_DIMENSIONS: "upload/image-exceeded-max-dimensions",
13
13
  UPLOAD_LIMIT_EXCEEDED: "upload/limit-exceeded",
14
+ SPEND_LIMIT_REACHED: "upload/spend-limit-reached",
14
15
  INVALID_UPLOAD_MIME_TYPE: "upload/invalid-mime-type",
15
16
  INVALID_EVENT_CURSOR: "events/invalid-cursor",
16
17
  INVALID_UPLOAD_PAYLOAD: "upload/invalid-payload",
@@ -38,6 +39,8 @@ const API_ERROR_CODES = {
38
39
  UPLOAD_PRESET_NOT_FOUND: "upload-preset/not-found",
39
40
  UPLOAD_PRESET_DISABLED: "upload-preset/disabled",
40
41
  UPLOAD_PRESET_TOKEN_REQUIRED: "upload-preset/token-required",
42
+ REMOTE_URL_NOT_ALLOWED: "upload/remote-url-not-allowed",
43
+ WEBSITE_RATE_LIMIT_EXCEEDED: "upload/website-rate-limit-exceeded",
41
44
  UPLOAD_PRESET_CONTENT_TYPE_NOT_ALLOWED: "upload-preset/content-type-not-allowed",
42
45
  UPLOAD_PRESET_TOO_LARGE: "upload-preset/too-large",
43
46
  UPLOAD_PRESET_DAILY_LIMIT_REACHED: "upload-preset/daily-limit-reached",
@@ -45,7 +48,18 @@ const API_ERROR_CODES = {
45
48
  UPLOAD_PRESET_FIELD_NOT_ALLOWED: "upload-preset/field-not-allowed",
46
49
  UPLOAD_TOKEN_INVALID: "upload-token/invalid",
47
50
  UPLOAD_TOKEN_EXPIRED: "upload-token/expired",
48
- UPLOAD_TOKEN_USED: "upload-token/used"
51
+ UPLOAD_TOKEN_USED: "upload-token/used",
52
+ INVALID_REMOTE_URL: "asset/invalid-remote-url",
53
+ REMOTE_URL_UNAVAILABLE: "asset/remote-url-unavailable",
54
+ APPEAL_NOT_FOUND: "appeal/not-found",
55
+ APPEAL_JUDGMENT_NOT_FOUND: "appeal/judgment-not-found",
56
+ APPEAL_ASSET_NOT_FOUND: "appeal/asset-not-found",
57
+ APPEAL_PERMISSION_DENIED: "appeal/permission-denied",
58
+ APPEAL_ALREADY_REVIEWED: "appeal/already-reviewed",
59
+ APPEAL_DECISION_CHANGED: "appeal/decision-changed",
60
+ APPEAL_INVALID_ACTION: "appeal/invalid-action",
61
+ APPEAL_SAFETY_HELD: "appeal/safety-held",
62
+ APPEAL_CONTENT_UNAVAILABLE: "appeal/content-unavailable"
49
63
  };
50
64
  /** The HTTP status and default message the API returns with each documented error code. */
51
65
  const API_ERROR_DEFINITIONS = {
@@ -89,6 +103,10 @@ const API_ERROR_DEFINITIONS = {
89
103
  status: 403,
90
104
  message: "Upload limit exceeded. You have used all your free uploads. Please upgrade to Plus or Pro for unlimited uploads."
91
105
  },
106
+ "upload/spend-limit-reached": {
107
+ status: 402,
108
+ message: "The project owner's spend limit for this billing period has been reached. Raise the limit in Billing to resume uploads."
109
+ },
92
110
  "upload/invalid-mime-type": {
93
111
  status: 400,
94
112
  message: "Upload file type is not supported. 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."
@@ -197,6 +215,14 @@ const API_ERROR_DEFINITIONS = {
197
215
  status: 401,
198
216
  message: "This upload preset only accepts uploads with an upload token. Sign one on your server with uploadTokens.sign and pass it to the client."
199
217
  },
218
+ "upload/remote-url-not-allowed": {
219
+ status: 403,
220
+ message: "This project doesn't accept URLs from this domain. Change the allowed or blocked domains under Project settings."
221
+ },
222
+ "upload/website-rate-limit-exceeded": {
223
+ status: 429,
224
+ message: "This project has submitted too many websites in the last minute. Wait for the Retry-After period, or lower the pace of submissions."
225
+ },
200
226
  "upload-preset/content-type-not-allowed": {
201
227
  status: 415,
202
228
  message: "This upload preset doesn't accept this file type."
@@ -228,6 +254,50 @@ const API_ERROR_DEFINITIONS = {
228
254
  "upload-token/used": {
229
255
  status: 409,
230
256
  message: "The upload token has already been used. Each token uploads one asset."
257
+ },
258
+ "asset/invalid-remote-url": {
259
+ status: 400,
260
+ message: "Remote URLs must use HTTP or HTTPS and cannot target private networks, localhost names or URL credentials."
261
+ },
262
+ "asset/remote-url-unavailable": {
263
+ status: 422,
264
+ message: "The URL responded with an error status. Check that it is publicly reachable."
265
+ },
266
+ "appeal/not-found": {
267
+ status: 404,
268
+ message: "The appeal does not exist in this project."
269
+ },
270
+ "appeal/judgment-not-found": {
271
+ status: 404,
272
+ message: "The judgment event is unavailable, belongs to another project or cannot supply an asset decision. Supply explicit context instead."
273
+ },
274
+ "appeal/asset-not-found": {
275
+ status: 404,
276
+ message: "The linked asset does not exist in this project."
277
+ },
278
+ "appeal/permission-denied": {
279
+ status: 403,
280
+ message: "You cannot review this project's appeals."
281
+ },
282
+ "appeal/already-reviewed": {
283
+ status: 409,
284
+ message: "A reviewer already resolved this appeal."
285
+ },
286
+ "appeal/decision-changed": {
287
+ status: 409,
288
+ message: "The linked asset's decision changed after the appealed decision. Submit a new appeal for the current decision."
289
+ },
290
+ "appeal/invalid-action": {
291
+ status: 400,
292
+ message: "The decision's action is not valid for this appeal."
293
+ },
294
+ "appeal/safety-held": {
295
+ status: 409,
296
+ message: "A safety hold prevents this appeal from being submitted or applied."
297
+ },
298
+ "appeal/content-unavailable": {
299
+ status: 409,
300
+ message: "The appeal's content is unavailable, or approval would require restoring removed content."
231
301
  }
232
302
  };
233
303
  //#endregion
@@ -318,6 +388,25 @@ const invalidArgument = (message, details) => new CalmLensError(SDK_ERROR_CODES.
318
388
  });
319
389
  const invalidConfig = (message) => new CalmLensError(SDK_ERROR_CODES.INVALID_CONFIG, { message });
320
390
  //#endregion
391
+ //#region src/client/CalmLensVersion.ts
392
+ /** This SDK's version, from its package.json. */
393
+ const SDK_VERSION = "0.14.0";
394
+ const runtimeName = () => {
395
+ const runtime = globalThis;
396
+ if (isReactNativeRuntime()) return "react-native";
397
+ if (runtime.Bun?.version) return `bun/${runtime.Bun.version}`;
398
+ if (runtime.Deno?.version?.deno) return `deno/${runtime.Deno.version.deno}`;
399
+ if (runtime.navigator?.userAgent === "Cloudflare-Workers") return "workerd";
400
+ if (runtime.process?.versions?.node) return `node/${runtime.process.versions.node}`;
401
+ return "unknown";
402
+ };
403
+ /** The `User-Agent` the SDK sends from server runtimes, e.g. `calmlens-js/0.10.0 (node/22.12.0)`. */
404
+ const getUserAgent = () => `calmlens-js/${SDK_VERSION} (${runtimeName()})`;
405
+ /** True in a web page, where API keys would be visible to every visitor. */
406
+ const isBrowserRuntime = () => typeof globalThis.window !== "undefined" && typeof globalThis.document !== "undefined";
407
+ /** True in a React Native app, where an API key ships inside the app for anyone to extract. */
408
+ const isReactNativeRuntime = () => globalThis.navigator?.product === "ReactNative";
409
+ //#endregion
321
410
  Object.defineProperty(exports, "API_ERROR_CODES", {
322
411
  enumerable: true,
323
412
  get: function() {
@@ -348,6 +437,18 @@ Object.defineProperty(exports, "SDK_ERROR_CODES", {
348
437
  return SDK_ERROR_CODES;
349
438
  }
350
439
  });
440
+ Object.defineProperty(exports, "SDK_VERSION", {
441
+ enumerable: true,
442
+ get: function() {
443
+ return SDK_VERSION;
444
+ }
445
+ });
446
+ Object.defineProperty(exports, "getUserAgent", {
447
+ enumerable: true,
448
+ get: function() {
449
+ return getUserAgent;
450
+ }
451
+ });
351
452
  Object.defineProperty(exports, "invalidArgument", {
352
453
  enumerable: true,
353
454
  get: function() {
@@ -360,3 +461,15 @@ Object.defineProperty(exports, "invalidConfig", {
360
461
  return invalidConfig;
361
462
  }
362
463
  });
464
+ Object.defineProperty(exports, "isBrowserRuntime", {
465
+ enumerable: true,
466
+ get: function() {
467
+ return isBrowserRuntime;
468
+ }
469
+ });
470
+ Object.defineProperty(exports, "isReactNativeRuntime", {
471
+ enumerable: true,
472
+ get: function() {
473
+ return isReactNativeRuntime;
474
+ }
475
+ });
@@ -1,5 +1,5 @@
1
- const require_CalmLensError = require("./CalmLensError-CKwMKV0S.js");
2
- const require_CalmLensUtils = require("./CalmLensUtils-Dg3s4loH.js");
1
+ const require_CalmLensVersion = require("./CalmLensVersion-Bs0DreBV.js");
2
+ const require_CalmLensUtils = require("./CalmLensUtils-DNdmYeX6.js");
3
3
  //#region ../shared/src/JsonUtils.ts
4
4
  const stringifyJsonCanonical = (value) => JSON.stringify(value, (_key, nestedValue) => {
5
5
  if (!nestedValue || typeof nestedValue !== "object" || Array.isArray(nestedValue)) return nestedValue;
@@ -56,11 +56,11 @@ const signUploadToken = async (claims, apiKey) => {
56
56
  * `MockCalmLensServerClient` share it so both check options the same way.
57
57
  */
58
58
  const signUploadTokenWithApiKey = async (apiKey, options, { now = Date.now(), nonce = crypto.randomUUID() } = {}) => {
59
- if (!options || typeof options !== "object") throw require_CalmLensError.invalidArgument("Options are required");
59
+ if (!options || typeof options !== "object") throw require_CalmLensVersion.invalidArgument("Options are required");
60
60
  const { uploadPresetId, externalId, expiresInSeconds = 600 } = options;
61
61
  require_CalmLensUtils.encodePathSegment(uploadPresetId, "Upload preset ID");
62
- if (!Number.isInteger(expiresInSeconds) || expiresInSeconds < 1 || expiresInSeconds > 3600) throw require_CalmLensError.invalidArgument(`expiresInSeconds must be a whole number from 1 to ${UPLOAD_TOKEN_MAX_TTL_SECONDS}`);
63
- if (externalId !== void 0 && (typeof externalId !== "string" || !externalId)) throw require_CalmLensError.invalidArgument("externalId must be a non-empty string");
62
+ if (!Number.isInteger(expiresInSeconds) || expiresInSeconds < 1 || expiresInSeconds > 3600) throw require_CalmLensVersion.invalidArgument(`expiresInSeconds must be a whole number from 1 to ${UPLOAD_TOKEN_MAX_TTL_SECONDS}`);
63
+ if (externalId !== void 0 && (typeof externalId !== "string" || !externalId)) throw require_CalmLensVersion.invalidArgument("externalId must be a non-empty string");
64
64
  const expiresAt = now + expiresInSeconds * 1e3;
65
65
  return {
66
66
  token: await signUploadToken({
@@ -96,7 +96,7 @@ const payloadToText = (payload) => {
96
96
  const view = ArrayBuffer.isView(payload) ? new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength) : new Uint8Array(payload);
97
97
  return new TextDecoder("utf-8", { fatal: false }).decode(view);
98
98
  }
99
- throw require_CalmLensError.invalidArgument("Webhook payload must be the raw request body as a string, Uint8Array/Buffer or ArrayBuffer, not parsed JSON");
99
+ throw require_CalmLensVersion.invalidArgument("Webhook payload must be the raw request body as a string, Uint8Array/Buffer or ArrayBuffer, not parsed JSON");
100
100
  };
101
101
  /**
102
102
  * Verifies a webhook delivery: `x-calmlens-signature` must contain a `v1=` HMAC-SHA256 of
@@ -130,18 +130,18 @@ async function verifyWebhookSignature(options) {
130
130
  * so keep a default branch when you switch on `type`.
131
131
  */
132
132
  async function parseWebhookEvent(options) {
133
- if (!await verifyWebhookSignature(options)) throw new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.INVALID_WEBHOOK_SIGNATURE, { message: "Webhook signature or timestamp did not verify" });
133
+ if (!await verifyWebhookSignature(options)) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.INVALID_WEBHOOK_SIGNATURE, { message: "Webhook signature or timestamp did not verify" });
134
134
  let event;
135
135
  try {
136
136
  event = JSON.parse(payloadToText(options.payload));
137
137
  } catch (cause) {
138
- throw new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.INVALID_WEBHOOK_PAYLOAD, {
138
+ throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.INVALID_WEBHOOK_PAYLOAD, {
139
139
  message: "Webhook body is not JSON",
140
140
  cause
141
141
  });
142
142
  }
143
143
  const candidate = event;
144
- if (!candidate || typeof candidate !== "object" || typeof candidate.id !== "string" || typeof candidate.type !== "string" || typeof candidate.projectId !== "string" || !candidate.data || typeof candidate.data !== "object") throw new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.INVALID_WEBHOOK_PAYLOAD, { message: "Webhook body is not a CalmLens event" });
144
+ if (!candidate || typeof candidate !== "object" || typeof candidate.id !== "string" || typeof candidate.type !== "string" || typeof candidate.projectId !== "string" || !candidate.data || typeof candidate.data !== "object") throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.INVALID_WEBHOOK_PAYLOAD, { message: "Webhook body is not a CalmLens event" });
145
145
  return event;
146
146
  }
147
147
  //#endregion