@isnap/sdk 0.0.0-next.3

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 (93) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +242 -0
  4. package/dist/client.d.ts +50 -0
  5. package/dist/client.d.ts.map +1 -0
  6. package/dist/client.js +242 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/config.d.ts +43 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +55 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/errors.d.ts +53 -0
  13. package/dist/errors.d.ts.map +1 -0
  14. package/dist/errors.js +100 -0
  15. package/dist/errors.js.map +1 -0
  16. package/dist/generated/openapi.d.ts +7963 -0
  17. package/dist/idempotency.d.ts +2 -0
  18. package/dist/idempotency.d.ts.map +1 -0
  19. package/dist/idempotency.js +4 -0
  20. package/dist/idempotency.js.map +1 -0
  21. package/dist/index.d.ts +28 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +14 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/internal/sleep.d.ts +10 -0
  26. package/dist/internal/sleep.d.ts.map +1 -0
  27. package/dist/internal/sleep.js +37 -0
  28. package/dist/internal/sleep.js.map +1 -0
  29. package/dist/pagination.d.ts +10 -0
  30. package/dist/pagination.d.ts.map +1 -0
  31. package/dist/pagination.js +16 -0
  32. package/dist/pagination.js.map +1 -0
  33. package/dist/resources/attachments.d.ts +45 -0
  34. package/dist/resources/attachments.d.ts.map +1 -0
  35. package/dist/resources/attachments.js +120 -0
  36. package/dist/resources/attachments.js.map +1 -0
  37. package/dist/resources/base.d.ts +6 -0
  38. package/dist/resources/base.d.ts.map +1 -0
  39. package/dist/resources/base.js +7 -0
  40. package/dist/resources/base.js.map +1 -0
  41. package/dist/resources/byod.d.ts +15 -0
  42. package/dist/resources/byod.d.ts.map +1 -0
  43. package/dist/resources/byod.js +13 -0
  44. package/dist/resources/byod.js.map +1 -0
  45. package/dist/resources/chats.d.ts +29 -0
  46. package/dist/resources/chats.d.ts.map +1 -0
  47. package/dist/resources/chats.js +28 -0
  48. package/dist/resources/chats.js.map +1 -0
  49. package/dist/resources/lines.d.ts +70 -0
  50. package/dist/resources/lines.d.ts.map +1 -0
  51. package/dist/resources/lines.js +86 -0
  52. package/dist/resources/lines.js.map +1 -0
  53. package/dist/resources/lookup.d.ts +15 -0
  54. package/dist/resources/lookup.d.ts.map +1 -0
  55. package/dist/resources/lookup.js +13 -0
  56. package/dist/resources/lookup.js.map +1 -0
  57. package/dist/resources/messages.d.ts +42 -0
  58. package/dist/resources/messages.d.ts.map +1 -0
  59. package/dist/resources/messages.js +41 -0
  60. package/dist/resources/messages.js.map +1 -0
  61. package/dist/resources/trial.d.ts +28 -0
  62. package/dist/resources/trial.d.ts.map +1 -0
  63. package/dist/resources/trial.js +31 -0
  64. package/dist/resources/trial.js.map +1 -0
  65. package/dist/resources/webhooks.d.ts +58 -0
  66. package/dist/resources/webhooks.d.ts.map +1 -0
  67. package/dist/resources/webhooks.js +65 -0
  68. package/dist/resources/webhooks.js.map +1 -0
  69. package/dist/retry.d.ts +14 -0
  70. package/dist/retry.d.ts.map +1 -0
  71. package/dist/retry.js +47 -0
  72. package/dist/retry.js.map +1 -0
  73. package/dist/types.d.ts +33 -0
  74. package/dist/types.d.ts.map +1 -0
  75. package/dist/types.js +2 -0
  76. package/dist/types.js.map +1 -0
  77. package/dist/version.d.ts +2 -0
  78. package/dist/version.d.ts.map +1 -0
  79. package/dist/version.js +4 -0
  80. package/dist/version.js.map +1 -0
  81. package/dist/webhooks/parse.d.ts +15 -0
  82. package/dist/webhooks/parse.d.ts.map +1 -0
  83. package/dist/webhooks/parse.js +47 -0
  84. package/dist/webhooks/parse.js.map +1 -0
  85. package/dist/webhooks/types.d.ts +80 -0
  86. package/dist/webhooks/types.d.ts.map +1 -0
  87. package/dist/webhooks/types.js +17 -0
  88. package/dist/webhooks/types.js.map +1 -0
  89. package/dist/webhooks/verify.d.ts +41 -0
  90. package/dist/webhooks/verify.d.ts.map +1 -0
  91. package/dist/webhooks/verify.js +110 -0
  92. package/dist/webhooks/verify.js.map +1 -0
  93. package/package.json +65 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@isnap/sdk` will be documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Pre-1.0 versions may introduce breaking changes between 0.x → 0.(x+1) minor bumps.
4
+
5
+ ## [Unreleased]
6
+
7
+ ### Added
8
+ - Initial public surface targeting iSnap API contract `v1.0.0`.
9
+ - `ISnapClient` with retries (jittered exponential backoff on 429/5xx), per-attempt timeout, auto-`Idempotency-Key` for mutating verbs, header injection, abortable requests.
10
+ - Resource classes: `messages`, `chats`, `lines`, `lookup`, `attachments`, `webhooks`, `byod`, `trial`.
11
+ - `attachments.upload()` — transparent 3-step (POST register → presigned R2 PUT → poll until `ready`) with 5 GB cap and four typed failure modes.
12
+ - Cursor pagination as `AsyncIterable` with optional `AbortSignal` on `messages.list`, `webhooks.deliveries`, `webhooks.events`.
13
+ - Webhook helpers: `verifyWebhook` (HMAC-SHA256 + 5min replay window + multi-secret rotation), `parseWebhookEvent`, prefix-based type guards (`isMessageEvent`, `isReactionEvent`, `isLineEvent`, `isTypingEvent`, `isTrialEvent`).
14
+ - Error hierarchy mirroring contract §6: `APIError` + 13 subclasses, all carrying `status`, `code`, `traceId`, `requestId`. Connection failures surface as `APIConnectionError` with native `Error.cause`.
15
+ - Generated TypeScript types (`src/generated/openapi.d.ts`) compiled from the backend's frozen `openapi.json`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 iSnap
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,242 @@
1
+ # @isnap/sdk
2
+
3
+ Official TypeScript SDK for the [iSnap](https://isnap.ai) P2P telephony API — real iMessage / SMS / RCS lines from real handsets, exposed as a single typed client.
4
+
5
+ > Pre-1.0. Targets contract `docs/api-contract.md` v1.0.0. Public surface is stable across the v0.x line; breaking changes will bump 0.x → 0.(x+1) until 1.0.0.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ bun add @isnap/sdk
11
+ # or
12
+ npm install @isnap/sdk
13
+ ```
14
+
15
+ Requires Node 20+ (or Bun 1.2+). The SDK uses native `fetch`, `node:crypto`, and ES2022 `Error.cause` — no polyfills.
16
+
17
+ ## Quick start
18
+
19
+ ```ts
20
+ import { ISnapClient } from '@isnap/sdk'
21
+
22
+ const client = new ISnapClient({ apiKey: process.env.ISNAP_API_KEY! })
23
+
24
+ const message = await client.messages.send({
25
+ line_id: 'line_01HX...',
26
+ to: '+15551234567',
27
+ body: 'Hello from iSnap'
28
+ })
29
+
30
+ console.log(message.id, message.status)
31
+ ```
32
+
33
+ ## Configuration
34
+
35
+ ```ts
36
+ new ISnapClient({
37
+ apiKey: 'sk_live_...', // required
38
+ baseUrl: 'https://api.isnap.ai', // default
39
+ maxRetries: 3, // default 3 — retries on 429/5xx with jittered backoff
40
+ timeoutMs: 30_000, // per-attempt timeout
41
+ idempotencyKey: 'auto', // 'auto' generates UUIDs for POST/PATCH/PUT/DELETE; 'manual' = pass via opts
42
+ fetch: undefined, // override (e.g. an instrumented fetch)
43
+ onRetry: (info) => console.warn('retry', info)
44
+ })
45
+ ```
46
+
47
+ Every resource method accepts an optional `RequestOptions` final argument: `{ signal, idempotencyKey, headers, query, maxRetries }`.
48
+
49
+ ## Resources
50
+
51
+ | Resource | What it does |
52
+ |---|---|
53
+ | `client.messages` | Send / get / cancel / list messages, add reactions |
54
+ | `client.chats` | Mark a chat read, send typing indicators, share a contact card |
55
+ | `client.lines` | List, configure, and inspect rented + BYOD lines, plus quota and queue snapshots |
56
+ | `client.lookup` | Test if a number is iMessage-capable before sending |
57
+ | `client.attachments` | Upload images / video / files (transparent 3-step flow) |
58
+ | `client.webhooks` | CRUD subscriptions, rotate secrets, list deliveries, replay events |
59
+ | `client.byod` | Pair a customer-owned Mac+iPhone or Android device |
60
+ | `client.trial` | Start, check, and convert trial sessions |
61
+
62
+ ## Sending a message
63
+
64
+ ```ts
65
+ const msg = await client.messages.send({
66
+ line_id: 'line_01HX...',
67
+ to: '+15551234567',
68
+ body: 'Order #1242 is on its way',
69
+ service: 'iMessage' // optional — falls back to SMS/RCS via the line's degradation chain
70
+ })
71
+ ```
72
+
73
+ `messages.send` is **idempotent by default** — the SDK auto-generates an `Idempotency-Key` for every POST. Retrying the same call with the same key returns the original message without sending a duplicate.
74
+
75
+ ## Listing with cursor pagination
76
+
77
+ `list()` returns an `AsyncIterable` that threads cursors automatically:
78
+
79
+ ```ts
80
+ for await (const msg of client.messages.list({ line_id: 'line_01HX...', limit: 100 })) {
81
+ console.log(msg.id, msg.created_at)
82
+ }
83
+ ```
84
+
85
+ To stop early, pass an `AbortSignal`. Aborting surfaces as a thrown `APIConnectionError` on the next page boundary, so wrap the loop:
86
+
87
+ ```ts
88
+ import { APIConnectionError } from '@isnap/sdk'
89
+
90
+ const ac = new AbortController()
91
+ setTimeout(() => ac.abort(), 5_000)
92
+
93
+ try {
94
+ for await (const msg of client.messages.list({}, { signal: ac.signal })) {
95
+ // ...
96
+ }
97
+ } catch (err) {
98
+ if (!(err instanceof APIConnectionError)) throw err
99
+ // expected — signal aborted the iterator
100
+ }
101
+ ```
102
+
103
+ ## Uploading an attachment
104
+
105
+ `attachments.upload()` wraps the 3-step contract (register → presigned PUT → poll) into one call:
106
+
107
+ ```ts
108
+ import { readFile } from 'node:fs/promises'
109
+
110
+ const file = await readFile('./photo.jpg')
111
+
112
+ const attachment = await client.attachments.upload(file, {
113
+ filename: 'photo.jpg',
114
+ contentType: 'image/jpeg'
115
+ })
116
+
117
+ await client.messages.send({
118
+ line_id: 'line_01HX...',
119
+ to: '+15551234567',
120
+ body: 'Here you go',
121
+ attachment_ids: [attachment.id]
122
+ })
123
+ ```
124
+
125
+ 5 GB cap. Four documented failure modes — note that the R2 PUT and the server-side `failed` transition share the same `upload_failed` code:
126
+
127
+ | Failure | `code` |
128
+ |---|---|
129
+ | `size_bytes` exceeds the 5 GB cap | `attachment_exceeds_storage_limit` |
130
+ | R2 presigned PUT returns non-2xx | `upload_failed` |
131
+ | Server transitions to `status: failed` | `upload_failed` |
132
+ | Polling exhausted before `status: ready` | `upload_timeout` |
133
+
134
+ ## Verifying webhooks
135
+
136
+ ```ts
137
+ import { verifyWebhook, isMessageEvent } from '@isnap/sdk'
138
+
139
+ // Express / Hono / native handler — give us the RAW body bytes (string).
140
+ async function handle(rawBody: string, headers: Record<string, string>) {
141
+ const event = verifyWebhook(rawBody, headers, process.env.ISNAP_WEBHOOK_SECRET!)
142
+
143
+ // Dedupe by event_id — delivery is at-least-once.
144
+ if (await alreadyProcessed(event.event_id)) return
145
+
146
+ if (isMessageEvent(event)) {
147
+ console.log(event.event_type, event.data.message.id)
148
+ }
149
+ }
150
+ ```
151
+
152
+ During a `rotate-secret` grace window pass both secrets:
153
+
154
+ ```ts
155
+ verifyWebhook(rawBody, headers, [newSecret, oldSecret])
156
+ ```
157
+
158
+ Throws `WebhookSignatureError` on missing headers, expired timestamp (5min tolerance), or signature mismatch.
159
+
160
+ If verification happens behind a trusted gateway, use `parseWebhookEvent(rawBody)` to skip HMAC and just type the envelope.
161
+
162
+ ## Errors
163
+
164
+ All API errors extend `APIError` and carry `status`, `code`, `message`, `traceId`, `requestId`. Subclasses match the §6 contract codes:
165
+
166
+ ```ts
167
+ import {
168
+ APIError,
169
+ APIConnectionError,
170
+ AuthenticationError,
171
+ BadRequestError,
172
+ ConflictError,
173
+ GoneError,
174
+ InternalServerError,
175
+ NotFoundError,
176
+ PaymentRequiredError,
177
+ PermissionDeniedError,
178
+ QuotaExceededError,
179
+ RateLimitError,
180
+ ServiceUnavailableError,
181
+ UnprocessableEntityError,
182
+ WebhookSignatureError
183
+ } from '@isnap/sdk'
184
+
185
+ try {
186
+ await client.messages.send({
187
+ line_id: 'line_01HX...',
188
+ to: '+15551234567',
189
+ body: 'hello'
190
+ })
191
+ } catch (err) {
192
+ if (err instanceof RateLimitError) {
193
+ // retry-after handled automatically by the retry loop, but visible here too
194
+ } else if (err instanceof QuotaExceededError) {
195
+ // pause sending until next quota window
196
+ } else if (err instanceof APIError) {
197
+ console.error(err.status, err.code, err.traceId)
198
+ } else if (err instanceof APIConnectionError) {
199
+ // network issue / timeout — already retried per `maxRetries`
200
+ } else {
201
+ throw err
202
+ }
203
+ }
204
+ ```
205
+
206
+ Retries: 429 + 5xx are retried automatically with jittered exponential backoff up to `maxRetries`. The retry budget is per-call.
207
+
208
+ ## Examples
209
+
210
+ Runnable scripts in [`examples/`](https://github.com/WhatSnap/iSnap-App/tree/main/packages/sdk/examples) (browse on GitHub — they live in the repo, not the published tarball):
211
+
212
+ - `send-message.ts` — minimal send
213
+ - `paginate-messages.ts` — async iterator + abort
214
+ - `upload-attachment.ts` — file upload + send with attachment
215
+ - `handle-webhook.ts` — `node:http` server that verifies and dispatches events
216
+
217
+ Run them against a local backend:
218
+
219
+ ```bash
220
+ ISNAP_API_KEY=sk_test_... bun run examples/send-message.ts
221
+ ```
222
+
223
+ ## Development
224
+
225
+ ```bash
226
+ bun install
227
+ bun run --filter '@isnap/sdk' test
228
+ bun run --filter '@isnap/sdk' typecheck
229
+ bun run --filter '@isnap/sdk' lint
230
+ bun run --filter '@isnap/sdk' build
231
+ ```
232
+
233
+ To regenerate types from the backend's frozen OpenAPI spec:
234
+
235
+ ```bash
236
+ bun run --filter '@isnap/backend' dump-openapi # writes packages/backend/openapi.json
237
+ bun run --filter '@isnap/sdk' generate # regenerates src/generated/openapi.d.ts
238
+ ```
239
+
240
+ ## License
241
+
242
+ MIT
@@ -0,0 +1,50 @@
1
+ import { type ClientConfig } from './config.js';
2
+ import { AttachmentsResource } from './resources/attachments.js';
3
+ import { ByodResource } from './resources/byod.js';
4
+ import { ChatsResource } from './resources/chats.js';
5
+ import { LinesResource } from './resources/lines.js';
6
+ import { LookupResource } from './resources/lookup.js';
7
+ import { MessagesResource } from './resources/messages.js';
8
+ import { TrialResource } from './resources/trial.js';
9
+ import { WebhooksResource } from './resources/webhooks.js';
10
+ import type { HttpMethod, RequestOptions } from './types.js';
11
+ export declare class ISnapClient {
12
+ private readonly config;
13
+ readonly attachments: AttachmentsResource;
14
+ readonly byod: ByodResource;
15
+ readonly chats: ChatsResource;
16
+ readonly lines: LinesResource;
17
+ readonly lookup: LookupResource;
18
+ readonly messages: MessagesResource;
19
+ readonly trial: TrialResource;
20
+ readonly webhooks: WebhooksResource;
21
+ constructor(config: ClientConfig);
22
+ /**
23
+ * Direct fetch with no SDK auth, no envelope parsing, no retry — used
24
+ * internally by resource methods that must call URLs OUTSIDE the iSnap
25
+ * API surface (e.g. presigned R2 PUTs during `attachments.upload`).
26
+ *
27
+ * The leading underscore signals "internal but unavoidably public": it
28
+ * lives on the public class because resource classes cannot reach the
29
+ * private `config.fetch` otherwise. SDK consumers should use the typed
30
+ * resource methods or `request()` for everything in `/v1/*` — calling
31
+ * this directly bypasses the SDK contract.
32
+ *
33
+ * Applies the same `config.timeoutMs` ceiling as `request()` so a
34
+ * stalled R2 PUT cannot hang the upload indefinitely.
35
+ *
36
+ * @internal
37
+ */
38
+ _externalFetch(url: string, init?: RequestInit): Promise<Response>;
39
+ /**
40
+ * Low-level request method. Resource classes wrap this with typed
41
+ * camelCase signatures; advanced users can call it directly for
42
+ * endpoints not yet covered by a resource.
43
+ */
44
+ request<T>(method: HttpMethod, path: string, body?: unknown, opts?: RequestOptions): Promise<T>;
45
+ private dispatch;
46
+ private buildUrl;
47
+ private buildHeaders;
48
+ private connectionErrorMessage;
49
+ }
50
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,YAAY,EAElB,MAAM,aAAa,CAAA;AAQpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAE1D,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAoB,MAAM,YAAY,CAAA;AAc9E,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IAEvC,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAA;IACzC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;IAC7B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAA;IACnC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;IAC7B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAA;gBAEvB,MAAM,EAAE,YAAY;IAYhC;;;;;;;;;;;;;;;OAeG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAoB5E;;;;OAIG;IACG,OAAO,CAAC,CAAC,EACb,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,CAAC,CAAC;YAwBC,QAAQ;IAyGtB,OAAO,CAAC,QAAQ;IAehB,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,sBAAsB;CAO/B"}
package/dist/client.js ADDED
@@ -0,0 +1,242 @@
1
+ import { resolveConfig, validateMaxRetries } from './config.js';
2
+ import { APIConnectionError, APIError, ServiceUnavailableError, fromEnvelope } from './errors.js';
3
+ import { generateIdempotencyKey } from './idempotency.js';
4
+ import { AttachmentsResource } from './resources/attachments.js';
5
+ import { ByodResource } from './resources/byod.js';
6
+ import { ChatsResource } from './resources/chats.js';
7
+ import { LinesResource } from './resources/lines.js';
8
+ import { LookupResource } from './resources/lookup.js';
9
+ import { MessagesResource } from './resources/messages.js';
10
+ import { TrialResource } from './resources/trial.js';
11
+ import { WebhooksResource } from './resources/webhooks.js';
12
+ import { withRetries } from './retry.js';
13
+ import { SDK_VERSION } from './version.js';
14
+ const MUTATING = new Set(['POST', 'PATCH', 'PUT', 'DELETE']);
15
+ const NON_JSON_PREVIEW = 200;
16
+ // Hard cap on response body size. Server-side limits are stricter in
17
+ // practice; this is defense-in-depth against a misbehaving proxy or test
18
+ // harness streaming an unbounded body.
19
+ const MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
20
+ const USER_AGENT = `@isnap/sdk/${SDK_VERSION}`;
21
+ export class ISnapClient {
22
+ config;
23
+ attachments;
24
+ byod;
25
+ chats;
26
+ lines;
27
+ lookup;
28
+ messages;
29
+ trial;
30
+ webhooks;
31
+ constructor(config) {
32
+ this.config = resolveConfig(config);
33
+ this.attachments = new AttachmentsResource(this);
34
+ this.byod = new ByodResource(this);
35
+ this.chats = new ChatsResource(this);
36
+ this.lines = new LinesResource(this);
37
+ this.lookup = new LookupResource(this);
38
+ this.messages = new MessagesResource(this);
39
+ this.trial = new TrialResource(this);
40
+ this.webhooks = new WebhooksResource(this);
41
+ }
42
+ /**
43
+ * Direct fetch with no SDK auth, no envelope parsing, no retry — used
44
+ * internally by resource methods that must call URLs OUTSIDE the iSnap
45
+ * API surface (e.g. presigned R2 PUTs during `attachments.upload`).
46
+ *
47
+ * The leading underscore signals "internal but unavoidably public": it
48
+ * lives on the public class because resource classes cannot reach the
49
+ * private `config.fetch` otherwise. SDK consumers should use the typed
50
+ * resource methods or `request()` for everything in `/v1/*` — calling
51
+ * this directly bypasses the SDK contract.
52
+ *
53
+ * Applies the same `config.timeoutMs` ceiling as `request()` so a
54
+ * stalled R2 PUT cannot hang the upload indefinitely.
55
+ *
56
+ * @internal
57
+ */
58
+ async _externalFetch(url, init = {}) {
59
+ const controller = new AbortController();
60
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
61
+ const callerSignal = init.signal;
62
+ let abortListener;
63
+ if (callerSignal) {
64
+ if (callerSignal.aborted)
65
+ controller.abort(callerSignal.reason);
66
+ else {
67
+ abortListener = () => controller.abort(callerSignal.reason);
68
+ callerSignal.addEventListener('abort', abortListener, { once: true });
69
+ }
70
+ }
71
+ try {
72
+ return await this.config.fetch(url, { ...init, signal: controller.signal });
73
+ }
74
+ finally {
75
+ clearTimeout(timeoutId);
76
+ if (abortListener)
77
+ callerSignal?.removeEventListener('abort', abortListener);
78
+ }
79
+ }
80
+ /**
81
+ * Low-level request method. Resource classes wrap this with typed
82
+ * camelCase signatures; advanced users can call it directly for
83
+ * endpoints not yet covered by a resource.
84
+ */
85
+ async request(method, path, body, opts = {}) {
86
+ const url = this.buildUrl(path, opts.query);
87
+ const idempotencyKey = opts.idempotencyKey ??
88
+ (this.config.idempotencyKey === 'auto' && MUTATING.has(method)
89
+ ? generateIdempotencyKey()
90
+ : undefined);
91
+ const headers = this.buildHeaders(idempotencyKey, body !== undefined, opts.headers);
92
+ const init = {
93
+ method,
94
+ headers,
95
+ body: body !== undefined ? JSON.stringify(body) : undefined
96
+ };
97
+ const maxRetries = opts.maxRetries !== undefined ? validateMaxRetries(opts.maxRetries) : this.config.maxRetries;
98
+ return withRetries(() => this.dispatch(url, init, opts), {
99
+ maxRetries,
100
+ onRetry: this.config.onRetry,
101
+ signal: opts.signal
102
+ });
103
+ }
104
+ async dispatch(url, init, opts) {
105
+ const controller = new AbortController();
106
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
107
+ // Track the abort listener so we can detach it in `finally`. Without
108
+ // detachment, a long-lived caller signal (e.g. a Node process-wide
109
+ // shutdown signal) accumulates one closure per request — leaking the
110
+ // per-attempt AbortController, the caller's `opts`, and any captured
111
+ // closure state until the signal eventually fires. Detaching at the
112
+ // end of the dispatch resolves the leak immediately.
113
+ const callerSignal = opts.signal;
114
+ let abortListener;
115
+ if (callerSignal) {
116
+ if (callerSignal.aborted)
117
+ controller.abort(callerSignal.reason);
118
+ else {
119
+ abortListener = () => controller.abort(callerSignal.reason);
120
+ callerSignal.addEventListener('abort', abortListener, { once: true });
121
+ }
122
+ }
123
+ let response;
124
+ try {
125
+ response = await this.config.fetch(url, { ...init, signal: controller.signal });
126
+ }
127
+ catch (err) {
128
+ throw new APIConnectionError(this.connectionErrorMessage(err), { cause: err });
129
+ }
130
+ finally {
131
+ clearTimeout(timeoutId);
132
+ if (abortListener)
133
+ callerSignal?.removeEventListener('abort', abortListener);
134
+ }
135
+ if (response.status === 204) {
136
+ return undefined;
137
+ }
138
+ const headerTrace = response.headers.get('x-trace-id') ?? '';
139
+ const headerRequest = response.headers.get('x-request-id') ?? '';
140
+ const declaredLength = response.headers.get('content-length');
141
+ if (declaredLength) {
142
+ const n = Number.parseInt(declaredLength, 10);
143
+ if (Number.isFinite(n) && n > MAX_RESPONSE_BYTES) {
144
+ throw new APIError({
145
+ status: response.status,
146
+ code: 'response_too_large',
147
+ message: `Response body exceeds ${MAX_RESPONSE_BYTES} bytes (Content-Length: ${n})`,
148
+ traceId: headerTrace,
149
+ requestId: headerRequest
150
+ });
151
+ }
152
+ }
153
+ const text = await response.text();
154
+ const payload = (() => {
155
+ if (!text)
156
+ return null;
157
+ try {
158
+ return JSON.parse(text);
159
+ }
160
+ catch {
161
+ return null;
162
+ }
163
+ })();
164
+ if (!payload) {
165
+ const message = `Server returned non-JSON response (HTTP ${response.status})${text ? `: ${text.slice(0, NON_JSON_PREVIEW)}` : ''}`;
166
+ // 5xx without an envelope is still transient infra (Cloudflare HTML, LB
167
+ // 502 page) — surface as ServiceUnavailableError so the retry loop
168
+ // kicks in. 4xx without an envelope is a misbehaving proxy; surface
169
+ // immediately as base APIError so the caller sees the raw response.
170
+ if (response.status >= 500) {
171
+ throw new ServiceUnavailableError({
172
+ status: response.status,
173
+ code: 'invalid_response',
174
+ message,
175
+ traceId: headerTrace,
176
+ requestId: headerRequest
177
+ });
178
+ }
179
+ throw new APIError({
180
+ status: response.status,
181
+ code: 'invalid_response',
182
+ message,
183
+ traceId: headerTrace,
184
+ requestId: headerRequest
185
+ });
186
+ }
187
+ const traceId = payload.trace_id ?? headerTrace;
188
+ const requestId = payload.request_id ?? headerRequest;
189
+ if (!response.ok || payload.success === false || payload.error) {
190
+ const envelope = payload.error ?? {
191
+ status: response.status,
192
+ code: 'unknown_error',
193
+ message: `HTTP ${response.status} (no error envelope)`
194
+ };
195
+ throw fromEnvelope(envelope, traceId, requestId);
196
+ }
197
+ return payload.data;
198
+ }
199
+ buildUrl(path, query) {
200
+ const fullPath = path.startsWith('/') ? path : `/${path}`;
201
+ let url = this.config.baseUrl + fullPath;
202
+ if (query) {
203
+ const params = new URLSearchParams();
204
+ for (const [key, value] of Object.entries(query)) {
205
+ if (value === undefined || value === null)
206
+ continue;
207
+ params.append(key, String(value));
208
+ }
209
+ const qs = params.toString();
210
+ if (qs)
211
+ url += (url.includes('?') ? '&' : '?') + qs;
212
+ }
213
+ return url;
214
+ }
215
+ buildHeaders(idempotencyKey, hasBody, custom) {
216
+ const headers = new Headers();
217
+ // Defaults set first so caller-supplied headers can override them.
218
+ headers.set('User-Agent', USER_AGENT);
219
+ headers.set('Accept', 'application/json');
220
+ if (hasBody)
221
+ headers.set('Content-Type', 'application/json');
222
+ if (custom) {
223
+ const merged = new Headers(custom);
224
+ merged.forEach((value, key) => headers.set(key, value));
225
+ }
226
+ // Locked headers — set last so caller-supplied custom values cannot
227
+ // override the SDK-managed authentication or idempotency contract.
228
+ headers.set('Authorization', `Bearer ${this.config.apiKey}`);
229
+ if (idempotencyKey)
230
+ headers.set('Idempotency-Key', idempotencyKey);
231
+ return headers;
232
+ }
233
+ connectionErrorMessage(err) {
234
+ if (err instanceof DOMException && err.name === 'AbortError') {
235
+ return `Request aborted after ${this.config.timeoutMs}ms`;
236
+ }
237
+ if (err instanceof Error)
238
+ return `Network error: ${err.message}`;
239
+ return 'Network error';
240
+ }
241
+ }
242
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { RetryInfo } from './retry.js';
2
+ export interface ClientConfig {
3
+ apiKey: string;
4
+ baseUrl?: string;
5
+ maxRetries?: number;
6
+ timeoutMs?: number;
7
+ idempotencyKey?: 'auto' | 'manual';
8
+ fetch?: typeof fetch;
9
+ onRetry?: (info: RetryInfo) => void;
10
+ }
11
+ export interface ResolvedConfig {
12
+ apiKey: string;
13
+ baseUrl: string;
14
+ maxRetries: number;
15
+ timeoutMs: number;
16
+ idempotencyKey: 'auto' | 'manual';
17
+ fetch: typeof fetch;
18
+ onRetry?: (info: RetryInfo) => void;
19
+ }
20
+ export declare const DEFAULTS: {
21
+ baseUrl: string;
22
+ maxRetries: number;
23
+ timeoutMs: number;
24
+ idempotencyKey: "auto";
25
+ };
26
+ export declare function resolveConfig(config: ClientConfig): ResolvedConfig;
27
+ /**
28
+ * Validate `maxRetries` — must be a non-negative integer. NaN, negatives,
29
+ * or non-integers (e.g. `Infinity`, `2.5`) would silently break the
30
+ * `for (let attempt = 0; attempt <= config.maxRetries; attempt++)` loop in
31
+ * `withRetries` and produce confusing failure modes (zero attempts, or
32
+ * fractional comparisons). Validate at the boundary so the error surfaces
33
+ * at construction, not on the first request. Exported for reuse from
34
+ * per-call `RequestOptions.maxRetries` overrides.
35
+ */
36
+ export declare function validateMaxRetries(value: number | undefined): number;
37
+ /**
38
+ * Validate `timeoutMs` — must be a finite positive number. NaN, zero,
39
+ * negative, or `Infinity` would either abort every request immediately or
40
+ * never time out at all.
41
+ */
42
+ export declare function validateTimeoutMs(value: number | undefined): number;
43
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;IAClC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IACpB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,GAAG,QAAQ,CAAA;IACjC,KAAK,EAAE,OAAO,KAAK,CAAA;IACnB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;CACpC;AAED,eAAO,MAAM,QAAQ;;;;;CAKpB,CAAA;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,CAiBlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAQpE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAQnE"}