@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.
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/dist/client.d.ts +50 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +242 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +43 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +55 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +53 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +100 -0
- package/dist/errors.js.map +1 -0
- package/dist/generated/openapi.d.ts +7963 -0
- package/dist/idempotency.d.ts +2 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +4 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/sleep.d.ts +10 -0
- package/dist/internal/sleep.d.ts.map +1 -0
- package/dist/internal/sleep.js +37 -0
- package/dist/internal/sleep.js.map +1 -0
- package/dist/pagination.d.ts +10 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +16 -0
- package/dist/pagination.js.map +1 -0
- package/dist/resources/attachments.d.ts +45 -0
- package/dist/resources/attachments.d.ts.map +1 -0
- package/dist/resources/attachments.js +120 -0
- package/dist/resources/attachments.js.map +1 -0
- package/dist/resources/base.d.ts +6 -0
- package/dist/resources/base.d.ts.map +1 -0
- package/dist/resources/base.js +7 -0
- package/dist/resources/base.js.map +1 -0
- package/dist/resources/byod.d.ts +15 -0
- package/dist/resources/byod.d.ts.map +1 -0
- package/dist/resources/byod.js +13 -0
- package/dist/resources/byod.js.map +1 -0
- package/dist/resources/chats.d.ts +29 -0
- package/dist/resources/chats.d.ts.map +1 -0
- package/dist/resources/chats.js +28 -0
- package/dist/resources/chats.js.map +1 -0
- package/dist/resources/lines.d.ts +70 -0
- package/dist/resources/lines.d.ts.map +1 -0
- package/dist/resources/lines.js +86 -0
- package/dist/resources/lines.js.map +1 -0
- package/dist/resources/lookup.d.ts +15 -0
- package/dist/resources/lookup.d.ts.map +1 -0
- package/dist/resources/lookup.js +13 -0
- package/dist/resources/lookup.js.map +1 -0
- package/dist/resources/messages.d.ts +42 -0
- package/dist/resources/messages.d.ts.map +1 -0
- package/dist/resources/messages.js +41 -0
- package/dist/resources/messages.js.map +1 -0
- package/dist/resources/trial.d.ts +28 -0
- package/dist/resources/trial.d.ts.map +1 -0
- package/dist/resources/trial.js +31 -0
- package/dist/resources/trial.js.map +1 -0
- package/dist/resources/webhooks.d.ts +58 -0
- package/dist/resources/webhooks.d.ts.map +1 -0
- package/dist/resources/webhooks.js +65 -0
- package/dist/resources/webhooks.js.map +1 -0
- package/dist/retry.d.ts +14 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +47 -0
- package/dist/retry.js.map +1 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/dist/webhooks/parse.d.ts +15 -0
- package/dist/webhooks/parse.d.ts.map +1 -0
- package/dist/webhooks/parse.js +47 -0
- package/dist/webhooks/parse.js.map +1 -0
- package/dist/webhooks/types.d.ts +80 -0
- package/dist/webhooks/types.d.ts.map +1 -0
- package/dist/webhooks/types.js +17 -0
- package/dist/webhooks/types.js.map +1 -0
- package/dist/webhooks/verify.d.ts +41 -0
- package/dist/webhooks/verify.d.ts.map +1 -0
- package/dist/webhooks/verify.js +110 -0
- package/dist/webhooks/verify.js.map +1 -0
- 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
|
package/dist/client.d.ts
ADDED
|
@@ -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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"}
|
package/dist/config.d.ts
ADDED
|
@@ -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"}
|