@isnap/sdk 0.1.0 → 1.1.0-next.38

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 (104) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +173 -167
  3. package/dist/client.d.ts +48 -50
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +258 -89
  6. package/dist/client.js.map +1 -1
  7. package/dist/config.d.ts +43 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +55 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/errors.d.ts +53 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +100 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/generated/openapi.d.ts +10924 -0
  16. package/dist/idempotency.d.ts +2 -0
  17. package/dist/idempotency.d.ts.map +1 -0
  18. package/dist/idempotency.js +4 -0
  19. package/dist/idempotency.js.map +1 -0
  20. package/dist/index.d.ts +26 -8
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +12 -6
  23. package/dist/index.js.map +1 -1
  24. package/dist/internal/sleep.d.ts +10 -0
  25. package/dist/internal/sleep.d.ts.map +1 -0
  26. package/dist/internal/sleep.js +37 -0
  27. package/dist/internal/sleep.js.map +1 -0
  28. package/dist/pagination.d.ts +10 -0
  29. package/dist/pagination.d.ts.map +1 -0
  30. package/dist/pagination.js +16 -0
  31. package/dist/pagination.js.map +1 -0
  32. package/dist/resources/attachments.d.ts +45 -0
  33. package/dist/resources/attachments.d.ts.map +1 -0
  34. package/dist/resources/attachments.js +120 -0
  35. package/dist/resources/attachments.js.map +1 -0
  36. package/dist/resources/base.d.ts +6 -0
  37. package/dist/resources/base.d.ts.map +1 -0
  38. package/dist/resources/base.js +7 -0
  39. package/dist/resources/base.js.map +1 -0
  40. package/dist/resources/byod.d.ts +15 -0
  41. package/dist/resources/byod.d.ts.map +1 -0
  42. package/dist/resources/byod.js +13 -0
  43. package/dist/resources/byod.js.map +1 -0
  44. package/dist/resources/chats.d.ts +29 -0
  45. package/dist/resources/chats.d.ts.map +1 -0
  46. package/dist/resources/chats.js +28 -0
  47. package/dist/resources/chats.js.map +1 -0
  48. package/dist/resources/lines.d.ts +64 -27
  49. package/dist/resources/lines.d.ts.map +1 -1
  50. package/dist/resources/lines.js +77 -37
  51. package/dist/resources/lines.js.map +1 -1
  52. package/dist/resources/lookup.d.ts +15 -0
  53. package/dist/resources/lookup.d.ts.map +1 -0
  54. package/dist/resources/lookup.js +13 -0
  55. package/dist/resources/lookup.js.map +1 -0
  56. package/dist/resources/messages.d.ts +36 -67
  57. package/dist/resources/messages.d.ts.map +1 -1
  58. package/dist/resources/messages.js +37 -94
  59. package/dist/resources/messages.js.map +1 -1
  60. package/dist/resources/trial.d.ts +28 -0
  61. package/dist/resources/trial.d.ts.map +1 -0
  62. package/dist/resources/trial.js +31 -0
  63. package/dist/resources/trial.js.map +1 -0
  64. package/dist/resources/webhooks.d.ts +49 -39
  65. package/dist/resources/webhooks.d.ts.map +1 -1
  66. package/dist/resources/webhooks.js +52 -39
  67. package/dist/resources/webhooks.js.map +1 -1
  68. package/dist/retry.d.ts +14 -0
  69. package/dist/retry.d.ts.map +1 -0
  70. package/dist/retry.js +47 -0
  71. package/dist/retry.js.map +1 -0
  72. package/dist/types.d.ts +30 -196
  73. package/dist/types.d.ts.map +1 -1
  74. package/dist/types.js +1 -28
  75. package/dist/types.js.map +1 -1
  76. package/dist/version.d.ts +3 -0
  77. package/dist/version.d.ts.map +1 -0
  78. package/dist/version.js +20 -0
  79. package/dist/version.js.map +1 -0
  80. package/dist/webhooks/parse.d.ts +15 -0
  81. package/dist/webhooks/parse.d.ts.map +1 -0
  82. package/dist/webhooks/parse.js +47 -0
  83. package/dist/webhooks/parse.js.map +1 -0
  84. package/dist/webhooks/types.d.ts +93 -0
  85. package/dist/webhooks/types.d.ts.map +1 -0
  86. package/dist/webhooks/types.js +20 -0
  87. package/dist/webhooks/types.js.map +1 -0
  88. package/dist/webhooks/verify.d.ts +41 -0
  89. package/dist/webhooks/verify.d.ts.map +1 -0
  90. package/dist/webhooks/verify.js +110 -0
  91. package/dist/webhooks/verify.js.map +1 -0
  92. package/package.json +45 -30
  93. package/dist/paginate.d.ts +0 -33
  94. package/dist/paginate.d.ts.map +0 -1
  95. package/dist/paginate.js +0 -40
  96. package/dist/paginate.js.map +0 -1
  97. package/dist/webhook-events.d.ts +0 -61
  98. package/dist/webhook-events.d.ts.map +0 -1
  99. package/dist/webhook-events.js +0 -44
  100. package/dist/webhook-events.js.map +0 -1
  101. package/dist/webhook-verify.d.ts +0 -27
  102. package/dist/webhook-verify.d.ts.map +0 -1
  103. package/dist/webhook-verify.js +0 -42
  104. package/dist/webhook-verify.js.map +0 -1
