@mymehq/sdk 3.3.2 → 3.5.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 +72 -2
- package/dist/index.js +48 -5
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -69,6 +69,21 @@ interface ClientConfig {
|
|
|
69
69
|
cdnBaseUrl?: string;
|
|
70
70
|
}
|
|
71
71
|
interface UpdateOptions {
|
|
72
|
+
/**
|
|
73
|
+
* Version the caller expects the item to currently be at. When provided,
|
|
74
|
+
* the SDK skips the upfront `GET /items/:id` and PATCHes directly. If the
|
|
75
|
+
* server's actual version differs, the update 409s through the usual
|
|
76
|
+
* conflict path — the caller's retry policy is out of scope here.
|
|
77
|
+
*
|
|
78
|
+
* Prefer `expectedVersion` for bulk importers and sync loops that already
|
|
79
|
+
* know the version locally; one request instead of two.
|
|
80
|
+
*/
|
|
81
|
+
expectedVersion?: number;
|
|
82
|
+
/**
|
|
83
|
+
* Legacy alias for `expectedVersion`. Kept for backwards compatibility;
|
|
84
|
+
* new code should use `expectedVersion`.
|
|
85
|
+
* @deprecated Use `expectedVersion`.
|
|
86
|
+
*/
|
|
72
87
|
version?: number;
|
|
73
88
|
/**
|
|
74
89
|
* Override the client's default conflict strategy for this update.
|
|
@@ -84,7 +99,10 @@ interface UpdateOptions {
|
|
|
84
99
|
library?: boolean;
|
|
85
100
|
/**
|
|
86
101
|
* Item type. Required by the `auto` strategy when a `keep_both_copies`
|
|
87
|
-
* conflict spawns a sibling item. Omit to let the SDK pre-fetch it
|
|
102
|
+
* conflict spawns a sibling item. Omit to let the SDK pre-fetch it —
|
|
103
|
+
* when `expectedVersion` is also omitted the pre-fetch happens upfront;
|
|
104
|
+
* when `expectedVersion` is provided the fetch is deferred until a
|
|
105
|
+
* `keep_both_copies` conflict actually needs it.
|
|
88
106
|
*/
|
|
89
107
|
type?: string;
|
|
90
108
|
/**
|
|
@@ -352,4 +370,56 @@ declare class ConflictError extends MymeError {
|
|
|
352
370
|
constructor(current: ConflictSnapshot, ancestor: ConflictSnapshot, conflictingFields: string[], clientPatch: Record<string, unknown>);
|
|
353
371
|
}
|
|
354
372
|
|
|
355
|
-
|
|
373
|
+
/**
|
|
374
|
+
* Reason a webhook signature did not validate. Receivers should treat
|
|
375
|
+
* any non-`valid` result as "do not trust this delivery".
|
|
376
|
+
*
|
|
377
|
+
* - `malformed` — the signature header was missing, empty, or not in
|
|
378
|
+
* the `t=<unix>,v1=<hex>` form.
|
|
379
|
+
* - `too_old` — the parsed timestamp is outside the tolerance window
|
|
380
|
+
* (default: more than 300s in the past, or more than 60s in the
|
|
381
|
+
* future to allow for mild clock skew).
|
|
382
|
+
* - `mismatch` — the recomputed HMAC did not match the provided hex.
|
|
383
|
+
*/
|
|
384
|
+
type WebhookVerifyReason = "malformed" | "too_old" | "mismatch";
|
|
385
|
+
interface WebhookVerifyResult {
|
|
386
|
+
valid: boolean;
|
|
387
|
+
reason?: WebhookVerifyReason;
|
|
388
|
+
}
|
|
389
|
+
interface VerifyWebhookSignatureInput {
|
|
390
|
+
/** The `X-Myme-Signature` header value as received. */
|
|
391
|
+
header: string | null | undefined;
|
|
392
|
+
/** The raw HTTP request body, exactly as received (pre-JSON-parse). */
|
|
393
|
+
rawBody: string;
|
|
394
|
+
/** The webhook secret shared with the Myme server. */
|
|
395
|
+
secret: string;
|
|
396
|
+
/**
|
|
397
|
+
* Maximum age (seconds in the past) to accept. Default 300 (5 min).
|
|
398
|
+
* Matches the documented platform contract.
|
|
399
|
+
*/
|
|
400
|
+
tolerance?: number;
|
|
401
|
+
/**
|
|
402
|
+
* Maximum future skew (seconds) to tolerate. Default 60 — matches
|
|
403
|
+
* the documented platform contract. Timestamps further ahead than
|
|
404
|
+
* this are rejected as `too_old` (name is legacy; it captures
|
|
405
|
+
* "outside the acceptable window").
|
|
406
|
+
*/
|
|
407
|
+
futureSkew?: number;
|
|
408
|
+
/**
|
|
409
|
+
* Injection point for tests. Defaults to `Date.now() / 1000`.
|
|
410
|
+
*/
|
|
411
|
+
nowSeconds?: () => number;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Verify a webhook signature produced by the Myme server. The server
|
|
415
|
+
* emits `X-Myme-Signature: t=<unix>,v1=<hex-sha256>` where the HMAC
|
|
416
|
+
* signs `<timestamp>.<raw-body>`.
|
|
417
|
+
*
|
|
418
|
+
* Pass the raw body string exactly as received (before JSON.parse) and
|
|
419
|
+
* the same `secret` configured on the webhook. Returns a structured
|
|
420
|
+
* result so receivers can log the failure reason without retrying on
|
|
421
|
+
* a permanently-malformed payload.
|
|
422
|
+
*/
|
|
423
|
+
declare function verifyWebhookSignature(input: VerifyWebhookSignatureInput): WebhookVerifyResult;
|
|
424
|
+
|
|
425
|
+
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
|
@@ -152,6 +152,13 @@ var HttpTransport = class {
|
|
|
152
152
|
};
|
|
153
153
|
|
|
154
154
|
// src/conflict.ts
|
|
155
|
+
async function fetchItemType(transport, itemId) {
|
|
156
|
+
const res = await transport.request(
|
|
157
|
+
"GET",
|
|
158
|
+
`/items/${itemId}`
|
|
159
|
+
);
|
|
160
|
+
return res.item.type;
|
|
161
|
+
}
|
|
155
162
|
var MAX_RETRIES = 3;
|
|
156
163
|
function isConflictResponse(body) {
|
|
157
164
|
return typeof body === "object" && body !== null && "error" in body && "current" in body && "conflicting_fields" in body;
|
|
@@ -235,9 +242,10 @@ async function handleConflictUpdate(transport, itemId, itemType, clientPatch, ve
|
|
|
235
242
|
const plan = planAutoMerge(conflict);
|
|
236
243
|
let conflictedCopyId;
|
|
237
244
|
if (plan.keepBothFields.length > 0) {
|
|
245
|
+
const effectiveType = itemType ?? await fetchItemType(transport, itemId);
|
|
238
246
|
const sibling = await keepBothFlow(
|
|
239
247
|
transport,
|
|
240
|
-
|
|
248
|
+
effectiveType,
|
|
241
249
|
result.current.properties,
|
|
242
250
|
clientPatch,
|
|
243
251
|
plan.keepBothFields
|
|
@@ -341,11 +349,14 @@ var MymeClient = class {
|
|
|
341
349
|
);
|
|
342
350
|
},
|
|
343
351
|
update: async (id, properties, options) => {
|
|
344
|
-
|
|
352
|
+
const expected = options?.expectedVersion ?? options?.version;
|
|
353
|
+
let version;
|
|
345
354
|
let type = options?.type;
|
|
346
|
-
if (
|
|
355
|
+
if (expected !== void 0) {
|
|
356
|
+
version = expected;
|
|
357
|
+
} else {
|
|
347
358
|
const item = await this.items.get(id);
|
|
348
|
-
version
|
|
359
|
+
version = item.version;
|
|
349
360
|
type ??= item.type;
|
|
350
361
|
}
|
|
351
362
|
const strategy = options?.conflict ?? this.defaultConflictStrategy;
|
|
@@ -760,6 +771,37 @@ var MymeClient = class {
|
|
|
760
771
|
}
|
|
761
772
|
}
|
|
762
773
|
};
|
|
774
|
+
|
|
775
|
+
// src/webhooks.ts
|
|
776
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
777
|
+
var STRIPE_HEADER_RE = /^t=(\d+),v1=([0-9a-f]+)$/;
|
|
778
|
+
function verifyWebhookSignature(input) {
|
|
779
|
+
const tolerance = input.tolerance ?? 300;
|
|
780
|
+
const futureSkew = input.futureSkew ?? 60;
|
|
781
|
+
const nowSeconds = input.nowSeconds ?? (() => Math.floor(Date.now() / 1e3));
|
|
782
|
+
const header = input.header?.trim() ?? "";
|
|
783
|
+
const match = STRIPE_HEADER_RE.exec(header);
|
|
784
|
+
if (!match?.[1] || !match[2]) {
|
|
785
|
+
return { valid: false, reason: "malformed" };
|
|
786
|
+
}
|
|
787
|
+
const timestamp = match[1];
|
|
788
|
+
const providedSig = match[2];
|
|
789
|
+
const ts = Number(timestamp);
|
|
790
|
+
const now = nowSeconds();
|
|
791
|
+
if (now - ts > tolerance || ts - now > futureSkew) {
|
|
792
|
+
return { valid: false, reason: "too_old" };
|
|
793
|
+
}
|
|
794
|
+
const computedSig = createHmac("sha256", input.secret).update(`${timestamp}.${input.rawBody}`).digest("hex");
|
|
795
|
+
if (computedSig.length !== providedSig.length) {
|
|
796
|
+
return { valid: false, reason: "mismatch" };
|
|
797
|
+
}
|
|
798
|
+
const computedBuf = Buffer.from(computedSig, "utf8");
|
|
799
|
+
const providedBuf = Buffer.from(providedSig, "utf8");
|
|
800
|
+
if (!timingSafeEqual(computedBuf, providedBuf)) {
|
|
801
|
+
return { valid: false, reason: "mismatch" };
|
|
802
|
+
}
|
|
803
|
+
return { valid: true };
|
|
804
|
+
}
|
|
763
805
|
export {
|
|
764
806
|
ConflictError,
|
|
765
807
|
ForbiddenError,
|
|
@@ -767,5 +809,6 @@ export {
|
|
|
767
809
|
MymeError,
|
|
768
810
|
NotFoundError,
|
|
769
811
|
UnauthorizedError,
|
|
770
|
-
ValidationError
|
|
812
|
+
ValidationError,
|
|
813
|
+
verifyWebhookSignature
|
|
771
814
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mymehq/sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.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.
|
|
19
|
+
"@mymehq/shared": "3.4.1"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^22.0.0",
|