@usequeek/app-sdk 0.6.0 → 0.6.2
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/README.md +97 -78
- package/dist/app-auth.d.ts +14 -15
- package/dist/app-auth.d.ts.map +1 -1
- package/dist/app-auth.js +19 -21
- package/dist/app-auth.js.map +1 -1
- package/dist/auth-fetch.d.ts +22 -23
- package/dist/auth-fetch.d.ts.map +1 -1
- package/dist/auth-fetch.js +22 -22
- package/dist/auth-fetch.js.map +1 -1
- package/dist/background.d.ts +1 -1
- package/dist/browser.d.ts +25 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +27 -0
- package/dist/browser.js.map +1 -0
- package/dist/client.d.ts +8 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -2
- package/dist/client.js.map +1 -1
- package/dist/collected.d.ts +13 -14
- package/dist/collected.d.ts.map +1 -1
- package/dist/collected.js +4 -4
- package/dist/collected.js.map +1 -1
- package/dist/crypto.js +3 -3
- package/dist/crypto.js.map +1 -1
- package/dist/delivery.d.ts +4 -8
- package/dist/delivery.d.ts.map +1 -1
- package/dist/delivery.js +4 -8
- package/dist/delivery.js.map +1 -1
- package/dist/frame.d.ts +11 -11
- package/dist/frame.d.ts.map +1 -1
- package/dist/frame.js +11 -11
- package/dist/frame.js.map +1 -1
- package/dist/handoff.d.ts +35 -38
- package/dist/handoff.d.ts.map +1 -1
- package/dist/handoff.js +9 -9
- package/dist/hono.d.ts +3 -3
- package/dist/hono.js +3 -3
- package/dist/install-handlers.d.ts +6 -7
- package/dist/install-handlers.d.ts.map +1 -1
- package/dist/install-handlers.js +11 -11
- package/dist/install-handlers.js.map +1 -1
- package/dist/logger.js +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/merchant-schema.d.ts +14882 -6288
- package/dist/merchant-schema.d.ts.map +1 -1
- package/dist/proxy.d.ts +20 -22
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +19 -20
- package/dist/proxy.js.map +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/resync.d.ts +4 -4
- package/dist/resync.d.ts.map +1 -1
- package/dist/resync.js +4 -4
- package/dist/resync.js.map +1 -1
- package/dist/scopes.d.ts +22 -26
- package/dist/scopes.d.ts.map +1 -1
- package/dist/scopes.js +19 -23
- package/dist/scopes.js.map +1 -1
- package/dist/server.d.ts +3 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/session.d.ts +16 -55
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +16 -86
- package/dist/session.js.map +1 -1
- package/dist/signatures.d.ts +5 -4
- package/dist/signatures.d.ts.map +1 -1
- package/dist/signatures.js +7 -6
- package/dist/signatures.js.map +1 -1
- package/dist/store.d.ts +20 -25
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +16 -19
- package/dist/store.js.map +1 -1
- package/dist/theme.d.ts +10 -6
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +10 -6
- package/dist/theme.js.map +1 -1
- package/dist/tokens.d.ts +9 -9
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +9 -10
- package/dist/tokens.js.map +1 -1
- package/dist/webhooks.d.ts +2 -3
- package/dist/webhooks.d.ts.map +1 -1
- package/dist/webhooks.js +2 -3
- package/dist/webhooks.js.map +1 -1
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
# @usequeek/app-sdk
|
|
2
2
|
|
|
3
|
-
> In development — not yet released. APIs may change before 1.0.
|
|
4
|
-
|
|
5
3
|
The SDK for building a Queek app on the public Merchant API and signed webhooks. Framework-agnostic Web-standard handlers for the install handoff and topic webhooks (`Request` in, `Response` out — use them from Next.js route handlers, Express, or any runtime), a typed Merchant API client (types generated from the live contract), GitHub-style app credentials (one asymmetric key per app, short-lived per-installation tokens minted on demand), resync recovery, and an encrypted installation store (SQLite for local/test, Postgres in production). Optional Hono wrappers live under `@usequeek/app-sdk/hono`.
|
|
6
4
|
|
|
5
|
+
Full guide: <https://docs.usequeek.com/docs/apps/sdk>.
|
|
6
|
+
|
|
7
|
+
> Pre-1.0: the API can still change; pin the exact version (`npm i @usequeek/app-sdk@<version>`).
|
|
8
|
+
|
|
7
9
|
```sh
|
|
8
10
|
npm i @usequeek/app-sdk pg
|
|
9
11
|
```
|
|
10
12
|
|
|
11
13
|
Requires Node `>=22.14`. `pg` is a regular dependency (the production store). `hono` is an optional peer — install it (`npm i hono`) only if you use the Hono wrappers.
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
## Quick start for AI agents
|
|
16
|
+
|
|
17
|
+
Register the app on the Queek Developer page, then use the CLI:
|
|
18
|
+
|
|
19
|
+
1. Scaffold: `npm create @usequeek/app my-app` (same as `queek app init`).
|
|
20
|
+
2. Develop: `queek app dev` — tunnel + dev-store install; mints credentials into `.queek/.env.local` on first run.
|
|
21
|
+
3. Deploy: `queek app deploy` — pushes `queek.app.toml` as a new version (secret shown once, also written to `.queek/.env.local`). In CI, authenticate with a per-app App Automation Token from the Developer page (`QUEEK_APP_AUTOMATION_TOKEN`).
|
|
22
|
+
4. Import from the entry table below: `@usequeek/app-sdk` for server code, `@usequeek/app-sdk/browser` (or `/react`) for browser code — never the main entry in a browser bundle.
|
|
23
|
+
5. Serve the install/uninstall/settings handoff (`handleInstallRequest`) and topic webhooks (`handleWebhookRequest`), then call the Merchant API via `createInstallationClient`.
|
|
24
|
+
6. Capabilities: the live list of scopes, webhook topics and other platform capabilities is served at `https://api.usequeek.com/docs/capabilities.json`; bridge capabilities are in the `queek-bridge` skill of [queek-ai-toolkit](https://github.com/usequeek/queek-ai-toolkit) (`references/bridge-spec.md`). Confirm scope and topic names with `queek app deploy`'s validator rather than a baked-in list.
|
|
25
|
+
|
|
26
|
+
## Which entry do I import?
|
|
27
|
+
|
|
28
|
+
| Import | For | Pulls in |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `@usequeek/app-sdk` (main) | Server / universal code: handlers, client, stores, verifiers | `node:crypto`, `pg` — NOT browser-bundlable |
|
|
31
|
+
| `@usequeek/app-sdk/server` | Session-token verifier (`verifySessionToken`) | Server-only (`jose`) — never import in a browser bundle |
|
|
32
|
+
| `@usequeek/app-sdk/hono` | Thin Hono wrappers (`hono` optional peer) | Server-only |
|
|
33
|
+
| `@usequeek/app-sdk/react` | React apps (`<QueekProvider>`, `useQueek()`; `react` optional peer) | Browser-safe (built on the same bridge modules as `/browser`) |
|
|
34
|
+
| `@usequeek/app-sdk/browser` | Plain-browser code: `installAuthFetch` + bridge/theme helpers, no framework | Browser-safe ONLY |
|
|
35
|
+
|
|
36
|
+
Browser rule of thumb: if the code ships to the browser and does not need React, import it from `@usequeek/app-sdk/browser`. `installAuthFetch` is a browser-safe MODULE, but it is not browser-safe FROM THE MAIN ENTRY — the main barrel re-exports server modules (`app-auth` → `node:crypto`, `store` → `pg`), so a bundler resolving `installAuthFetch` from `@usequeek/app-sdk` still parses those Node-only files and fails (Vite/Rollup builds break).
|
|
14
37
|
|
|
15
38
|
## Example (any framework)
|
|
16
39
|
|
|
@@ -124,17 +147,15 @@ fastify.post("/api/webhooks", async (req, reply) => {
|
|
|
124
147
|
|
|
125
148
|
### Why the raw body matters
|
|
126
149
|
|
|
127
|
-
Signature verification covers the exact bytes Queek sent (`{id}.{timestamp}.{body}
|
|
150
|
+
Signature verification covers the exact bytes Queek sent (`{id}.{timestamp}.{body}`, as defined by the [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks/blob/main/spec/standard-webhooks.md) spec), not the parsed JSON value — so the SDK takes the untouched body bytes at every layer. A parsed-then-restringified body has different bytes (spacing, key order) and will NOT verify. A string `rawBody` is used verbatim, so only pass a string when it is already the exact UTF-8 text — otherwise prefer `Buffer`/`Uint8Array` straight from `express.raw`, Fastify's `parseAs: "buffer"`, or `await request.arrayBuffer()`. In practice: Express needs `express.raw(...)` (never `express.json()`) on webhook routes, Fastify needs `addContentTypeParser` with `parseAs: "buffer"`, and in Next.js you read `await request.arrayBuffer()` (the SDK's layer 2 already does) rather than `await request.json()`.
|
|
128
151
|
|
|
129
152
|
### Body-size posture
|
|
130
153
|
|
|
131
|
-
The SDK itself imposes no maximum body size (`arrayBuffer` / `TextDecoder` / `JSON.parse` are unbounded) — the cap belongs to the framework parser in front of it, which already rejects oversized bodies before the SDK ever sees them. Queek deliveries are small JSON payloads, so the framework defaults are plenty; if you set them explicitly, `express.raw({ type: "application/json", limit: "1mb" })` and Fastify's default `bodyLimit` of 1 MiB are the recommended ceilings.
|
|
132
|
-
|
|
133
|
-
(If you already run Hono on Node, `@hono/node-server`'s `getRequestListener` bridges serving for you — but the adapters above need no extra dependency.)
|
|
154
|
+
The SDK itself imposes no maximum body size (`arrayBuffer` / `TextDecoder` / `JSON.parse` are unbounded) — the cap belongs to the framework parser in front of it, which already rejects oversized bodies before the SDK ever sees them. Queek deliveries are small JSON payloads, so the framework defaults are plenty; if you set them explicitly, `express.raw({ type: "application/json", limit: "1mb" })` and Fastify's default `bodyLimit` of 1 MiB are the recommended ceilings. The SDK has no body-size option.
|
|
134
155
|
|
|
135
156
|
### Hono
|
|
136
157
|
|
|
137
|
-
Prefer Hono? The thin wrappers under `@usequeek/app-sdk/hono` (same options, same behaviour, same errors/status codes) mount the same core
|
|
158
|
+
Prefer Hono? The thin wrappers under `@usequeek/app-sdk/hono` (same options, same behaviour, same errors/status codes) mount the same core. (If you already run Hono on Node, `@hono/node-server`'s `getRequestListener` bridges serving for you.)
|
|
138
159
|
|
|
139
160
|
```ts
|
|
140
161
|
import { SqliteInstallationStore } from "@usequeek/app-sdk";
|
|
@@ -167,28 +188,33 @@ export default app;
|
|
|
167
188
|
Every Merchant API call goes through `createInstallationClient({ installationId, apiBase, tokens })`,
|
|
168
189
|
which resolves the installation's token via `acquireToken()` and sends it as `X-Client-Key`.
|
|
169
190
|
|
|
170
|
-
## Credential lifecycle (
|
|
191
|
+
## Credential lifecycle (GitHub-style installation tokens)
|
|
171
192
|
|
|
172
|
-
One asymmetric credential per app — no per-installation secrets cross the handoff
|
|
193
|
+
One asymmetric credential per app — no per-installation secrets cross the handoff.
|
|
173
194
|
|
|
174
|
-
1. **
|
|
175
|
-
|
|
195
|
+
1. **Keypair — two paths.** (a) Offline: generate locally, then upload the PUBLIC key on
|
|
196
|
+
the Developer page (Keys) — Queek never receives the private key:
|
|
176
197
|
|
|
177
198
|
```sh
|
|
178
199
|
openssl genrsa -out app-private.pem 2048
|
|
179
200
|
openssl rsa -in app-private.pem -pubout -out app-public.pem
|
|
180
201
|
```
|
|
181
202
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
203
|
+
(b) Faster: `queek app dev` server-generates the pair and stores the kid + private key
|
|
204
|
+
(base64, one line) in `.queek/.env.local` (gitignored, 0600) — the private key is
|
|
205
|
+
returned once and never shown again; if lost, generate a new key. Either way, keep the
|
|
206
|
+
private key in your secrets manager beside `APP_ENCRYPTION_KEY` (key + DB loss = working
|
|
207
|
+
data lost — see below).
|
|
208
|
+
|
|
209
|
+
2. **Register the app** on the Queek Developer page, then configure five values — each from
|
|
210
|
+
exactly one place: `APP_SLUG` is the slug in `queek.app.toml` (= JWT `iss`); `APP_KEY_ID`
|
|
211
|
+
is the kid from Keys (returned by generate/upload, or written by `queek app dev`);
|
|
212
|
+
`APP_PRIVATE_KEY` is the matching private key (your offline file, or the once-shown
|
|
213
|
+
server-generated one; raw PEM also accepted — never logged, never shipped to clients);
|
|
214
|
+
`QUEEK_APP_SECRET` verifies the handoff (shown once on first registration — `queek app
|
|
215
|
+
deploy` appends it to `.queek/.env.local`; `queek app dev` fetches it there; or Reveal
|
|
216
|
+
it on the dashboard) and is never rotated by dev; `APP_ENCRYPTION_KEY` is 32 local
|
|
217
|
+
random bytes, base64 or hex (`queek app dev` mints one into `.queek/.env.local`).
|
|
192
218
|
|
|
193
219
|
3. **Acquire / cache / re-mint.** `acquireToken(installationId)` serves the cached token while
|
|
194
220
|
its expiry is more than 5 minutes away; otherwise it signs an RS256 app JWT
|
|
@@ -197,14 +223,14 @@ One asymmetric credential per app — no per-installation secrets cross the hand
|
|
|
197
223
|
`kid` (one shared cache row per installation — restarts never burst). Concurrent callers
|
|
198
224
|
in one process share one in-flight mint; two containers minting at once is harmless by
|
|
199
225
|
design (Queek keeps coexisting tokens valid; a residual race self-heals via re-mint).
|
|
200
|
-
|
|
226
|
+
Token refusals: any 401, or 403 `api_key_revoked` / `api_key_expired` /
|
|
201
227
|
`invalid_client_key` → drop the token, re-mint once, retry once (a second refusal
|
|
202
228
|
propagates); 403 `app_token_revoked` → drop ALL cached tokens and halt minting (kill
|
|
203
229
|
switch / disabled app, no mint); every other 403 (scope, plan, mode) propagates to the
|
|
204
230
|
caller without a mint.
|
|
205
231
|
|
|
206
|
-
4. **Failures
|
|
207
|
-
constant
|
|
232
|
+
4. **Failures are handled by status and code** (`app-auth.ts` holds each code in one
|
|
233
|
+
constant):
|
|
208
234
|
- `401 invalid_client` — fatal for the app: loud log, minting stops, no retry loop.
|
|
209
235
|
- `403 app_token_revoked` (kill switch / disabled app) — drops ALL cached tokens, stops
|
|
210
236
|
minting, loud log. Minting resumes after the app is re-enabled + process restart
|
|
@@ -232,10 +258,10 @@ One asymmetric credential per app — no per-installation secrets cross the hand
|
|
|
232
258
|
existing installation idempotently (secret + settings refresh, `installedAt` and the
|
|
233
259
|
cached token kept).
|
|
234
260
|
|
|
235
|
-
6. **Kill switch behaviour.** One
|
|
236
|
-
every store on the next request. The SDK side
|
|
237
|
-
|
|
238
|
-
process restarts (or resumes).
|
|
261
|
+
6. **Kill switch behaviour.** One operation on Queek's side (the kill switch) revokes every
|
|
262
|
+
token of an app across every store on the next request. The SDK side is the
|
|
263
|
+
`app_token_revoked` row of item 4: every call fails closed until the app is re-enabled
|
|
264
|
+
and the process restarts (or resumes). Key rotation: add a `kid` (both verify) →
|
|
239
265
|
switch the app to it → wait one token TTL → remove the old `kid`.
|
|
240
266
|
|
|
241
267
|
## Data rule + backups
|
|
@@ -245,7 +271,7 @@ Business data lives in Queek. `resyncFromQueek` restores CONNECTIVITY with zero
|
|
|
245
271
|
action — but app WORKING data (inbound tokens, order links, form tokens, app-side-only
|
|
246
272
|
settings) does NOT come back from resync: it needs the per-app `pg_dump` backup
|
|
247
273
|
(RPO ≤ 24 h) plus the `APP_ENCRYPTION_KEY` backup. Events missed beyond Queek's webhook
|
|
248
|
-
retries (~4 h) are gone; resync cannot backfill them.
|
|
274
|
+
retries (~4 h) are gone; resync cannot backfill them. Deploy reference: `queek app deploy --help`.
|
|
249
275
|
|
|
250
276
|
## Data deletion
|
|
251
277
|
|
|
@@ -261,8 +287,8 @@ topics, register explicit handlers for them — an unhandled topic only reports
|
|
|
261
287
|
|
|
262
288
|
## Storefront app-proxy (signed reads)
|
|
263
289
|
|
|
264
|
-
Queek signs each storefront proxy fetch with the installation's `proxy_secret
|
|
265
|
-
|
|
290
|
+
Queek signs each storefront proxy fetch with the installation's `proxy_secret`;
|
|
291
|
+
verify before answering the shopper:
|
|
266
292
|
|
|
267
293
|
```ts
|
|
268
294
|
import { handleProxyRequest } from "@usequeek/app-sdk";
|
|
@@ -281,7 +307,7 @@ The canonical string is `path + "\n" + shop + "\n" + ts + "\n" + sorted(k=v&...)
|
|
|
281
307
|
(`sig` excluded), hex HMAC-SHA256 over the FULL `whsec_…` string — no base64 decode
|
|
282
308
|
step. `kid` routes to the installation whose `proxy_secret` verifies (previous-secret
|
|
283
309
|
grace: pass every active secret to `verifyProxyQuery`); timestamps skew at most 5
|
|
284
|
-
minutes, each `jti` is single-use, and only `GET` is served (
|
|
310
|
+
minutes, each `jti` is single-use, and only `GET` is served (proxy reads are read-only).
|
|
285
311
|
Hono: mount `createProxyHandler({ store, path, onVerified })` from
|
|
286
312
|
`@usequeek/app-sdk/hono`.
|
|
287
313
|
|
|
@@ -290,32 +316,31 @@ Hono: mount `createProxyHandler({ store, path, onVerified })` from
|
|
|
290
316
|
- **app-auth** (`app-auth.ts`): `loadAppCredential` (`APP_SLUG`/`APP_KEY_ID`/`APP_PRIVATE_KEY`
|
|
291
317
|
— base64 of the PEM, raw PEM, or `\n`-escaped one-line PEM; RSA validated at boot),
|
|
292
318
|
`signAppJwt` (RS256, `iat` now − 60 s, `exp` window 540 s ≤ 600 s,
|
|
293
|
-
`kid` header), the
|
|
319
|
+
`kid` header), the error codes in one place (`INVALID_CLIENT_CODE`,
|
|
294
320
|
`APP_TOKEN_REVOKED_CODE`, `APP_INSTALLATION_GONE_CODE`, `APP_INSTALLATION_PENDING_CODE`,
|
|
295
321
|
`RESYNC_COOLDOWN_CODE`, `TOO_MANY_REQUESTS_CODE`), `AppMintHaltedError`.
|
|
296
322
|
- **tokens** (`tokens.ts`): `createAppTokenProvider({ credential, store, … })` —
|
|
297
|
-
`acquireToken` (cache → sign → mint → persist), single-flight per installation, the
|
|
298
|
-
|
|
323
|
+
`acquireToken` (cache → sign → mint → persist), single-flight per installation, with the
|
|
324
|
+
error handling in Credential lifecycle items 3–4;
|
|
299
325
|
`createInstallationClient({ installationId, apiBase, tokens })` — the `QueekClient`
|
|
300
326
|
every app call uses (re-mint once + retry once on token refusals only).
|
|
301
|
-
- **resync** (`resync.ts`): `resyncFromQueek({ apiBase, tokens, store })` —
|
|
302
|
-
|
|
303
|
-
429 `resync_cooldown` → skip + record; other 429 → backoff + retry) → drop tokens →
|
|
304
|
-
purge absent except known-pending. Connectivity scope only.
|
|
327
|
+
- **resync** (`resync.ts`): `resyncFromQueek({ apiBase, tokens, store })` — the flow in
|
|
328
|
+
Credential lifecycle item 5. Connectivity scope only.
|
|
305
329
|
- **verify** (`signatures.ts`): `verifyQueekSignature` — Standard Webhooks verification (`webhook-id`, `webhook-timestamp`, `webhook-signature` over `{id}.{timestamp}.{body}`, keyed by the decoded `whsec_…` bytes), with timestamp-skew enforcement.
|
|
306
330
|
- **delivery core** (`delivery.ts`): `CoreDelivery` (`rawBody` + `headers`) / `InstallDelivery` (+ `method`/`path`) / `DeliveryResult` (`{ status, body }`) / `CoreHeaders`, plus `readHeader` (case-insensitive, array-tolerant), `decodeBody`, and `toResponse`. Zero request/response types.
|
|
307
331
|
- **install handlers** (`install-handlers.ts`): layer 1 `handleInstallDelivery(input, { appSecret, store, onInstall?, onUninstall?, onSettings? })` serves the signed install/uninstall/settings handoff from raw bytes + headers (routes on the path's trailing segment); layer 2 `handleInstallRequest(request, …)` adapts `Request` → `Response` onto it. Defaults persist the installation (encrypted) in the store; a redelivered install for an existing installation merges idempotently (`saveResyncedInstallation`). The Hono wrapper `createInstallHandlers` lives under `@usequeek/app-sdk/hono` (`hono.ts`).
|
|
308
|
-
- **client** (`client.ts`): `createQueekClient({ apiBase, apiKey })` — the low-level typed fetch client over the Merchant API (`X-Client-Key`), with `Idempotency-Key` on writes, typed `QueekApiError`s, and 429 retry helpers. Both it and `createInstallationClient({ installationId, apiBase, tokens })` are generic (`<AppPaths>`, default: the bundled `merchant-schema.ts` compat shim): pass the app's own codegen output for current types
|
|
332
|
+
- **client** (`client.ts`): `createQueekClient({ apiBase, apiKey })` — the low-level typed fetch client over the Merchant API (`X-Client-Key`), with `Idempotency-Key` on writes, typed `QueekApiError`s, and 429 retry helpers. Both it and `createInstallationClient({ installationId, apiBase, tokens })` are generic (`<AppPaths>`, default: the bundled `merchant-schema.ts` compat shim): pass the app's own codegen output for current types without waiting for an SDK release. The default does NOT auto-update (re-run codegen in the app; sunset signal at 1.0). `openapi/merchant.json` stays in-repo as `gen:merchant`'s reference input and is not shipped in the published package. Prefer `createInstallationClient` in apps.
|
|
309
333
|
- **webhooks** (`webhooks.ts`): layer 1 `handleWebhookDelivery(input, { store, handlers })` verifies each delivery against the installation's endpoint secret, dedupes on `webhook-id`, and dispatches `topic → handler` at most once; layer 2 `handleWebhookRequest(request, …)` adapts `Request` → `Response` onto it. The Hono wrapper `createWebhookHandler` lives under `@usequeek/app-sdk/hono` (`hono.ts`). Unknown topics answer 200 `unhandled` — see Data deletion before relying on that.
|
|
310
|
-
- **proxy** (`proxy.ts`): `verifyProxyQuery` / `verifyProxyQueryDetailed` — app-proxy query verification byte-exact with
|
|
334
|
+
- **proxy** (`proxy.ts`): `verifyProxyQuery` / `verifyProxyQueryDetailed` — app-proxy query verification byte-exact with Queek's signer (`path\nshop\nts\nsorted(k=v&...)`, hex HMAC-SHA256 over the FULL `whsec_…` string, 5-minute skew floored at 60 s, `timingSafeEqual`, previous-secret grace over the secrets list); layer 1 `verifyProxyDelivery(input, { store, … })` resolves the installation from the store by `kid` and claims single-use `jti`; layer 2 `handleProxyRequest(request, { store, path, … }, onVerified)` serves GET only. The Hono wrapper `createProxyHandler` lives under `@usequeek/app-sdk/hono` (`hono.ts`).
|
|
311
335
|
- **store** (`store.ts`): `SqliteInstallationStore` (local/dev/test) and `PostgresInstallationStore`
|
|
312
336
|
(`pg`, pool max 2, advisory-locked schema + `schema_version` row so two containers boot
|
|
313
337
|
safely) — installations encrypted at rest (AES-GCM via `APP_ENCRYPTION_KEY`), plus the
|
|
314
338
|
persisted 409-pending mark (`pending` column, schema v2, migrated in place) and the
|
|
315
339
|
seen-webhook-id claim table behind dedupe. Pick with `createInstallationStore()`
|
|
316
340
|
(`DATABASE_URL` set → Postgres, else SQLite — which production REFUSES with a clear
|
|
317
|
-
message).
|
|
318
|
-
a row to the app's database, with that store's token + webhook secret
|
|
341
|
+
message). `APP_ENCRYPTION_KEY` is set once when the app is deployed; installs never change env:
|
|
342
|
+
each install adds a row to the app's database, with that store's token + webhook secret
|
|
343
|
+
encrypted using this key.
|
|
319
344
|
- **background** (`background.ts`): `detach` plus `runInstallationCatchup` — jittered
|
|
320
345
|
per-installation cron (uniform 0–600 s start jitter, per-install error isolation,
|
|
321
346
|
concurrency ≤ pool size, honors 429 once per installation).
|
|
@@ -325,15 +350,16 @@ Hono: mount `createProxyHandler({ store, path, onVerified })` from
|
|
|
325
350
|
`verifySessionToken` — HS256 dashboard session tokens minted per installation
|
|
326
351
|
(`embsec_…` secret, raw UTF-8 key bytes, 20 s clock tolerance, slug audience,
|
|
327
352
|
issuer = the handoff `apiBase` verbatim (`installation.apiBase` — it equals the
|
|
328
|
-
bare `app.url`
|
|
353
|
+
bare `app.url` Queek signs as `iss`), full installation binding). There is one token
|
|
354
|
+
type: the dashboard puts the same token in the first-load URL param
|
|
355
|
+
(`queek_token`, stripped on arrival) and answers bridge `ready` requests with it,
|
|
356
|
+
so first-load and refresh tokens are verified by this one verifier — no purpose
|
|
357
|
+
split, extra claims are ignored. The secret never enters a
|
|
329
358
|
browser bundle: the main entry does not export the verifier. The install
|
|
330
359
|
and resync handoffs deliver `embed_secret` + `app_id`; the store keeps
|
|
331
360
|
them on the installation (`embedSecret` encrypted, `appId`), and
|
|
332
361
|
`sessionTokenInstallationId` reads the token's `installation_id` as an
|
|
333
362
|
unverified routing hint so a server can load that row before verifying.
|
|
334
|
-
`verifyLaunchToken` checks the signed-first-load token the same way plus the purpose pin
|
|
335
|
-
(`purpose` must be `launch`, with the `store` p_id and `theme` hints) — a launch token is refused
|
|
336
|
-
as a bridge token and vice versa.
|
|
337
363
|
- **frame** (`frame.ts`, browser-safe, no secret): the embedded-app bridge
|
|
338
364
|
v1 — typed unions both directions (`ready{capabilities,sdkVersion}`,
|
|
339
365
|
`theme`, `title`/`title-action`, `toast`, `save-bar`/`save-bar-action`,
|
|
@@ -363,8 +389,10 @@ Hono: mount `createProxyHandler({ store, path, onVerified })` from
|
|
|
363
389
|
Each pick carries a `requestId` (on `pick-resource`); a dashboard that
|
|
364
390
|
declares `pick-resource` MUST echo it on `resource-picked` / `resource-pick-cancelled` — answers with a
|
|
365
391
|
different or missing id are dropped as stale.
|
|
366
|
-
- **auth** (`auth-fetch.ts`, browser-safe
|
|
367
|
-
|
|
392
|
+
- **auth** (`auth-fetch.ts`, no secret — browser-safe as a module, but import it
|
|
393
|
+
from `@usequeek/app-sdk/browser` in browser bundles, never from the main
|
|
394
|
+
entry): `installAuthFetch({ exchange })`
|
|
395
|
+
— reads the dashboard token (`queek_token` query param) from the first
|
|
368
396
|
load, strips it with `history.replaceState`, exchanges it once for the
|
|
369
397
|
app's own session, attaches `Authorization: Bearer <session>` to
|
|
370
398
|
same-origin fetch (cookies stay out), and re-establishes via the bridge
|
|
@@ -372,27 +400,25 @@ Hono: mount `createProxyHandler({ store, path, onVerified })` from
|
|
|
372
400
|
window itself) and is not retried per request after a failed attempt — only
|
|
373
401
|
after a later dashboard token arrives. Pass the same `capabilities`/
|
|
374
402
|
`sdkVersion` as the provider so its refresh `ready` announces one consistent set. The app's
|
|
375
|
-
token-exchange endpoint receives the
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
(both purposes):
|
|
403
|
+
token-exchange endpoint receives the first-load token and every 401-refresh
|
|
404
|
+
token through the same `exchange` callback, so it verifies both with the
|
|
405
|
+
single `verifySessionToken` verifier:
|
|
379
406
|
|
|
380
407
|
```ts
|
|
381
|
-
import {
|
|
408
|
+
import { verifySessionTokenDetailed } from "@usequeek/app-sdk/server";
|
|
382
409
|
|
|
383
410
|
async function exchange(token: string): Promise<string> {
|
|
384
411
|
const options = { secret: installation.embedSecret!, audience: "my-app", issuer: installation.apiBase, expected };
|
|
385
|
-
const
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
if (bridge.ok) return mintAppSession(bridge.claims);
|
|
389
|
-
throw Object.assign(new Error("unauthorized"), { status: 401 });
|
|
412
|
+
const checked = await verifySessionTokenDetailed(token, options);
|
|
413
|
+
if (!checked.ok) throw Object.assign(new Error("unauthorized"), { status: 401 });
|
|
414
|
+
return mintAppSession(checked.claims);
|
|
390
415
|
}
|
|
391
416
|
```
|
|
392
417
|
- **theme** (`theme.ts`, browser-safe, no secret): `applyTheme(mode)` toggles
|
|
393
418
|
`.dark` on `<html>` + `color-scheme` (shadcn's dark-mode mechanism);
|
|
394
|
-
`themeBootstrapScript()` returns the inline `<head>` snippet that reads
|
|
395
|
-
`theme`
|
|
419
|
+
`themeBootstrapScript()` returns the inline `<head>` snippet that reads the
|
|
420
|
+
unsigned `theme` URL param before first paint (no flash — a first-paint hint
|
|
421
|
+
only, validated to `light`|`dark`, overwritten by the live bridge message);
|
|
396
422
|
`installThemeListener` follows live bridge `theme{mode}` messages (only from
|
|
397
423
|
`window.parent` by default; pass `expectSource` to override). The live mode is
|
|
398
424
|
remembered in `sessionStorage`, and the bootstrap script falls back to it when
|
|
@@ -439,14 +465,14 @@ list you pass (the app surface exposes the grant but no declared list, so the
|
|
|
439
465
|
split needs your manifest knowledge). `requestScopes` returns
|
|
440
466
|
`/apps?app={slug}&view=scopes&scopes={a},{b}` (absolute under
|
|
441
467
|
`dashboardOrigin` when configured) — open it via `sendOpen` or a redirect: it
|
|
442
|
-
opens the merchant's consent screen in the dashboard (
|
|
443
|
-
|
|
468
|
+
opens the merchant's consent screen in the dashboard (requires a dashboard that
|
|
469
|
+
supports the consent screen). The link carries the slug + the scope list only; the dashboard
|
|
444
470
|
resolves the installation from the signed-in store. The grant reaches you as a
|
|
445
471
|
signed `app/scopes_update` handoff, which the install/settings handlers apply
|
|
446
472
|
like a resync (same verifier, same replay claim): cached grant refreshed,
|
|
447
473
|
cached token dropped only when the grant moved.
|
|
448
474
|
|
|
449
|
-
## Embedded merchant page
|
|
475
|
+
## Embedded merchant page
|
|
450
476
|
|
|
451
477
|
The dashboard frames your app's granted merchant page in a
|
|
452
478
|
`sandbox="allow-scripts allow-forms"` iframe and delivers the session token
|
|
@@ -454,29 +480,22 @@ by postMessage. Serve the page with a `frame-ancestors` policy naming ONLY
|
|
|
454
480
|
your dashboard origin, for example:
|
|
455
481
|
|
|
456
482
|
```http
|
|
457
|
-
Content-Security-Policy: frame-ancestors https://
|
|
483
|
+
Content-Security-Policy: frame-ancestors https://dashboard.usequeek.com
|
|
458
484
|
```
|
|
459
485
|
|
|
460
486
|
Replace the host with the dashboard origin you registered. `frame-ancestors`
|
|
461
|
-
is a
|
|
462
|
-
site may frame the page, and the token handshake (origin-bound) is
|
|
463
|
-
remaining gate. Verify every token server-side before trusting calls that carry one
|
|
464
|
-
|
|
465
|
-
every 401 refresh through the same `installAuthFetch({ exchange })` callback, so it must try
|
|
466
|
-
`verifyLaunchTokenDetailed`/`verifyLaunchToken` first and fall back to `verifySessionToken`
|
|
467
|
-
(both purposes — see the `auth` entry above).
|
|
487
|
+
is a header only your server can set, so the dashboard cannot enforce it for you:
|
|
488
|
+
without it any site may frame the page, and the token handshake (origin-bound) is
|
|
489
|
+
your only remaining gate. Verify every token server-side before trusting calls that carry one
|
|
490
|
+
(see the `auth` entry under API surface).
|
|
468
491
|
|
|
469
492
|
## Local development
|
|
470
493
|
|
|
471
|
-
Point the client at a local
|
|
494
|
+
Point the client at a local Queek API with `QUEEK_DEV_API_HOSTS` (comma-separated hosts). It is refused when `NODE_ENV=production`, so it can never weaken a live app. See `devApiHostsFromEnv` in `client.ts`.
|
|
472
495
|
|
|
473
496
|
Postgres locally: any Postgres works — point `DATABASE_URL` at it and the pg suite runs
|
|
474
497
|
(CI always runs it via a service container); without `DATABASE_URL` those tests skip cleanly.
|
|
475
498
|
|
|
476
|
-
## Registration
|
|
477
|
-
|
|
478
|
-
App registration is by the Queek team today: you ship Queek your manifest URL and receive the app secret + store key. There is no public self-serve registration yet.
|
|
479
|
-
|
|
480
499
|
## License
|
|
481
500
|
|
|
482
501
|
MIT — see [LICENSE](./LICENSE).
|
package/dist/app-auth.d.ts
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The app credential
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* The app credential: ONE asymmetric RS256 key per app. Queek holds the
|
|
3
|
+
* PUBLIC keys (kid-capped); the app holds the PRIVATE key in
|
|
4
|
+
* `APP_PRIVATE_KEY` and signs short-lived app JWTs that mint
|
|
5
|
+
* per-installation tokens (`acquireToken()`).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `
|
|
10
|
-
* — no new dependency. The JWT and the private key are NEVER logged (the
|
|
7
|
+
* Every call under `/api/v1/apps` carries `Authorization: Bearer <app JWT>`
|
|
8
|
+
* (RS256, header `kid`, claims `iss` = app slug, `iat`, `exp`,
|
|
9
|
+
* `exp − iat` ≤ 600 s). `node:crypto` only — no new dependency. The JWT and the private key are NEVER logged (the
|
|
11
10
|
* logger redacts `Bearer …` values; signers below never emit them).
|
|
12
11
|
*/
|
|
13
12
|
/** 401 on an app-credential call: bad/expired JWT or unknown kid. Fatal for the app. */
|
|
14
13
|
export declare const INVALID_CLIENT_CODE = "invalid_client";
|
|
15
14
|
/**
|
|
16
15
|
* 403 on an app-credential call: the app is disabled or its token epoch was
|
|
17
|
-
* bumped
|
|
18
|
-
* (`ApiError::APP_TOKEN_REVOKED`, returned in both cases); matched in
|
|
16
|
+
* bumped (kill switch, returned in both cases); matched in
|
|
19
17
|
* exactly one place (`isAppTokenRevoked`).
|
|
20
18
|
*/
|
|
21
19
|
export declare const APP_TOKEN_REVOKED_CODE = "app_token_revoked";
|
|
@@ -55,10 +53,10 @@ export declare function isInstallationPending(code: string | undefined): boolean
|
|
|
55
53
|
/** True when a resync 429 code is the per-installation rotation cooldown (skip, recorded). */
|
|
56
54
|
export declare function isResyncCooldown(code: string | undefined): boolean;
|
|
57
55
|
/**
|
|
58
|
-
* Merchant-API token-refusal codes
|
|
56
|
+
* Merchant-API token-refusal codes: a token in one of these states
|
|
59
57
|
* is dead — drop it, re-mint once, retry once. `api_key_revoked` covers the
|
|
60
|
-
* uninstalled store, the removed kid, and
|
|
61
|
-
* then answers 404 `app_installation_gone` (purge) or 401 `invalid_client`
|
|
58
|
+
* uninstalled store, the removed kid, and a token replaced by a newer one;
|
|
59
|
+
* the re-mint then answers 404 `app_installation_gone` (purge) or 401 `invalid_client`
|
|
62
60
|
* (halt) where applicable. Every OTHER merchant 403 (plan, mode)
|
|
63
61
|
* propagates to the caller without a mint.
|
|
64
62
|
*/
|
|
@@ -112,7 +110,7 @@ export declare class UnknownInstallationError extends Error {
|
|
|
112
110
|
constructor(installationId: string);
|
|
113
111
|
}
|
|
114
112
|
export interface AppCredential {
|
|
115
|
-
/** `iss`: the app slug (as registered
|
|
113
|
+
/** `iss`: the app slug (as registered on the Queek Developer page). */
|
|
116
114
|
appSlug: string;
|
|
117
115
|
/** `kid`: which of the app's public keys Queek verifies against (`APP_KEY_ID`). */
|
|
118
116
|
keyId: string;
|
|
@@ -128,7 +126,8 @@ export interface AppCredentialOptions {
|
|
|
128
126
|
/**
|
|
129
127
|
* Accepted `APP_PRIVATE_KEY` forms (env files are one line per variable):
|
|
130
128
|
* raw PEM text, one-line PEM with literal `\n` escapes, or base64 of the
|
|
131
|
-
* full PEM text (one line, no wrapping —
|
|
129
|
+
* full PEM text (one line, no wrapping — the form `queek app dev` writes to
|
|
130
|
+
* `.queek/.env.local`; a server-generated key is shown once, never again).
|
|
132
131
|
*/
|
|
133
132
|
export declare const APP_PRIVATE_KEY_FORMS = "raw PEM text, one-line PEM with literal \\n escapes, or base64 of the PEM text";
|
|
134
133
|
/**
|
package/dist/app-auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-auth.d.ts","sourceRoot":"","sources":["../src/app-auth.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"app-auth.d.ts","sourceRoot":"","sources":["../src/app-auth.ts"],"names":[],"mappings":"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"}
|
package/dist/app-auth.js
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
import { createPrivateKey, createSign } from "node:crypto";
|
|
2
2
|
/**
|
|
3
|
-
* The app credential
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* The app credential: ONE asymmetric RS256 key per app. Queek holds the
|
|
4
|
+
* PUBLIC keys (kid-capped); the app holds the PRIVATE key in
|
|
5
|
+
* `APP_PRIVATE_KEY` and signs short-lived app JWTs that mint
|
|
6
|
+
* per-installation tokens (`acquireToken()`).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
11
|
-
* — no new dependency. The JWT and the private key are NEVER logged (the
|
|
8
|
+
* Every call under `/api/v1/apps` carries `Authorization: Bearer <app JWT>`
|
|
9
|
+
* (RS256, header `kid`, claims `iss` = app slug, `iat`, `exp`,
|
|
10
|
+
* `exp − iat` ≤ 600 s). `node:crypto` only — no new dependency. The JWT and the private key are NEVER logged (the
|
|
12
11
|
* logger redacts `Bearer …` values; signers below never emit them).
|
|
13
12
|
*/
|
|
14
13
|
// ---------------------------------------------------------------------------
|
|
15
|
-
//
|
|
16
|
-
// one constant here, so a
|
|
17
|
-
// B1/B2 build must confirm.
|
|
14
|
+
// Error codes on the wire. Each code the SDK reacts to lives in EXACTLY
|
|
15
|
+
// one constant here, so a rename on Queek's side is a one-line change.
|
|
18
16
|
// ---------------------------------------------------------------------------
|
|
19
17
|
/** 401 on an app-credential call: bad/expired JWT or unknown kid. Fatal for the app. */
|
|
20
18
|
export const INVALID_CLIENT_CODE = "invalid_client";
|
|
21
19
|
/**
|
|
22
20
|
* 403 on an app-credential call: the app is disabled or its token epoch was
|
|
23
|
-
* bumped
|
|
24
|
-
* (`ApiError::APP_TOKEN_REVOKED`, returned in both cases); matched in
|
|
21
|
+
* bumped (kill switch, returned in both cases); matched in
|
|
25
22
|
* exactly one place (`isAppTokenRevoked`).
|
|
26
23
|
*/
|
|
27
24
|
export const APP_TOKEN_REVOKED_CODE = "app_token_revoked";
|
|
@@ -71,10 +68,10 @@ export function isResyncCooldown(code) {
|
|
|
71
68
|
return code === RESYNC_COOLDOWN_CODE;
|
|
72
69
|
}
|
|
73
70
|
/**
|
|
74
|
-
* Merchant-API token-refusal codes
|
|
71
|
+
* Merchant-API token-refusal codes: a token in one of these states
|
|
75
72
|
* is dead — drop it, re-mint once, retry once. `api_key_revoked` covers the
|
|
76
|
-
* uninstalled store, the removed kid, and
|
|
77
|
-
* then answers 404 `app_installation_gone` (purge) or 401 `invalid_client`
|
|
73
|
+
* uninstalled store, the removed kid, and a token replaced by a newer one;
|
|
74
|
+
* the re-mint then answers 404 `app_installation_gone` (purge) or 401 `invalid_client`
|
|
78
75
|
* (halt) where applicable. Every OTHER merchant 403 (plan, mode)
|
|
79
76
|
* propagates to the caller without a mint.
|
|
80
77
|
*/
|
|
@@ -146,14 +143,15 @@ export class AppMintHaltedError extends Error {
|
|
|
146
143
|
export class UnknownInstallationError extends Error {
|
|
147
144
|
code = "unknown_installation";
|
|
148
145
|
constructor(installationId) {
|
|
149
|
-
super(`Unknown installation ${installationId}: no local row (
|
|
146
|
+
super(`Unknown installation ${installationId}: no local row. Recover with resyncFromQueek() (lists the app's active installations from Queek) or reinstall the app on the store.`);
|
|
150
147
|
this.name = "UnknownInstallationError";
|
|
151
148
|
}
|
|
152
149
|
}
|
|
153
150
|
/**
|
|
154
151
|
* Accepted `APP_PRIVATE_KEY` forms (env files are one line per variable):
|
|
155
152
|
* raw PEM text, one-line PEM with literal `\n` escapes, or base64 of the
|
|
156
|
-
* full PEM text (one line, no wrapping —
|
|
153
|
+
* full PEM text (one line, no wrapping — the form `queek app dev` writes to
|
|
154
|
+
* `.queek/.env.local`; a server-generated key is shown once, never again).
|
|
157
155
|
*/
|
|
158
156
|
export const APP_PRIVATE_KEY_FORMS = "raw PEM text, one-line PEM with literal \\n escapes, or base64 of the PEM text";
|
|
159
157
|
/**
|
|
@@ -183,15 +181,15 @@ export function loadAppCredential(options = {}) {
|
|
|
183
181
|
const env = options.env ?? process.env;
|
|
184
182
|
const appSlug = (options.appSlug ?? env.APP_SLUG ?? "").trim();
|
|
185
183
|
if (appSlug === "") {
|
|
186
|
-
throw new InvalidAppCredentialError("Missing app slug for `iss`: pass appSlug or set APP_SLUG to the slug
|
|
184
|
+
throw new InvalidAppCredentialError("Missing app slug for `iss`: pass appSlug or set APP_SLUG to the slug from the Queek Developer page (register the app there, or run `queek app dev` — it writes .queek/.env.local).");
|
|
187
185
|
}
|
|
188
186
|
const keyId = (options.keyId ?? env.APP_KEY_ID ?? "").trim();
|
|
189
187
|
if (keyId === "") {
|
|
190
|
-
throw new InvalidAppCredentialError("Missing APP_KEY_ID: set it to the
|
|
188
|
+
throw new InvalidAppCredentialError("Missing APP_KEY_ID: set it to the key id shown on the Queek Developer page for this app (`queek app dev` writes it to .queek/.env.local on first run).");
|
|
191
189
|
}
|
|
192
190
|
const privateKeyInput = (options.privateKeyPem ?? env.APP_PRIVATE_KEY ?? "").trim();
|
|
193
191
|
if (privateKeyInput === "") {
|
|
194
|
-
throw new InvalidAppCredentialError(`Missing APP_PRIVATE_KEY: set it to
|
|
192
|
+
throw new InvalidAppCredentialError(`Missing APP_PRIVATE_KEY: set it to the app's RSA private key — your offline-generated file, or the server-generated key (shown once on the Developer page; \`queek app dev\` stores it in .queek/.env.local). If lost, generate a new key. Accepted forms: ${APP_PRIVATE_KEY_FORMS}.`);
|
|
195
193
|
}
|
|
196
194
|
const privateKeyPem = decodePrivateKeyInput(privateKeyInput);
|
|
197
195
|
try {
|
package/dist/app-auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-auth.js","sourceRoot":"","sources":["../src/app-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE3D
|
|
1
|
+
{"version":3,"file":"app-auth.js","sourceRoot":"","sources":["../src/app-auth.ts"],"names":[],"mappings":"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"}
|