@@ -0,0 +1,47 @@
1
+ import { WebhookSignatureError } from '../errors.js';
2
+ /**
3
+ * Parse a raw webhook delivery body into a typed envelope. Throws
4
+ * `WebhookSignatureError` if the body is not valid JSON or doesn't carry
5
+ * the §4.1 envelope shape (`event_id`, `event_type`, `data`). Use
6
+ * `verifyWebhook` for the full HMAC + replay-window verification — this
7
+ * is the parsing-only half for cases where verification happened
8
+ * out-of-band (e.g. behind a trusted gateway).
9
+ *
10
+ * **Consumers MUST dedupe by `event.event_id`** — webhook delivery is
11
+ * at-least-once, so retries replay the same envelope. Without dedup,
12
+ * processing the same event twice produces duplicate side-effects.
13
+ */
14
+ export function parseWebhookEvent(rawBody) {
15
+ let parsed;
16
+ try {
17
+ parsed = JSON.parse(rawBody);
18
+ }
19
+ catch (err) {
20
+ throw new WebhookSignatureError(`Webhook body is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
21
+ }
22
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
23
+ throw new WebhookSignatureError('Webhook body must be a JSON object');
24
+ }
25
+ const obj = parsed;
26
+ if (typeof obj.api_version !== 'string' || obj.api_version.length === 0) {
27
+ throw new WebhookSignatureError('Webhook envelope missing api_version');
28
+ }
29
+ if (typeof obj.event_id !== 'string' || obj.event_id.length === 0) {
30
+ throw new WebhookSignatureError('Webhook envelope missing event_id');
31
+ }
32
+ if (typeof obj.event_type !== 'string' || obj.event_type.length === 0) {
33
+ throw new WebhookSignatureError('Webhook envelope missing event_type');
34
+ }
35
+ if (typeof obj.created_at !== 'string' || obj.created_at.length === 0) {
36
+ throw new WebhookSignatureError('Webhook envelope missing created_at');
37
+ }
38
+ // `data` must be a plain object — arrays satisfy `typeof === 'object'`
39
+ // but the contract §4.1 envelope is `{ ...fields, data: { message: ... } }`,
40
+ // never an array. Rejecting at the boundary keeps the
41
+ // `Record<string, unknown>` declared type aligned with runtime.
42
+ if (typeof obj.data !== 'object' || obj.data === null || Array.isArray(obj.data)) {
43
+ throw new WebhookSignatureError('Webhook envelope missing data');
44
+ }
45
+ return obj;
46
+ }
47
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/webhooks/parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAGpD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,IAAI,MAAe,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IAC9B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,qBAAqB,CAC7B,mCAAmC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACtF,CAAA;IACH,CAAC;IACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,qBAAqB,CAAC,oCAAoC,CAAC,CAAA;IACvE,CAAC;IACD,MAAM,GAAG,GAAG,MAAiC,CAAA;IAC7C,IAAI,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,IAAI,GAAG,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,qBAAqB,CAAC,sCAAsC,CAAC,CAAA;IACzE,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,qBAAqB,CAAC,mCAAmC,CAAC,CAAA;IACtE,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,qBAAqB,CAAC,qCAAqC,CAAC,CAAA;IACxE,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,qBAAqB,CAAC,qCAAqC,CAAC,CAAA;IACxE,CAAC;IACD,uEAAuE;IACvE,6EAA6E;IAC7E,sDAAsD;IACtD,gEAAgE;IAChE,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACjF,MAAM,IAAI,qBAAqB,CAAC,+BAA+B,CAAC,CAAA;IAClE,CAAC;IACD,OAAO,GAAkC,CAAA;AAC3C,CAAC"}
@@ -0,0 +1,93 @@
1
+ import type { Message, Reaction } from '../resources/messages.js';
2
+ /**
3
+ * Common envelope for every webhook event. Per contract §4.1: every
4
+ * delivery body is shaped `{ api_version, event_id, event_type,
5
+ * created_at, trace_id?, data }`. `data` is a per-event-type payload —
6
+ * the type guards below narrow it to a typed shape.
7
+ */
8
+ export interface WebhookEventBase {
9
+ api_version: string;
10
+ event_id: string;
11
+ event_type: string;
12
+ created_at: string;
13
+ trace_id?: string;
14
+ data: Record<string, unknown>;
15
+ }
16
+ export type MessageEventType = 'message.queued' | 'message.scheduled' | 'message.sent' | 'message.delivered' | 'message.failed' | 'message.received' | 'message.read' | 'message.cancelled';
17
+ export interface MessageEvent extends WebhookEventBase {
18
+ event_type: `message.${string}`;
19
+ data: {
20
+ message: Message;
21
+ };
22
+ }
23
+ export type ReactionEventType = 'reaction.added' | 'reaction.received' | 'reaction.removed';
24
+ export interface ReactionEvent extends WebhookEventBase {
25
+ event_type: `reaction.${string}`;
26
+ data: {
27
+ reaction: Reaction;
28
+ };
29
+ }
30
+ export type LineEventType = 'line.connected' | 'line.disconnected' | 'line.offline' | 'line.degraded' | 'line.apple_id_flagged' | 'line.quota_warning' | 'line.quota_exceeded';
31
+ /**
32
+ * `data` is intentionally typed as `Record<string, unknown>` until the
33
+ * contract pins per-event-type field shapes. Today's payloads vary by
34
+ * event (e.g. `line.apple_id_flagged` carries `flag_reason`,
35
+ * `line.quota_warning` carries `bucket` + `usage`), and surfacing a
36
+ * union with optional fields would let TypeScript autocomplete suggest
37
+ * keys that aren't present at runtime. Tighten when the contract is
38
+ * amended to specify the per-event shapes.
39
+ */
40
+ export interface LineEvent extends WebhookEventBase {
41
+ event_type: `line.${string}`;
42
+ data: Record<string, unknown>;
43
+ }
44
+ export type TypingIndicatorEventType = 'typing_indicator.started' | 'typing_indicator.stopped';
45
+ /**
46
+ * `data` is intentionally `Record<string, unknown>` — the partner-facing
47
+ * shape is not yet documented in the contract. See `LineEvent` for the
48
+ * same rationale.
49
+ */
50
+ export interface TypingIndicatorEvent extends WebhookEventBase {
51
+ event_type: `typing_indicator.${string}`;
52
+ data: Record<string, unknown>;
53
+ }
54
+ export type TrialEventType = 'trial.linked' | 'trial.dormant' | 'trial.reactivated';
55
+ /** See `LineEvent` for why `data` is loose. */
56
+ export interface TrialEvent extends WebhookEventBase {
57
+ event_type: `trial.${string}`;
58
+ data: Record<string, unknown>;
59
+ }
60
+ export type PreOrderEventType = 'pre_order.fulfilled' | 'pre_order.cancelled' | 'pre_order.refunded';
61
+ /**
62
+ * Pre-order lifecycle events fired when ops procures hardware against a
63
+ * customer pre-order. `pre_order.fulfilled` carries the freshly-activated
64
+ * `line_id` so partner dashboards can wire it without a follow-up GET.
65
+ * `data` is left loose for the same reason as `LineEvent` until the
66
+ * per-event-type shapes are pinned in the contract.
67
+ */
68
+ export interface PreOrderEvent extends WebhookEventBase {
69
+ event_type: `pre_order.${string}`;
70
+ data: Record<string, unknown>;
71
+ }
72
+ /**
73
+ * Discriminated union over the typed event families plus `WebhookEventBase`
74
+ * as the catch-all for event types not yet known to this SDK version.
75
+ * Use a type guard (`isMessageEvent`, `isReactionEvent`, ...) to narrow
76
+ * inside an event handler:
77
+ *
78
+ * ```ts
79
+ * function handle(event: WebhookEvent) {
80
+ * if (isMessageEvent(event)) {
81
+ * console.log(event.data.message.id) // typed as Message
82
+ * }
83
+ * }
84
+ * ```
85
+ */
86
+ export type WebhookEvent = MessageEvent | ReactionEvent | LineEvent | TypingIndicatorEvent | TrialEvent | PreOrderEvent | WebhookEventBase;
87
+ export declare function isMessageEvent(event: WebhookEventBase): event is MessageEvent;
88
+ export declare function isReactionEvent(event: WebhookEventBase): event is ReactionEvent;
89
+ export declare function isLineEvent(event: WebhookEventBase): event is LineEvent;
90
+ export declare function isTypingIndicatorEvent(event: WebhookEventBase): event is TypingIndicatorEvent;
91
+ export declare function isTrialEvent(event: WebhookEventBase): event is TrialEvent;
92
+ export declare function isPreOrderEvent(event: WebhookEventBase): event is PreOrderEvent;
93
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/webhooks/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /* ─────────────────────────── Type guards ─────────────────────────────── */
2
+ export function isMessageEvent(event) {
3
+ return event.event_type.startsWith('message.');
4
+ }
5
+ export function isReactionEvent(event) {
6
+ return event.event_type.startsWith('reaction.');
7
+ }
8
+ export function isLineEvent(event) {
9
+ return event.event_type.startsWith('line.');
10
+ }
11
+ export function isTypingIndicatorEvent(event) {
12
+ return event.event_type.startsWith('typing_indicator.');
13
+ }
14
+ export function isTrialEvent(event) {
15
+ return event.event_type.startsWith('trial.');
16
+ }
17
+ export function isPreOrderEvent(event) {
18
+ return event.event_type.startsWith('pre_order.');
19
+ }
20
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/webhooks/types.ts"],"names":[],"mappings":"AA0JA,6EAA6E;AAE7E,MAAM,UAAU,cAAc,CAAC,KAAuB;IACpD,OAAO,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;AAChD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAuB;IACrD,OAAO,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;AACjD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,OAAO,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;AAC7C,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,KAAuB;IAEvB,OAAO,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAA;AACzD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAuB;IAClD,OAAO,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;AAC9C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAuB;IACrD,OAAO,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,YAAY,CAAC,CAAA;AAClD,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { WebhookEventBase } from './types.js';
2
+ export interface VerifyWebhookOptions {
3
+ /**
4
+ * Maximum drift in seconds between the request timestamp and the
5
+ * verifier's clock. Defaults to 300 (5 minutes) per contract §4.4.
6
+ * Set higher only if your server is reverse-proxied through a queue
7
+ * that may stall messages.
8
+ */
9
+ tolerance?: number;
10
+ /**
11
+ * Override the "now" clock — exposed primarily so tests can pin a
12
+ * deterministic time. Returns unix seconds.
13
+ */
14
+ now?: () => number;
15
+ }
16
+ /**
17
+ * Verify an incoming webhook delivery using the iSnap signing scheme:
18
+ *
19
+ * - `X-iSnap-Signature` header carries the hex-encoded
20
+ * `HMAC-SHA256(secret, "<timestamp>.<rawBody>")`
21
+ * - `X-iSnap-Timestamp` header carries the unix-second issuance time
22
+ *
23
+ * Throws `WebhookSignatureError` on missing headers, malformed
24
+ * timestamp, expired timestamp (outside `tolerance`), or signature
25
+ * mismatch. Returns the parsed event envelope on success.
26
+ *
27
+ * `secret` may be a single secret string OR an array — pass `[newSecret,
28
+ * oldSecret]` during the 5-minute rotate-secret grace window so both
29
+ * still validate. The verifier tries each in order and returns on the
30
+ * first match.
31
+ *
32
+ * Headers may be a `Record<string, string>` (e.g. Express's `req.headers`)
33
+ * or a `Headers` instance — both are read case-insensitively.
34
+ *
35
+ * **Consumers MUST dedupe by `event.event_id`** — retries replay the same
36
+ * envelope (same `event_id`, same body, same signature, refreshed
37
+ * timestamp). Without consumer-side dedup, an at-least-once delivery
38
+ * model produces duplicate side-effects on the partner system.
39
+ */
40
+ export declare function verifyWebhook(rawBody: string, headers: Record<string, string | string[] | undefined> | Headers, secret: string | readonly string[], options?: VerifyWebhookOptions): WebhookEventBase;
41
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/webhooks/verify.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAIlD,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,OAAO,EAChE,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EAClC,OAAO,GAAE,oBAAyB,GACjC,gBAAgB,CA+DlB"}
@@ -0,0 +1,110 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+ import { WebhookSignatureError } from '../errors.js';
3
+ import { parseWebhookEvent } from './parse.js';
4
+ const DEFAULT_TOLERANCE_SECONDS = 300;
5
+ /**
6
+ * Verify an incoming webhook delivery using the iSnap signing scheme:
7
+ *
8
+ * - `X-iSnap-Signature` header carries the hex-encoded
9
+ * `HMAC-SHA256(secret, "<timestamp>.<rawBody>")`
10
+ * - `X-iSnap-Timestamp` header carries the unix-second issuance time
11
+ *
12
+ * Throws `WebhookSignatureError` on missing headers, malformed
13
+ * timestamp, expired timestamp (outside `tolerance`), or signature
14
+ * mismatch. Returns the parsed event envelope on success.
15
+ *
16
+ * `secret` may be a single secret string OR an array — pass `[newSecret,
17
+ * oldSecret]` during the 5-minute rotate-secret grace window so both
18
+ * still validate. The verifier tries each in order and returns on the
19
+ * first match.
20
+ *
21
+ * Headers may be a `Record<string, string>` (e.g. Express's `req.headers`)
22
+ * or a `Headers` instance — both are read case-insensitively.
23
+ *
24
+ * **Consumers MUST dedupe by `event.event_id`** — retries replay the same
25
+ * envelope (same `event_id`, same body, same signature, refreshed
26
+ * timestamp). Without consumer-side dedup, an at-least-once delivery
27
+ * model produces duplicate side-effects on the partner system.
28
+ */
29
+ export function verifyWebhook(rawBody, headers, secret, options = {}) {
30
+ const sigHeader = getHeader(headers, 'x-isnap-signature');
31
+ if (!sigHeader) {
32
+ throw new WebhookSignatureError('Missing X-iSnap-Signature header');
33
+ }
34
+ const tsHeader = getHeader(headers, 'x-isnap-timestamp');
35
+ if (!tsHeader) {
36
+ throw new WebhookSignatureError('Missing X-iSnap-Timestamp header');
37
+ }
38
+ // Strict integer-only check — `Number.parseInt` would happily accept
39
+ // `"1714478400foo"` and silently truncate. The HMAC compare downstream
40
+ // catches the resulting payload mismatch, but rejecting earlier here
41
+ // surfaces a clearer error and avoids one unnecessary HMAC computation.
42
+ if (!/^\d+$/.test(tsHeader)) {
43
+ throw new WebhookSignatureError(`Malformed X-iSnap-Timestamp: ${tsHeader}`);
44
+ }
45
+ const timestamp = Number(tsHeader);
46
+ if (!Number.isFinite(timestamp) || timestamp <= 0) {
47
+ throw new WebhookSignatureError(`Malformed X-iSnap-Timestamp: ${tsHeader}`);
48
+ }
49
+ const tolerance = options.tolerance ?? DEFAULT_TOLERANCE_SECONDS;
50
+ // Validate before use — `Math.abs(...) > NaN` is always false (so the
51
+ // replay-window check would silently pass), `Math.abs(...) > -1` is
52
+ // always true (every timestamp rejected), and `Infinity` makes every
53
+ // timestamp pass. Reject the bad inputs explicitly so misuse fails
54
+ // loudly at the verify call site instead of hiding a security gap.
55
+ if (!Number.isFinite(tolerance) || tolerance < 0) {
56
+ throw new WebhookSignatureError(`tolerance must be a finite non-negative number (got ${String(tolerance)})`);
57
+ }
58
+ const nowSec = (options.now ?? defaultNow)();
59
+ if (Math.abs(nowSec - timestamp) > tolerance) {
60
+ throw new WebhookSignatureError(`Webhook timestamp is outside the ${tolerance}s tolerance window (drift: ${nowSec - timestamp}s)`);
61
+ }
62
+ const candidates = typeof secret === 'string' ? [secret] : secret;
63
+ if (candidates.length === 0) {
64
+ throw new WebhookSignatureError('No webhook secret(s) provided');
65
+ }
66
+ const provided = decodeHex(sigHeader);
67
+ if (!provided) {
68
+ throw new WebhookSignatureError('X-iSnap-Signature is not valid hex');
69
+ }
70
+ const signedPayload = `${timestamp}.${rawBody}`;
71
+ // Multi-secret loop returns early on first match — this is a minor
72
+ // timing side-channel during the rotate-secret grace window (an
73
+ // attacker who controls request timing could distinguish "matched first
74
+ // candidate" from "matched second"). Practical exploit value is near
75
+ // zero: knowing which secret matched does not help forge signatures
76
+ // for either secret. Worth revisiting if real partners run rotation
77
+ // through hostile networks.
78
+ for (const candidate of candidates) {
79
+ const expected = createHmac('sha256', candidate).update(signedPayload).digest();
80
+ if (provided.length === expected.length && timingSafeEqual(provided, expected)) {
81
+ return parseWebhookEvent(rawBody);
82
+ }
83
+ }
84
+ throw new WebhookSignatureError('Webhook signature did not match any provided secret');
85
+ }
86
+ function getHeader(headers, name) {
87
+ if (typeof Headers !== 'undefined' && headers instanceof Headers) {
88
+ return headers.get(name);
89
+ }
90
+ const lower = name.toLowerCase();
91
+ for (const [key, value] of Object.entries(headers)) {
92
+ if (key.toLowerCase() !== lower)
93
+ continue;
94
+ if (Array.isArray(value))
95
+ return value[0] ?? null;
96
+ if (typeof value === 'string')
97
+ return value;
98
+ return null;
99
+ }
100
+ return null;
101
+ }
102
+ function decodeHex(hex) {
103
+ if (!/^[0-9a-fA-F]+$/.test(hex) || hex.length % 2 !== 0)
104
+ return null;
105
+ return Buffer.from(hex, 'hex');
106
+ }
107
+ function defaultNow() {
108
+ return Math.floor(Date.now() / 1000);
109
+ }
110
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/webhooks/verify.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,50 +1,65 @@
1
1
  {
2
2
  "name": "@isnap/sdk",
3
- "version": "0.1.0",
4
- "description": "Official Node.js/TypeScript SDK for the iSnap messaging API. Zero runtime dependencies.",
3
+ "version": "1.1.0-next.38",
4
+ "description": "Official TypeScript SDK for the iSnap P2P telephony API",
5
+ "license": "MIT",
5
6
  "type": "module",
6
- "main": "dist/index.js",
7
- "module": "dist/index.js",
8
- "types": "dist/index.d.ts",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "import": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
13
- },
14
- "./webhook-verify": {
15
- "import": "./dist/webhook-verify.js",
16
- "types": "./dist/webhook-verify.d.ts"
17
- },
18
- "./webhooks": {
19
- "import": "./dist/webhook-events.js",
20
- "types": "./dist/webhook-events.d.ts"
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
21
13
  }
22
14
  },
23
- "files": ["dist"],
24
- "scripts": {
25
- "build": "tsc",
26
- "typecheck": "tsc --noEmit",
27
- "test": "vitest run",
28
- "test:watch": "vitest",
29
- "prepublishOnly": "npm run build"
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "CHANGELOG.md",
19
+ "LICENSE"
20
+ ],
21
+ "keywords": [
22
+ "isnap",
23
+ "imessage",
24
+ "sms",
25
+ "rcs",
26
+ "p2p",
27
+ "telephony",
28
+ "messaging",
29
+ "sdk",
30
+ "typescript"
31
+ ],
32
+ "homepage": "https://github.com/WhatSnap/iSnap-App/tree/main/packages/sdk#readme",
33
+ "bugs": {
34
+ "url": "https://github.com/WhatSnap/iSnap-App/issues"
30
35
  },
31
- "author": "iSnap <hello@isnap.dev>",
32
- "license": "MIT",
33
36
  "repository": {
34
37
  "type": "git",
35
- "url": "https://github.com/isnap-dev/isnap-node"
38
+ "url": "git+https://github.com/WhatSnap/iSnap-App.git",
39
+ "directory": "packages/sdk"
36
40
  },
37
- "homepage": "https://github.com/isnap-dev/isnap-node#readme",
38
- "bugs": {
39
- "url": "https://github.com/isnap-dev/isnap-node/issues"
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.build.json && bun scripts/copy-generated.ts",
43
+ "lint": "eslint src/",
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "generate": "openapi-typescript ../backend/openapi.json -o src/generated/openapi.d.ts",
48
+ "prepublishOnly": "bun run lint && bun run typecheck && bun run test && bun run build"
40
49
  },
41
- "keywords": ["isnap", "messaging", "sms", "imessage", "p2p", "sdk"],
42
50
  "engines": {
43
- "node": ">=18"
51
+ "node": ">=20"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
44
55
  },
45
56
  "devDependencies": {
57
+ "@eslint/js": "^10.0.1",
46
58
  "@types/node": "^22.0.0",
59
+ "eslint": "^9.0.0",
60
+ "openapi-typescript": "^7.13.0",
47
61
  "typescript": "^5.7.0",
62
+ "typescript-eslint": "^8.57.1",
48
63
  "vitest": "^3.0.0"
49
64
  }
50
65
  }
@@ -1,33 +0,0 @@
1
- /**
2
- * Generic async generator that paginates over any SDK list endpoint.
3
- *
4
- * Yields individual items from each page until all pages have been consumed.
5
- *
6
- * @param fetcher - A function that accepts `{ page, limit }` and returns a paginated response
7
- * @param params - Initial pagination parameters (defaults: `page = 1`, `limit = 100`)
8
- * @param dataKey - The key in the response object that holds the array of items
9
- *
10
- * @example
11
- * ```ts
12
- * import { init, paginate } from '@isnap/sdk';
13
- *
14
- * const client = init({ apiKey: 'isnap_your_key' });
15
- *
16
- * for await (const msg of paginate(
17
- * (p) => client.messages.list(p),
18
- * { status: 'delivered' },
19
- * 'messages',
20
- * )) {
21
- * console.log(msg.id, msg.body);
22
- * }
23
- * ```
24
- */
25
- export declare function paginate<TItem, TParams extends {
26
- page?: number;
27
- limit?: number;
28
- }, TKey extends string>(fetcher: (params: TParams) => Promise<{
29
- total: number;
30
- page: number;
31
- limit: number;
32
- } & Record<TKey, TItem[]>>, params: TParams, dataKey: TKey): AsyncGenerator<TItem, void, undefined>;
33
- //# sourceMappingURL=paginate.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"paginate.d.ts","sourceRoot":"","sources":["../src/paginate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAuB,QAAQ,CAC7B,KAAK,EACL,OAAO,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EACjD,IAAI,SAAS,MAAM,EAEnB,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,EAC7G,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,IAAI,GACZ,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,CAkBxC"}
package/dist/paginate.js DELETED
@@ -1,40 +0,0 @@
1
- /**
2
- * Generic async generator that paginates over any SDK list endpoint.
3
- *
4
- * Yields individual items from each page until all pages have been consumed.
5
- *
6
- * @param fetcher - A function that accepts `{ page, limit }` and returns a paginated response
7
- * @param params - Initial pagination parameters (defaults: `page = 1`, `limit = 100`)
8
- * @param dataKey - The key in the response object that holds the array of items
9
- *
10
- * @example
11
- * ```ts
12
- * import { init, paginate } from '@isnap/sdk';
13
- *
14
- * const client = init({ apiKey: 'isnap_your_key' });
15
- *
16
- * for await (const msg of paginate(
17
- * (p) => client.messages.list(p),
18
- * { status: 'delivered' },
19
- * 'messages',
20
- * )) {
21
- * console.log(msg.id, msg.body);
22
- * }
23
- * ```
24
- */
25
- export async function* paginate(fetcher, params, dataKey) {
26
- let page = params.page ?? 1;
27
- const limit = params.limit ?? 100;
28
- while (true) {
29
- const response = await fetcher({ ...params, page, limit });
30
- const items = response[dataKey];
31
- for (const item of items) {
32
- yield item;
33
- }
34
- if (items.length < limit || page * limit >= response.total) {
35
- break;
36
- }
37
- page++;
38
- }
39
- }
40
- //# sourceMappingURL=paginate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"paginate.js","sourceRoot":"","sources":["../src/paginate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,QAAQ,CAK7B,OAA6G,EAC7G,MAAe,EACf,OAAa;IAEb,IAAI,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC;IAElC,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,KAAK,EAAa,CAAC,CAAC;QACtE,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAEhC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,CAAC;QACb,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;YAC3D,MAAM;QACR,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC;AACH,CAAC"}
@@ -1,61 +0,0 @@
1
- import type { WebhookEvent } from './types.js';
2
- /**
3
- * Parameters for parsing an incoming webhook request.
4
- */
5
- export interface ParseWebhookOptions {
6
- /** The webhook secret (from {@link Webhook.secret}). */
7
- secret: string;
8
- /** The `x-isnap-signature` header value. */
9
- signature: string;
10
- /** The `x-isnap-timestamp` header value (Unix seconds). */
11
- timestamp: string;
12
- /** The raw request body string. */
13
- body: string;
14
- }
15
- /**
16
- * Result of parsing a webhook request.
17
- */
18
- export interface ParseWebhookResult {
19
- /** Whether the signature was valid and the timestamp was fresh. */
20
- verified: boolean;
21
- /** The parsed and typed webhook event. Only meaningful when `verified` is `true`. */
22
- event: WebhookEvent;
23
- }
24
- /**
25
- * Verify and parse an incoming webhook request in a single call.
26
- *
27
- * Combines signature verification with JSON parsing to produce a typed
28
- * {@link WebhookEvent} that supports discriminated-union narrowing.
29
- *
30
- * @param options - Signature, timestamp, secret, and raw body
31
- * @returns Object with `verified` flag and the parsed `event`
32
- *
33
- * @example
34
- * ```ts
35
- * import { parseWebhook } from '@isnap/sdk';
36
- *
37
- * app.post('/webhooks/isnap', (req, res) => {
38
- * const { verified, event } = parseWebhook({
39
- * secret: process.env.WEBHOOK_SECRET!,
40
- * signature: req.headers['x-isnap-signature'] as string,
41
- * timestamp: req.headers['x-isnap-timestamp'] as string,
42
- * body: req.body, // raw string
43
- * });
44
- *
45
- * if (!verified) return res.status(401).send('Invalid signature');
46
- *
47
- * switch (event.event) {
48
- * case 'message.sent':
49
- * console.log('Sent:', event.data.id);
50
- * break;
51
- * case 'message.received':
52
- * console.log('Received:', event.data.body);
53
- * break;
54
- * }
55
- *
56
- * res.sendStatus(200);
57
- * });
58
- * ```
59
- */
60
- export declare function parseWebhook(options: ParseWebhookOptions): ParseWebhookResult;
61
- //# sourceMappingURL=webhook-events.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhook-events.d.ts","sourceRoot":"","sources":["../src/webhook-events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,QAAQ,EAAE,OAAO,CAAC;IAClB,qFAAqF;IACrF,KAAK,EAAE,YAAY,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAK7E"}
@@ -1,44 +0,0 @@
1
- import { verifyWebhookSignature } from './webhook-verify.js';
2
- /**
3
- * Verify and parse an incoming webhook request in a single call.
4
- *
5
- * Combines signature verification with JSON parsing to produce a typed
6
- * {@link WebhookEvent} that supports discriminated-union narrowing.
7
- *
8
- * @param options - Signature, timestamp, secret, and raw body
9
- * @returns Object with `verified` flag and the parsed `event`
10
- *
11
- * @example
12
- * ```ts
13
- * import { parseWebhook } from '@isnap/sdk';
14
- *
15
- * app.post('/webhooks/isnap', (req, res) => {
16
- * const { verified, event } = parseWebhook({
17
- * secret: process.env.WEBHOOK_SECRET!,
18
- * signature: req.headers['x-isnap-signature'] as string,
19
- * timestamp: req.headers['x-isnap-timestamp'] as string,
20
- * body: req.body, // raw string
21
- * });
22
- *
23
- * if (!verified) return res.status(401).send('Invalid signature');
24
- *
25
- * switch (event.event) {
26
- * case 'message.sent':
27
- * console.log('Sent:', event.data.id);
28
- * break;
29
- * case 'message.received':
30
- * console.log('Received:', event.data.body);
31
- * break;
32
- * }
33
- *
34
- * res.sendStatus(200);
35
- * });
36
- * ```
37
- */
38
- export function parseWebhook(options) {
39
- const { secret, signature, timestamp, body } = options;
40
- const event = JSON.parse(body);
41
- const verified = verifyWebhookSignature(secret, signature, timestamp, body);
42
- return { verified, event };
43
- }
44
- //# sourceMappingURL=webhook-events.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhook-events.js","sourceRoot":"","sources":["../src/webhook-events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AA2B7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,UAAU,YAAY,CAAC,OAA4B;IACvD,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IACvD,MAAM,KAAK,GAAiB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IAC5E,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC"}