@mymehq/sdk 3.3.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -352,4 +352,56 @@ declare class ConflictError extends MymeError {
352
352
  constructor(current: ConflictSnapshot, ancestor: ConflictSnapshot, conflictingFields: string[], clientPatch: Record<string, unknown>);
353
353
  }
354
354
 
355
- export { type ClientConfig, type ConflictAutoMergeListener, type ConflictAutoMergedEvent, type ConflictData, ConflictError, type ConflictResolver, type ConflictStrategy, ForbiddenError, type ItemWithExtensions, type ListFilters, type MetadataInput, MymeClient, MymeError, NotFoundError, type SearchFilters, UnauthorizedError, type UpdateOptions, ValidationError };
355
+ /**
356
+ * Reason a webhook signature did not validate. Receivers should treat
357
+ * any non-`valid` result as "do not trust this delivery".
358
+ *
359
+ * - `malformed` — the signature header was missing, empty, or not in
360
+ * the `t=<unix>,v1=<hex>` form.
361
+ * - `too_old` — the parsed timestamp is outside the tolerance window
362
+ * (default: more than 300s in the past, or more than 60s in the
363
+ * future to allow for mild clock skew).
364
+ * - `mismatch` — the recomputed HMAC did not match the provided hex.
365
+ */
366
+ type WebhookVerifyReason = "malformed" | "too_old" | "mismatch";
367
+ interface WebhookVerifyResult {
368
+ valid: boolean;
369
+ reason?: WebhookVerifyReason;
370
+ }
371
+ interface VerifyWebhookSignatureInput {
372
+ /** The `X-Myme-Signature` header value as received. */
373
+ header: string | null | undefined;
374
+ /** The raw HTTP request body, exactly as received (pre-JSON-parse). */
375
+ rawBody: string;
376
+ /** The webhook secret shared with the Myme server. */
377
+ secret: string;
378
+ /**
379
+ * Maximum age (seconds in the past) to accept. Default 300 (5 min).
380
+ * Matches the documented platform contract.
381
+ */
382
+ tolerance?: number;
383
+ /**
384
+ * Maximum future skew (seconds) to tolerate. Default 60 — matches
385
+ * the documented platform contract. Timestamps further ahead than
386
+ * this are rejected as `too_old` (name is legacy; it captures
387
+ * "outside the acceptable window").
388
+ */
389
+ futureSkew?: number;
390
+ /**
391
+ * Injection point for tests. Defaults to `Date.now() / 1000`.
392
+ */
393
+ nowSeconds?: () => number;
394
+ }
395
+ /**
396
+ * Verify a webhook signature produced by the Myme server. The server
397
+ * emits `X-Myme-Signature: t=<unix>,v1=<hex-sha256>` where the HMAC
398
+ * signs `<timestamp>.<raw-body>`.
399
+ *
400
+ * Pass the raw body string exactly as received (before JSON.parse) and
401
+ * the same `secret` configured on the webhook. Returns a structured
402
+ * result so receivers can log the failure reason without retrying on
403
+ * a permanently-malformed payload.
404
+ */
405
+ declare function verifyWebhookSignature(input: VerifyWebhookSignatureInput): WebhookVerifyResult;
406
+
407
+ export { type ClientConfig, type ConflictAutoMergeListener, type ConflictAutoMergedEvent, type ConflictData, ConflictError, type ConflictResolver, type ConflictStrategy, ForbiddenError, type ItemWithExtensions, type ListFilters, type MetadataInput, MymeClient, MymeError, NotFoundError, type SearchFilters, UnauthorizedError, type UpdateOptions, ValidationError, type VerifyWebhookSignatureInput, type WebhookVerifyReason, type WebhookVerifyResult, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -760,6 +760,37 @@ var MymeClient = class {
760
760
  }
761
761
  }
762
762
  };
763
+
764
+ // src/webhooks.ts
765
+ import { createHmac, timingSafeEqual } from "crypto";
766
+ var STRIPE_HEADER_RE = /^t=(\d+),v1=([0-9a-f]+)$/;
767
+ function verifyWebhookSignature(input) {
768
+ const tolerance = input.tolerance ?? 300;
769
+ const futureSkew = input.futureSkew ?? 60;
770
+ const nowSeconds = input.nowSeconds ?? (() => Math.floor(Date.now() / 1e3));
771
+ const header = input.header?.trim() ?? "";
772
+ const match = STRIPE_HEADER_RE.exec(header);
773
+ if (!match?.[1] || !match[2]) {
774
+ return { valid: false, reason: "malformed" };
775
+ }
776
+ const timestamp = match[1];
777
+ const providedSig = match[2];
778
+ const ts = Number(timestamp);
779
+ const now = nowSeconds();
780
+ if (now - ts > tolerance || ts - now > futureSkew) {
781
+ return { valid: false, reason: "too_old" };
782
+ }
783
+ const computedSig = createHmac("sha256", input.secret).update(`${timestamp}.${input.rawBody}`).digest("hex");
784
+ if (computedSig.length !== providedSig.length) {
785
+ return { valid: false, reason: "mismatch" };
786
+ }
787
+ const computedBuf = Buffer.from(computedSig, "utf8");
788
+ const providedBuf = Buffer.from(providedSig, "utf8");
789
+ if (!timingSafeEqual(computedBuf, providedBuf)) {
790
+ return { valid: false, reason: "mismatch" };
791
+ }
792
+ return { valid: true };
793
+ }
763
794
  export {
764
795
  ConflictError,
765
796
  ForbiddenError,
@@ -767,5 +798,6 @@ export {
767
798
  MymeError,
768
799
  NotFoundError,
769
800
  UnauthorizedError,
770
- ValidationError
801
+ ValidationError,
802
+ verifyWebhookSignature
771
803
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mymehq/sdk",
3
- "version": "3.3.1",
3
+ "version": "3.4.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",
@@ -16,7 +16,7 @@
16
16
  "dist"
17
17
  ],
18
18
  "dependencies": {
19
- "@mymehq/shared": "3.3.1"
19
+ "@mymehq/shared": "3.4.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^22.0.0",