@onebooks/node 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +359 -2
- package/dist/app-data.d.ts +107 -0
- package/dist/app-data.d.ts.map +1 -0
- package/dist/app-data.js +48 -0
- package/dist/app-data.js.map +1 -0
- package/dist/client.d.ts +58 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +214 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +58 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +114 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +49 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +39 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.d.ts +87 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +140 -0
- package/dist/oauth.js.map +1 -0
- package/dist/session-token.d.ts +36 -0
- package/dist/session-token.d.ts.map +1 -0
- package/dist/session-token.js +41 -0
- package/dist/session-token.js.map +1 -0
- package/dist/types.d.ts +46 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/webhook.d.ts +41 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +86 -0
- package/dist/webhook.js.map +1 -0
- package/package.json +52 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OneBooks
|
|
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
CHANGED
|
@@ -1,3 +1,360 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @onebooks/node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Server-side SDK for the OneBooks API: an authenticated HTTP client with
|
|
4
|
+
sane retries, OAuth 2.0 helpers (PKCE, code exchange, refresh, RFC 8693
|
|
5
|
+
session-token exchange), ES256 App Bridge session-token verification (via
|
|
6
|
+
JWKS), and webhook signature verification.
|
|
7
|
+
|
|
8
|
+
ESM, TypeScript, Node ≥ 20. One runtime dependency: [`jose`](https://github.com/panva/jose)
|
|
9
|
+
(JWKS fetching + ES256 verification).
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @onebooks/node
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { OneBooksClient } from '@onebooks/node';
|
|
21
|
+
|
|
22
|
+
const client = new OneBooksClient({ accessToken: process.env.ONEBOOKS_ACCESS_TOKEN! });
|
|
23
|
+
|
|
24
|
+
const invoice = await client.get(`/invoices/${invoiceId}`);
|
|
25
|
+
|
|
26
|
+
// A write the API de-duplicates by Idempotency-Key — and that the SDK may
|
|
27
|
+
// therefore safely retry after a dropped connection (see "Retries").
|
|
28
|
+
const created = await client.post('/invoices', { customerId, lineItems }, {
|
|
29
|
+
idempotencyKey: crypto.randomUUID(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// List endpoints are page-based and return their own shape:
|
|
33
|
+
const { invoices, total } = await client.get<{ invoices: unknown[]; total: number }>('/invoices', {
|
|
34
|
+
query: { page: 1, limit: 50 },
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// The event log is cursor-paginated — iterate it without managing cursors:
|
|
38
|
+
for await (const event of client.events.iterate({ types: ['invoice.paid'] })) {
|
|
39
|
+
console.log(event.type, event.resourceId);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## `OneBooksClient`
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
new OneBooksClient({
|
|
47
|
+
accessToken?: string;
|
|
48
|
+
getAccessToken?: () => string | Promise<string>;
|
|
49
|
+
apiBase?: string; // default "https://api.getonebooks.com"
|
|
50
|
+
apiVersion?: string; // sent as the "OneBooks-Version" header
|
|
51
|
+
maxRetries?: number; // default 3
|
|
52
|
+
fetch?: typeof fetch; // override for tests / custom runtimes
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Provide **either** `accessToken` (a static token) **or** `getAccessToken`
|
|
57
|
+
(called before every request — use this if you refresh tokens elsewhere, a
|
|
58
|
+
token store, per-installation tokens, etc). Exactly one is required; the
|
|
59
|
+
constructor throws otherwise.
|
|
60
|
+
|
|
61
|
+
### `request(method, path, options?)`
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
client.request<T>(method: 'GET'|'POST'|'PUT'|'PATCH'|'DELETE', path: string, options?: {
|
|
65
|
+
query?: Record<string, string | number | boolean | Array<string|number> | null | undefined>;
|
|
66
|
+
body?: unknown;
|
|
67
|
+
idempotencyKey?: string;
|
|
68
|
+
headers?: Record<string, string>;
|
|
69
|
+
signal?: AbortSignal;
|
|
70
|
+
}): Promise<T>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Also exposed as `client.get(path, options?)`, `client.delete(path, options?)`
|
|
74
|
+
and `client.post/put/patch(path, body?, options?)`.
|
|
75
|
+
|
|
76
|
+
### Retries
|
|
77
|
+
|
|
78
|
+
Up to `maxRetries` (default 3) extra attempts, by this rule:
|
|
79
|
+
|
|
80
|
+
| Failure | Retried for |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `429 Too Many Requests` | **Every method.** A rate-limited request was never processed, so replaying it is always safe. Honors `Retry-After` (seconds or an HTTP date). |
|
|
83
|
+
| Network error (connection reset, timeout, DNS), `502`, `503`, `504` | **`GET`/`HEAD` only — or any method when you pass `idempotencyKey`.** Jittered exponential backoff. |
|
|
84
|
+
| Anything else (`400`, `401`, `403`, `404`, `409`, `422`, `500`, …) | Never. |
|
|
85
|
+
|
|
86
|
+
Why writes are different: after a network error or a gateway `5xx` the API
|
|
87
|
+
may already have processed the request — only the response was lost. Blindly
|
|
88
|
+
re-sending a `POST /customers` would create a second customer. With an
|
|
89
|
+
`idempotencyKey` the SDK re-sends the **same** `Idempotency-Key` header, and
|
|
90
|
+
the API returns the original result instead of acting twice.
|
|
91
|
+
|
|
92
|
+
Only these endpoints de-duplicate on `Idempotency-Key` today:
|
|
93
|
+
`POST /invoices`, `POST /invoices/:id/payments`, `POST /payments`,
|
|
94
|
+
`POST /payments/allocate` and `POST /journal`. Elsewhere the header is
|
|
95
|
+
accepted but ignored — setting a key there makes the SDK retry, yet the
|
|
96
|
+
server cannot de-duplicate, so only set one where it is honored (or where a
|
|
97
|
+
duplicate is harmless). A write without a key fails fast instead: catch the
|
|
98
|
+
error and reconcile (for example, look the record up) before trying again.
|
|
99
|
+
|
|
100
|
+
On exhausting retries, or on any non-retried failure, the promise rejects
|
|
101
|
+
with `OneBooksApiError` (a network error is re-thrown as-is):
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
class OneBooksApiError extends Error {
|
|
105
|
+
status: number;
|
|
106
|
+
code?: string; // machine-readable code; absent for class-validator array messages and OAuth endpoints
|
|
107
|
+
body: unknown; // the full parsed (or raw text) response body
|
|
108
|
+
oauthError?: OAuthErrorCode; // OAuth helpers only: the RFC 6749 `error` code, e.g. "invalid_grant"
|
|
109
|
+
oauthErrorDescription?: string; // OAuth helpers only: `error_description`, when sent
|
|
110
|
+
retryAfter?: number; // seconds, from the response's Retry-After header (every 429 has one)
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Branch on `.code`, not `.message` — see the [errors doc](../../developer-web/src/content/docs/errors.mdx).
|
|
115
|
+
Errors from the [OAuth helpers](#errors-from-the-oauth-helpers) carry
|
|
116
|
+
`.oauthError` instead.
|
|
117
|
+
|
|
118
|
+
### `paginate(path, query?, options?)`
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
for await (const event of client.paginate<OneBooksEventEnvelope>('/events', { types: 'invoice.paid' }, { cursorParam: 'after' })) { ... }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Async-iterates every item of a **cursor-paginated** endpoint — one that
|
|
125
|
+
returns `{ data, hasMore, nextCursor }`. `options.cursorParam` names the
|
|
126
|
+
query parameter the endpoint reads the cursor from (default `"cursor"`).
|
|
127
|
+
|
|
128
|
+
Today the only cursor-paginated partner endpoint is `GET /events` (cursor
|
|
129
|
+
parameter `after`) — prefer `client.events.iterate()`, which wraps exactly
|
|
130
|
+
this. Resource lists such as `GET /invoices` are **page-based** and return
|
|
131
|
+
their own shape (`{ invoices, total }`, with `page`/`limit` query
|
|
132
|
+
parameters), so `paginate()` does not apply to them; page them with
|
|
133
|
+
`client.get()`.
|
|
134
|
+
|
|
135
|
+
### `appData` — Contract 6 partner app-data API
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
await client.appData.definitions(); // -> AppDataDefinition[]
|
|
139
|
+
await client.appData.get({ resourceType: 'INVOICE', resourceId: 'inv_1' }); // -> AppDataValue[]
|
|
140
|
+
await client.appData.get({ resourceType: 'INVOICE', resourceIds: ['a', 'b'] }); // -> AppDataValue[]
|
|
141
|
+
await client.appData.set([{ resourceType: 'INVOICE', resourceId: 'inv_1', key: 'tracking_number', value: '1Z...' }]); // up to 25; value: null deletes
|
|
142
|
+
await client.appData.delete(valueId);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### `events` — Contract 6 partner events API
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
const page = await client.events.list({ types: ['invoice.paid'], limit: 50 });
|
|
149
|
+
const event = await client.events.get(eventId);
|
|
150
|
+
|
|
151
|
+
for await (const event of client.events.iterate({ after: lastSeenEventId })) {
|
|
152
|
+
// process, then persist event.id as your new "after" cursor
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## OAuth helpers (`oauth`)
|
|
157
|
+
|
|
158
|
+
Standalone functions — you need these *before* you have a client/token, so
|
|
159
|
+
they don't hang off `OneBooksClient`:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { oauth } from '@onebooks/node';
|
|
163
|
+
|
|
164
|
+
const { codeVerifier, codeChallenge } = await oauth.createPkcePair();
|
|
165
|
+
|
|
166
|
+
const authorizeUrl = oauth.buildAuthorizeUrl({
|
|
167
|
+
clientId, redirectUri, scopes: ['invoices:read'], state, codeChallenge,
|
|
168
|
+
});
|
|
169
|
+
// -> redirect the user's browser here
|
|
170
|
+
|
|
171
|
+
// ... on your callback route:
|
|
172
|
+
const tokens = await oauth.exchangeCode({
|
|
173
|
+
clientId, clientSecret /* confidential only */, code, redirectUri, codeVerifier,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const refreshed = await oauth.refresh({ clientId, clientSecret, refreshToken: tokens.refresh_token! });
|
|
177
|
+
|
|
178
|
+
await oauth.revoke({ clientId, clientSecret, token: tokens.refresh_token!, tokenTypeHint: 'refresh_token' });
|
|
179
|
+
|
|
180
|
+
const introspection = await oauth.introspect({ clientId, clientSecret, token: tokens.access_token });
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
A **public** (PKCE) client omits `clientSecret` everywhere above — the SDK
|
|
184
|
+
sends `client_id` in the request body instead of an `Authorization: Basic`
|
|
185
|
+
header, matching the [authentication doc](../../developer-web/src/content/docs/authentication.mdx).
|
|
186
|
+
|
|
187
|
+
### Errors from the OAuth helpers
|
|
188
|
+
|
|
189
|
+
`/oauth/token`, `/oauth/revoke` and `/oauth/introspect` answer errors the
|
|
190
|
+
standard OAuth way (RFC 6749 §5.2, RFC 7009, RFC 7662), not in the OneBooks
|
|
191
|
+
`{ statusCode, message, code }` shape the rest of the API uses:
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{ "error": "invalid_grant", "error_description": "refresh token reuse detected" }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Every helper rejects with `OneBooksApiError`, with the code in
|
|
198
|
+
`.oauthError` and the optional description in `.oauthErrorDescription`.
|
|
199
|
+
`.message` reads `"invalid_grant: refresh token reuse detected"` (just the
|
|
200
|
+
code when there is no description), and `.code` stays unset. Branch on
|
|
201
|
+
`.oauthError`:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { OneBooksApiError, oauth } from '@onebooks/node';
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
tokens = await oauth.refresh({ clientId, clientSecret, refreshToken });
|
|
208
|
+
} catch (err) {
|
|
209
|
+
if (err instanceof OneBooksApiError && err.oauthError === 'invalid_grant') {
|
|
210
|
+
// expired, revoked, reused, or the business uninstalled you: re-authorize
|
|
211
|
+
} else if (err instanceof OneBooksApiError && err.status === 429) {
|
|
212
|
+
// rate limited: wait err.retryAfter seconds before the next token request
|
|
213
|
+
} else {
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
- `invalid_client` (HTTP `401`) is a configuration problem — a wrong or
|
|
220
|
+
missing secret, or an app that is suspended or deactivated. Fix it rather
|
|
221
|
+
than retrying: repeated failures lock the caller out for a minute.
|
|
222
|
+
- The helpers **don't retry**, a `429` included. Its body is
|
|
223
|
+
`{ "error": "temporarily_unavailable", … }`, and `.retryAfter` holds the
|
|
224
|
+
`Retry-After` seconds to wait.
|
|
225
|
+
- A `5xx` answers `server_error`. A proxy's non-JSON error page still
|
|
226
|
+
rejects with `OneBooksApiError`: `.message` is the HTTP status text and
|
|
227
|
+
`.body` the raw text.
|
|
228
|
+
- The helpers also read the code from `message`, where these endpoints put
|
|
229
|
+
it before 2026-09-23 (`{ "statusCode": 400, "message": "invalid_grant: expired", … }`),
|
|
230
|
+
so `.oauthError` is filled against an older server too.
|
|
231
|
+
|
|
232
|
+
### `exchangeSessionToken` — Contract 3 (embedded apps)
|
|
233
|
+
|
|
234
|
+
For an app embedded via App Bridge: trade the short-lived session token
|
|
235
|
+
(`app.sessionToken()` on the frontend) for a normal access/refresh token pair
|
|
236
|
+
scoped to that installation. **Confidential clients only.**
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
const tokens = await oauth.exchangeSessionToken({
|
|
240
|
+
clientId: process.env.ONEBOOKS_CLIENT_ID!,
|
|
241
|
+
clientSecret: process.env.ONEBOOKS_CLIENT_SECRET!,
|
|
242
|
+
sessionToken, // from your frontend, e.g. a header your embedded page sent you
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Exchange once, cache the pair; verify — don't exchange — on every
|
|
247
|
+
request.** Each session token can be exchanged exactly **once**: the token
|
|
248
|
+
endpoint records its `jti`, and a second exchange of the same token fails
|
|
249
|
+
with `invalid_grant`. App Bridge deliberately reuses one session token for
|
|
250
|
+
up to ~50 seconds across all of a page's `app.fetch()` calls, so a backend
|
|
251
|
+
that exchanges on every request breaks on its second request. The pattern:
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
const claims = await verifySessionToken(sessionToken, { clientId }); // every request
|
|
255
|
+
const key = `${claims.bid}:${claims.sub}`;
|
|
256
|
+
let pair = await store.get(key);
|
|
257
|
+
if (!pair) {
|
|
258
|
+
pair = await oauth.exchangeSessionToken({ clientId, clientSecret, sessionToken }); // once
|
|
259
|
+
await store.set(key, pair);
|
|
260
|
+
} else if (expiresSoon(pair)) {
|
|
261
|
+
pair = await oauth.refresh({ clientId, clientSecret, refreshToken: pair.refresh_token! });
|
|
262
|
+
await store.set(key, pair);
|
|
263
|
+
}
|
|
264
|
+
// If the refresh fails (tokens revoked — e.g. uninstall, suspension) or the
|
|
265
|
+
// API answers 401, drop the stored pair and exchange a FRESH session token.
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Run that exchange/refresh **one at a time per key**: a page's parallel
|
|
269
|
+
requests carry the same session token, so when two of them find no stored
|
|
270
|
+
pair and both exchange it, the second gets `invalid_grant`. Let concurrent
|
|
271
|
+
requests wait for the exchange or refresh already in flight.
|
|
272
|
+
|
|
273
|
+
The starter template (`developer-sdk/templates/embedded-node/lib/session-tokens.mjs`,
|
|
274
|
+
wired up in `lib/onebooks.mjs`) implements exactly this, including the
|
|
275
|
+
fallback and the one-at-a-time rule.
|
|
276
|
+
|
|
277
|
+
## `verifySessionToken(token, options)`
|
|
278
|
+
|
|
279
|
+
Verifies an App Bridge session token server-side (ES256, JWKS from
|
|
280
|
+
`GET /.well-known/jwks.json`) — use this in **every** backend route your
|
|
281
|
+
embedded app calls, to authenticate the (business, user) making the request
|
|
282
|
+
(verification is local and repeatable; only the exchange is single-use):
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import { verifySessionToken } from '@onebooks/node';
|
|
286
|
+
|
|
287
|
+
const claims = await verifySessionToken(token, { clientId: process.env.ONEBOOKS_CLIENT_ID! });
|
|
288
|
+
// claims: { iss, aud, sub (userId), bid (businessId), role, locale, jti, iat, nbf, exp, typ }
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Checks performed: ES256 signature against the live JWKS (fetched once,
|
|
292
|
+
cached, and refreshed automatically by `jose` on a `kid` it hasn't seen),
|
|
293
|
+
`aud === clientId`, `iss === apiBase`'s origin, `exp`/`nbf` within
|
|
294
|
+
`clockToleranceSec` (default 30s), and the `typ` claim equals
|
|
295
|
+
`"onebooks.app_session"` — a token minted for some other purpose but
|
|
296
|
+
sharing the same signing keys must not be accepted here. Throws (a `jose`
|
|
297
|
+
error, e.g. `JWTExpired`) on any failure.
|
|
298
|
+
|
|
299
|
+
Options: `{ clientId, apiBase? (default the OneBooks API), jwksUrl? (default
|
|
300
|
+
"<apiBase origin>/.well-known/jwks.json"), clockToleranceSec? (default 30) }`.
|
|
301
|
+
|
|
302
|
+
## `verifyWebhook(options)`
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
import { verifyWebhook } from '@onebooks/node';
|
|
306
|
+
import { createServer } from 'node:http';
|
|
307
|
+
|
|
308
|
+
const server = createServer(async (req, res) => {
|
|
309
|
+
const chunks: Buffer[] = [];
|
|
310
|
+
for await (const chunk of req) chunks.push(chunk as Buffer);
|
|
311
|
+
const rawBody = Buffer.concat(chunks); // MUST verify against the raw bytes, not a parsed/re-serialized body
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const event = verifyWebhook({
|
|
315
|
+
rawBody,
|
|
316
|
+
signatureHeader: req.headers['x-webhook-signature'] as string,
|
|
317
|
+
secrets: [process.env.ONEBOOKS_WEBHOOK_SECRET!], // pass [new, old] while rotating
|
|
318
|
+
});
|
|
319
|
+
// event: { id, eventId, type, apiVersion, businessId, created, data }
|
|
320
|
+
res.writeHead(200).end();
|
|
321
|
+
void handle(event);
|
|
322
|
+
} catch {
|
|
323
|
+
res.writeHead(400).end();
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Verifies `X-Webhook-Signature: t=<unix>,v1=<hex>[,v1=<hex>]` — HMAC-SHA256 of
|
|
329
|
+
`` `${t}.${rawBody}` `` — with a constant-time comparison, and rejects a
|
|
330
|
+
timestamp older than `toleranceSec` (default 300) to blunt replay of a
|
|
331
|
+
captured delivery. **Accepts the delivery if ANY provided secret matches ANY
|
|
332
|
+
`v1` value**, so passing both your current and previous secret during a
|
|
333
|
+
rotation window (`secrets: [current, previous]`) verifies deliveries signed
|
|
334
|
+
with either. Throws on any failure; on success, returns the parsed event —
|
|
335
|
+
never a boolean, so a caller can't accidentally ignore a failed check.
|
|
336
|
+
|
|
337
|
+
`parseWebhookEvent(rawBody)` parses the envelope **without** verifying
|
|
338
|
+
anything — only use it on a body you already ran through `verifyWebhook()`.
|
|
339
|
+
|
|
340
|
+
## Development
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
npm install
|
|
344
|
+
npm run build # tsc -> dist/
|
|
345
|
+
npm test # node:test via tsx — no network, no real timers beyond real retry backoff
|
|
346
|
+
npm run typecheck
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Tests cover: webhook verification (valid / tampered / stale timestamp /
|
|
350
|
+
secret rotation with two `v1` values), session-token verification against a
|
|
351
|
+
locally generated ES256 key and a mocked JWKS fetch (aud mismatch, expired,
|
|
352
|
+
wrong `typ`, unknown signing key, clock tolerance) — **no real network
|
|
353
|
+
call** — the client's retry rule against a fake `fetch` (including that
|
|
354
|
+
a write without an idempotency key is never replayed), and the OAuth
|
|
355
|
+
helpers' error parsing for both the RFC 6749 body and an older server's
|
|
356
|
+
`message` body, `Retry-After` on a `429`, and a non-JSON gateway page.
|
|
357
|
+
|
|
358
|
+
## License
|
|
359
|
+
|
|
360
|
+
MIT
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { QueryParams } from './types.js';
|
|
2
|
+
interface RequestClient {
|
|
3
|
+
get<T>(path: string, options?: {
|
|
4
|
+
query?: QueryParams;
|
|
5
|
+
}): Promise<T>;
|
|
6
|
+
put<T>(path: string, body?: unknown): Promise<T>;
|
|
7
|
+
delete<T>(path: string): Promise<T>;
|
|
8
|
+
}
|
|
9
|
+
/** `en` is required; other locales are filled in as the app's listing supports them. */
|
|
10
|
+
export interface LocalizedText {
|
|
11
|
+
en: string;
|
|
12
|
+
[locale: string]: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Matches Prisma's `AppDataResourceType` enum exactly (`schema.prisma`). */
|
|
15
|
+
export type AppDataResourceType = 'BUSINESS' | 'CUSTOMER' | 'SUPPLIER' | 'ITEM' | 'INVOICE' | 'QUOTE' | 'SALES_RETURN' | 'PURCHASE' | 'PURCHASE_RETURN' | 'PAYMENT' | 'SUPPLIER_PAYMENT' | 'EXPENSE';
|
|
16
|
+
/** Matches Prisma's `AppDataType` enum exactly (`schema.prisma`). */
|
|
17
|
+
export type AppDataFieldType = 'TEXT' | 'MULTILINE_TEXT' | 'INTEGER' | 'DECIMAL' | 'BOOLEAN' | 'DATE' | 'DATETIME' | 'URL' | 'CHOICE' | 'JSON';
|
|
18
|
+
/** Matches Prisma's `AppDataUpdatedVia` enum exactly. */
|
|
19
|
+
export type AppDataUpdatedVia = 'APP' | 'MERCHANT';
|
|
20
|
+
export interface AppDataValidationRules {
|
|
21
|
+
/** TEXT / MULTILINE_TEXT only. */
|
|
22
|
+
maxLength?: number;
|
|
23
|
+
/** INTEGER / DECIMAL only. */
|
|
24
|
+
min?: number;
|
|
25
|
+
/** INTEGER / DECIMAL only. */
|
|
26
|
+
max?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A field your app has declared via the developer console (Contract 6:
|
|
30
|
+
* `GET /app-data/definitions`).
|
|
31
|
+
*
|
|
32
|
+
* Server-side validation per `type` (`app-data-values.ts` — informational;
|
|
33
|
+
* this SDK does not re-validate client-side, the server is authoritative):
|
|
34
|
+
* `TEXT` — string, no line breaks, ≤ `validation.maxLength` (default 255).
|
|
35
|
+
* `MULTILINE_TEXT` — string, ≤ `validation.maxLength` (default 5000).
|
|
36
|
+
* `INTEGER` — a safe integer, within `validation.min`/`max`.
|
|
37
|
+
* `DECIMAL` — a finite number with ≤ 6 decimal places, within `validation.min`/`max`.
|
|
38
|
+
* `BOOLEAN` — `true`/`false`.
|
|
39
|
+
* `DATE` — `"YYYY-MM-DD"`, a real calendar date.
|
|
40
|
+
* `DATETIME` — an ISO-8601 date-time string; stored normalized to `Date#toISOString()`.
|
|
41
|
+
* `URL` — an absolute **https** URL, ≤ 2048 characters.
|
|
42
|
+
* `CHOICE` — a string that is one of this definition's `choices`.
|
|
43
|
+
* `JSON` — an object or array that serializes to ≤ 16 KB.
|
|
44
|
+
* In every case, `value: null` deletes the stored value regardless of type.
|
|
45
|
+
*/
|
|
46
|
+
export interface AppDataDefinition {
|
|
47
|
+
id: string;
|
|
48
|
+
resourceType: AppDataResourceType;
|
|
49
|
+
key: string;
|
|
50
|
+
type: AppDataFieldType;
|
|
51
|
+
name: LocalizedText;
|
|
52
|
+
description: LocalizedText | null;
|
|
53
|
+
choices: string[];
|
|
54
|
+
validation: AppDataValidationRules | null;
|
|
55
|
+
merchantVisible: boolean;
|
|
56
|
+
merchantEditable: boolean;
|
|
57
|
+
position: number;
|
|
58
|
+
createdAt: string;
|
|
59
|
+
updatedAt: string;
|
|
60
|
+
}
|
|
61
|
+
export interface AppDataValue {
|
|
62
|
+
/** Null when this entry is the echo of a delete (a `set()` call with `value: null`). */
|
|
63
|
+
id: string | null;
|
|
64
|
+
resourceType: AppDataResourceType;
|
|
65
|
+
resourceId: string;
|
|
66
|
+
key: string;
|
|
67
|
+
value: unknown;
|
|
68
|
+
updatedVia: AppDataUpdatedVia;
|
|
69
|
+
updatedAt: string;
|
|
70
|
+
}
|
|
71
|
+
export interface AppDataGetParams {
|
|
72
|
+
resourceType: AppDataResourceType;
|
|
73
|
+
resourceId?: string;
|
|
74
|
+
/** At most 50. */
|
|
75
|
+
resourceIds?: string[];
|
|
76
|
+
}
|
|
77
|
+
export interface AppDataSetInput {
|
|
78
|
+
resourceType: AppDataResourceType;
|
|
79
|
+
/** At most 255 characters. */
|
|
80
|
+
resourceId: string;
|
|
81
|
+
/** Must match an existing definition key for this app + resourceType (`/^[a-z][a-z0-9_]{0,63}$/`). */
|
|
82
|
+
key: string;
|
|
83
|
+
/** The typed value (shape depends on the field's type — see {@link AppDataDefinition}). `null` deletes the value. */
|
|
84
|
+
value: unknown;
|
|
85
|
+
}
|
|
86
|
+
/** Partner app-data API (Contract 6): `client.appData.*`. */
|
|
87
|
+
export declare class AppDataResource {
|
|
88
|
+
private readonly client;
|
|
89
|
+
constructor(client: RequestClient);
|
|
90
|
+
/** `GET /app-data/definitions` — every field your app has declared. */
|
|
91
|
+
definitions(): Promise<AppDataDefinition[]>;
|
|
92
|
+
/**
|
|
93
|
+
* `GET /app-data?resourceType=…&resourceId=…` (or `&resourceIds=a,b`, at
|
|
94
|
+
* most 50). Provide exactly one of `resourceId` / `resourceIds`.
|
|
95
|
+
*/
|
|
96
|
+
get(params: AppDataGetParams): Promise<AppDataValue[]>;
|
|
97
|
+
/**
|
|
98
|
+
* `PUT /app-data { values }` — 1 to 25 per call (the server rejects an
|
|
99
|
+
* empty array; this SDK short-circuits to `[]` instead of making that
|
|
100
|
+
* call); `value: null` deletes that value.
|
|
101
|
+
*/
|
|
102
|
+
set(values: AppDataSetInput[]): Promise<AppDataValue[]>;
|
|
103
|
+
/** `DELETE /app-data/:id`. */
|
|
104
|
+
delete(id: string): Promise<void>;
|
|
105
|
+
}
|
|
106
|
+
export {};
|
|
107
|
+
//# sourceMappingURL=app-data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-data.d.ts","sourceRoot":"","sources":["../src/app-data.ts"],"names":[],"mappings":"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"}
|
package/dist/app-data.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const MAX_SET_BATCH = 25;
|
|
2
|
+
/** Partner app-data API (Contract 6): `client.appData.*`. */
|
|
3
|
+
export class AppDataResource {
|
|
4
|
+
client;
|
|
5
|
+
constructor(client) {
|
|
6
|
+
this.client = client;
|
|
7
|
+
}
|
|
8
|
+
/** `GET /app-data/definitions` — every field your app has declared. */
|
|
9
|
+
async definitions() {
|
|
10
|
+
const res = await this.client.get('/app-data/definitions');
|
|
11
|
+
return res.data;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* `GET /app-data?resourceType=…&resourceId=…` (or `&resourceIds=a,b`, at
|
|
15
|
+
* most 50). Provide exactly one of `resourceId` / `resourceIds`.
|
|
16
|
+
*/
|
|
17
|
+
async get(params) {
|
|
18
|
+
if (!params.resourceId && !params.resourceIds) {
|
|
19
|
+
throw new Error('appData.get(): provide either "resourceId" or "resourceIds".');
|
|
20
|
+
}
|
|
21
|
+
const query = { resourceType: params.resourceType };
|
|
22
|
+
if (params.resourceId)
|
|
23
|
+
query.resourceId = params.resourceId;
|
|
24
|
+
if (params.resourceIds)
|
|
25
|
+
query.resourceIds = params.resourceIds.join(',');
|
|
26
|
+
const res = await this.client.get('/app-data', { query });
|
|
27
|
+
return res.data;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `PUT /app-data { values }` — 1 to 25 per call (the server rejects an
|
|
31
|
+
* empty array; this SDK short-circuits to `[]` instead of making that
|
|
32
|
+
* call); `value: null` deletes that value.
|
|
33
|
+
*/
|
|
34
|
+
async set(values) {
|
|
35
|
+
if (values.length === 0)
|
|
36
|
+
return [];
|
|
37
|
+
if (values.length > MAX_SET_BATCH) {
|
|
38
|
+
throw new RangeError(`appData.set(): at most ${MAX_SET_BATCH} values per call (got ${values.length}).`);
|
|
39
|
+
}
|
|
40
|
+
const res = await this.client.put('/app-data', { values });
|
|
41
|
+
return res.data;
|
|
42
|
+
}
|
|
43
|
+
/** `DELETE /app-data/:id`. */
|
|
44
|
+
async delete(id) {
|
|
45
|
+
await this.client.delete(`/app-data/${encodeURIComponent(id)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=app-data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-data.js","sourceRoot":"","sources":["../src/app-data.ts"],"names":[],"mappings":"AAuHA,MAAM,aAAa,GAAG,EAAE,CAAC;AAEzB,6DAA6D;AAC7D,MAAM,OAAO,eAAe;IACG;IAA7B,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAEtD,uEAAuE;IACvE,KAAK,CAAC,WAAW;QACf,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAgC,uBAAuB,CAAC,CAAC;QAC1F,OAAO,GAAG,CAAC,IAAI,CAAC;IAClB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAC,MAAwB;QAChC,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;QAClF,CAAC;QACD,MAAM,KAAK,GAAgB,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC;QACjE,IAAI,MAAM,CAAC,UAAU;YAAE,KAAK,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QAC5D,IAAI,MAAM,CAAC,WAAW;YAAE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAA2B,WAAW,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACpF,OAAO,GAAG,CAAC,IAAI,CAAC;IAClB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,GAAG,CAAC,MAAyB;QACjC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACnC,IAAI,MAAM,CAAC,MAAM,GAAG,aAAa,EAAE,CAAC;YAClC,MAAM,IAAI,UAAU,CAAC,0BAA0B,aAAa,yBAAyB,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;QAC1G,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAA2B,WAAW,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QACrF,OAAO,GAAG,CAAC,IAAI,CAAC;IAClB,CAAC;IAED,8BAA8B;IAC9B,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAO,aAAa,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACxE,CAAC;CACF"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { AppDataResource } from './app-data.js';
|
|
2
|
+
import { EventsResource } from './events.js';
|
|
3
|
+
import type { HttpMethod, OneBooksClientOptions, QueryParams, RequestOptions } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* The retry rule, in one place:
|
|
6
|
+
* - `429` is retried for EVERY method — a rate-limited request was never
|
|
7
|
+
* processed, so replaying it can't duplicate anything.
|
|
8
|
+
* - A network error or `502`/`503`/`504` is retried only for `GET`/`HEAD`, or
|
|
9
|
+
* for any other method when the call set an `idempotencyKey` — otherwise a
|
|
10
|
+
* POST that the API processed before the connection dropped would be sent
|
|
11
|
+
* twice (a duplicate customer, expense, quote, …).
|
|
12
|
+
* - Nothing else is ever retried.
|
|
13
|
+
*/
|
|
14
|
+
export declare function canReplayAfterAmbiguousFailure(method: HttpMethod, idempotencyKey: string | undefined): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Server-side OneBooks API client: authenticated `request()` with retries,
|
|
17
|
+
* plus `appData`/`events` resource helpers. OAuth flow helpers (PKCE, code
|
|
18
|
+
* exchange, session-token exchange, …) are standalone — see `oauth.ts` — since
|
|
19
|
+
* you need them before a client (and its token) exists.
|
|
20
|
+
*/
|
|
21
|
+
export declare class OneBooksClient {
|
|
22
|
+
readonly apiBase: string;
|
|
23
|
+
readonly apiVersion?: string;
|
|
24
|
+
readonly appData: AppDataResource;
|
|
25
|
+
readonly events: EventsResource;
|
|
26
|
+
private readonly maxRetries;
|
|
27
|
+
private readonly fetchImpl;
|
|
28
|
+
private readonly staticToken?;
|
|
29
|
+
private readonly getAccessTokenFn?;
|
|
30
|
+
constructor(options: OneBooksClientOptions);
|
|
31
|
+
private resolveAccessToken;
|
|
32
|
+
private buildUrl;
|
|
33
|
+
/**
|
|
34
|
+
* Issues one authenticated API request. Retries (up to `maxRetries`) a
|
|
35
|
+
* `429` for any method, honoring `Retry-After`; retries a network error or
|
|
36
|
+
* `502`/`503`/`504` (jittered backoff) only for `GET`/`HEAD` or when
|
|
37
|
+
* `options.idempotencyKey` is set — see `canReplayAfterAmbiguousFailure`.
|
|
38
|
+
* Never retries any other status. Resolves the parsed JSON body (or
|
|
39
|
+
* `undefined` for a `204`); throws `OneBooksApiError` on a non-retried
|
|
40
|
+
* failure (or rethrows the network error).
|
|
41
|
+
*/
|
|
42
|
+
request<T = unknown>(method: HttpMethod, path: string, options?: RequestOptions): Promise<T>;
|
|
43
|
+
get<T = unknown>(path: string, options?: Omit<RequestOptions, 'body'>): Promise<T>;
|
|
44
|
+
post<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, 'body'>): Promise<T>;
|
|
45
|
+
put<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, 'body'>): Promise<T>;
|
|
46
|
+
patch<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, 'body'>): Promise<T>;
|
|
47
|
+
delete<T = unknown>(path: string, options?: Omit<RequestOptions, 'body'>): Promise<T>;
|
|
48
|
+
/**
|
|
49
|
+
* Async-iterates every item of a `{ data, hasMore, nextCursor }` endpoint,
|
|
50
|
+
* paging automatically. `cursorParam` names the query parameter the
|
|
51
|
+
* endpoint reads the cursor from (default `"cursor"`; the events endpoint
|
|
52
|
+
* uses `"after"` — see `events.iterate()`, built on this).
|
|
53
|
+
*/
|
|
54
|
+
paginate<T = unknown>(path: string, query?: QueryParams, paginateOptions?: {
|
|
55
|
+
cursorParam?: string;
|
|
56
|
+
}): AsyncGenerator<T, void, undefined>;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EACV,UAAU,EACV,qBAAqB,EAErB,WAAW,EACX,cAAc,EACf,MAAM,YAAY,CAAC;AAWpB;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE9G;AA2CD;;;;;GAKG;AACH,qBAAa,cAAc;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAiC;gBAEvD,OAAO,EAAE,qBAAqB;YAe5B,kBAAkB;IAMhC,OAAO,CAAC,QAAQ;IAiBhB;;;;;;;;OAQG;IACG,OAAO,CAAC,CAAC,GAAG,OAAO,EACvB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,CAAC,CAAC;IAkDb,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIlF,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAInG,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIlG,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIpG,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIrF;;;;;OAKG;IACI,QAAQ,CAAC,CAAC,GAAG,OAAO,EACzB,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,WAAgB,EACvB,eAAe,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAO,GAC7C,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC;CAYtC"}
|