@usequeek/app-sdk 0.4.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/LICENSE +21 -0
  3. package/README.md +210 -0
  4. package/dist/app-auth.d.ts +139 -0
  5. package/dist/app-auth.d.ts.map +1 -0
  6. package/dist/app-auth.js +205 -0
  7. package/dist/app-auth.js.map +1 -0
  8. package/dist/background.d.ts +68 -0
  9. package/dist/background.d.ts.map +1 -0
  10. package/dist/background.js +78 -0
  11. package/dist/background.js.map +1 -0
  12. package/dist/client.d.ts +148 -0
  13. package/dist/client.d.ts.map +1 -0
  14. package/dist/client.js +269 -0
  15. package/dist/client.js.map +1 -0
  16. package/dist/collected.d.ts +103 -0
  17. package/dist/collected.d.ts.map +1 -0
  18. package/dist/collected.js +59 -0
  19. package/dist/collected.js.map +1 -0
  20. package/dist/crypto.d.ts +6 -0
  21. package/dist/crypto.d.ts.map +1 -0
  22. package/dist/crypto.js +76 -0
  23. package/dist/crypto.js.map +1 -0
  24. package/dist/frame.d.ts +58 -0
  25. package/dist/frame.d.ts.map +1 -0
  26. package/dist/frame.js +56 -0
  27. package/dist/frame.js.map +1 -0
  28. package/dist/handoff.d.ts +113 -0
  29. package/dist/handoff.d.ts.map +1 -0
  30. package/dist/handoff.js +29 -0
  31. package/dist/handoff.js.map +1 -0
  32. package/dist/index.d.ts +25 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +25 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/install-handlers.d.ts +62 -0
  37. package/dist/install-handlers.d.ts.map +1 -0
  38. package/dist/install-handlers.js +238 -0
  39. package/dist/install-handlers.js.map +1 -0
  40. package/dist/logger.d.ts +27 -0
  41. package/dist/logger.d.ts.map +1 -0
  42. package/dist/logger.js +75 -0
  43. package/dist/logger.js.map +1 -0
  44. package/dist/merchant-schema.d.ts +13963 -0
  45. package/dist/merchant-schema.d.ts.map +1 -0
  46. package/dist/merchant-schema.js +11 -0
  47. package/dist/merchant-schema.js.map +1 -0
  48. package/dist/resync.d.ts +80 -0
  49. package/dist/resync.d.ts.map +1 -0
  50. package/dist/resync.js +203 -0
  51. package/dist/resync.js.map +1 -0
  52. package/dist/server.d.ts +9 -0
  53. package/dist/server.d.ts.map +1 -0
  54. package/dist/server.js +8 -0
  55. package/dist/server.js.map +1 -0
  56. package/dist/session.d.ts +66 -0
  57. package/dist/session.d.ts.map +1 -0
  58. package/dist/session.js +155 -0
  59. package/dist/session.js.map +1 -0
  60. package/dist/signatures.d.ts +55 -0
  61. package/dist/signatures.d.ts.map +1 -0
  62. package/dist/signatures.js +74 -0
  63. package/dist/signatures.js.map +1 -0
  64. package/dist/store.d.ts +278 -0
  65. package/dist/store.d.ts.map +1 -0
  66. package/dist/store.js +610 -0
  67. package/dist/store.js.map +1 -0
  68. package/dist/tokens.d.ts +162 -0
  69. package/dist/tokens.d.ts.map +1 -0
  70. package/dist/tokens.js +411 -0
  71. package/dist/tokens.js.map +1 -0
  72. package/dist/webhooks.d.ts +60 -0
  73. package/dist/webhooks.d.ts.map +1 -0
  74. package/dist/webhooks.js +125 -0
  75. package/dist/webhooks.js.map +1 -0
  76. package/openapi/merchant.json +21452 -0
  77. package/package.json +63 -0
@@ -0,0 +1,205 @@
1
+ import { createPrivateKey, createSign } from "node:crypto";
2
+ /**
3
+ * The app credential (S1, SDK 0.2.0): ONE asymmetric RS256 key per app.
4
+ * Queek holds the PUBLIC keys (`apps.public_keys`, kid-capped); the app
5
+ * holds the PRIVATE key in `APP_PRIVATE_KEY` and signs short-lived app
6
+ * JWTs that mint per-installation tokens (`acquireToken()`).
7
+ *
8
+ * Wire contract (BINDING for B2 and S1): every call under `/api/v1/apps`
9
+ * carries `Authorization: Bearer <app JWT>` (RS256, header `kid`, claims
10
+ * `iss` = app slug, `iat`, `exp`, `exp − iat` ≤ 600 s). `node:crypto` only
11
+ * — no new dependency. The JWT and the private key are NEVER logged (the
12
+ * logger redacts `Bearer …` values; signers below never emit them).
13
+ */
14
+ // ---------------------------------------------------------------------------
15
+ // Wire-contract error codes. Each code the SDK reacts to lives in EXACTLY
16
+ // one constant here, so a backend rename is a one-line change the parallel
17
+ // B1/B2 build must confirm.
18
+ // ---------------------------------------------------------------------------
19
+ /** 401 on an app-credential call: bad/expired JWT or unknown kid. Fatal for the app. */
20
+ export const INVALID_CLIENT_CODE = "invalid_client";
21
+ /**
22
+ * 403 on an app-credential call: the app is disabled or its token epoch was
23
+ * bumped by `app:revoke-tokens` (kill switch). Confirmed against B1
24
+ * (`ApiError::APP_TOKEN_REVOKED`, returned in both cases); matched in
25
+ * exactly one place (`isAppTokenRevoked`).
26
+ */
27
+ export const APP_TOKEN_REVOKED_CODE = "app_token_revoked";
28
+ /** 404 on an installation-scoped app call: purge that installation locally. */
29
+ export const APP_INSTALLATION_GONE_CODE = "app_installation_gone";
30
+ /**
31
+ * 409 on an installation-scoped app call: the installation is PENDING
32
+ * (not yet active). Retry later with backoff — NEVER purge, NEVER halt.
33
+ * `GET installations` lists active installations only, so a pending row is
34
+ * absent from the list: resync's purge-absent step must keep local rows
35
+ * the app knows (via a 409) are pending.
36
+ */
37
+ export const APP_INSTALLATION_PENDING_CODE = "app_installation_pending";
38
+ /**
39
+ * 429 on a per-installation resync: the ≤1/hour rotation COOLDOWN. Skip
40
+ * that installation (recorded, no retry loop). Any OTHER resync 429 is the
41
+ * per-app bucket (`too_many_requests`): back off on `Retry-After`
42
+ * (jittered) and retry — never recorded as a cooldown skip.
43
+ */
44
+ export const RESYNC_COOLDOWN_CODE = "resync_cooldown";
45
+ /** 429 everywhere: honor `Retry-After`, jittered. */
46
+ export const TOO_MANY_REQUESTS_CODE = "too_many_requests";
47
+ /** True when a Queek error code is the kill-switch/disabled refusal. */
48
+ export function isAppTokenRevoked(code) {
49
+ return code === APP_TOKEN_REVOKED_CODE;
50
+ }
51
+ /** True when a Queek error code is the fatal app-credential refusal. */
52
+ export function isInvalidClient(code) {
53
+ return code === INVALID_CLIENT_CODE;
54
+ }
55
+ /** True when a Queek error code means the installation is gone server-side. */
56
+ export function isInstallationGone(code) {
57
+ return code === APP_INSTALLATION_GONE_CODE;
58
+ }
59
+ /** True when a Queek error code means the installation is pending (409: retry later, never purge). */
60
+ export function isInstallationPending(code) {
61
+ return code === APP_INSTALLATION_PENDING_CODE;
62
+ }
63
+ /** True when a resync 429 code is the per-installation rotation cooldown (skip, recorded). */
64
+ export function isResyncCooldown(code) {
65
+ return code === RESYNC_COOLDOWN_CODE;
66
+ }
67
+ /**
68
+ * Merchant-API token-refusal codes (rev 7): a token in one of these states
69
+ * is dead — drop it, re-mint once, retry once. `api_key_revoked` covers the
70
+ * uninstalled store, the removed kid, and the replaced K-slot; the re-mint
71
+ * then answers 404 `app_installation_gone` (purge) or 401 `invalid_client`
72
+ * (halt) where applicable. Every OTHER merchant 403 (scope, plan, mode)
73
+ * propagates to the caller without a mint.
74
+ */
75
+ export const API_KEY_REVOKED_CODE = "api_key_revoked";
76
+ export const API_KEY_EXPIRED_CODE = "api_key_expired";
77
+ export const INVALID_CLIENT_KEY_CODE = "invalid_client_key";
78
+ /**
79
+ * True when a merchant-API refusal means the presented token is dead: any
80
+ * 401, or a 403 with a revoked/expired-key code. `app_token_revoked` is
81
+ * deliberately NOT here — that is the app-wide kill switch (drop all +
82
+ * halt), not a single dead token.
83
+ */
84
+ export function isTokenRefusal(status, code) {
85
+ if (status === 401)
86
+ return true;
87
+ return (status === 403 &&
88
+ (code === API_KEY_REVOKED_CODE || code === API_KEY_EXPIRED_CODE || code === INVALID_CLIENT_KEY_CODE));
89
+ }
90
+ // ---------------------------------------------------------------------------
91
+ // Timing bounds (all in the open, all injectable in tests).
92
+ // ---------------------------------------------------------------------------
93
+ /** App-JWT `iat` is backdated this far (clock skew, GitHub's recommendation). */
94
+ export const APP_JWT_SKEW_SECONDS = 60;
95
+ /** App-JWT lifetime: `exp = iat + 540`, so `exp − iat` (540 s) stays ≤ the 600 s wire bound. */
96
+ export const APP_JWT_TTL_SECONDS = 540;
97
+ /** A cached installation token counts as valid only while its expiry is more than this far away. */
98
+ export const TOKEN_VALIDITY_SKEW_SECONDS = 300;
99
+ /** Mint attempts per `acquireToken` before surfacing a 429/5xx (1 initial + retries). */
100
+ export const MAX_MINT_ATTEMPTS = 3;
101
+ /** 5xx backoff base (doubles per attempt); every sleep also carries uniform jitter (see below). */
102
+ export const MINT_BACKOFF_BASE_MS = 250;
103
+ /** 5xx backoff ceiling (a 429 `Retry-After` may exceed it — the server's word wins). */
104
+ export const MINT_BACKOFF_MAX_MS = 5_000;
105
+ /** Uniform jitter added to every mint/resync backoff sleep (0–1000 ms). */
106
+ export const RETRY_JITTER_MAX_MS = 1_000;
107
+ // ---------------------------------------------------------------------------
108
+ // Credential.
109
+ // ---------------------------------------------------------------------------
110
+ /** Thrown when `APP_PRIVATE_KEY`/`APP_KEY_ID`/slug fail validation — before any fetch. */
111
+ export class InvalidAppCredentialError extends Error {
112
+ code = "invalid_app_credential";
113
+ constructor(message) {
114
+ super(message);
115
+ this.name = "InvalidAppCredentialError";
116
+ }
117
+ }
118
+ /**
119
+ * Thrown (and cached in-process) when Queek refuses the app itself:
120
+ * `invalid_client` (bad/unknown key — fatal until the key is fixed) or the
121
+ * kill-switch 403 (disabled/epoch-revoked — fatal until the app is
122
+ * re-enabled). Minting STOPS: no retry loop. Clears on process restart;
123
+ * call `resumeMinting()` to clear it sooner (tests, or an operator who
124
+ * just re-enabled the app).
125
+ */
126
+ export class AppMintHaltedError extends Error {
127
+ code = "app_mint_halted";
128
+ reason;
129
+ constructor(reason, detail) {
130
+ super(`Minting halted (${reason}): ${detail}`);
131
+ this.name = "AppMintHaltedError";
132
+ this.reason = reason;
133
+ }
134
+ }
135
+ /** Thrown when no installation row exists locally for an id. */
136
+ export class UnknownInstallationError extends Error {
137
+ code = "unknown_installation";
138
+ constructor(installationId) {
139
+ super(`Unknown installation ${installationId}: no local row (resync or reinstall first).`);
140
+ this.name = "UnknownInstallationError";
141
+ }
142
+ }
143
+ /**
144
+ * Load the app credential: explicit options win, otherwise `APP_SLUG`,
145
+ * `APP_KEY_ID`, `APP_PRIVATE_KEY` from env. The PEM must parse as an RSA
146
+ * private key here, once, so a broken key fails at boot — never mid-mint.
147
+ * Throws `InvalidAppCredentialError` before any fetch.
148
+ */
149
+ export function loadAppCredential(options = {}) {
150
+ const env = options.env ?? process.env;
151
+ const appSlug = (options.appSlug ?? env.APP_SLUG ?? "").trim();
152
+ if (appSlug === "") {
153
+ throw new InvalidAppCredentialError("Missing app slug for `iss`: pass appSlug or set APP_SLUG to the slug registered with Queek.");
154
+ }
155
+ const keyId = (options.keyId ?? env.APP_KEY_ID ?? "").trim();
156
+ if (keyId === "") {
157
+ throw new InvalidAppCredentialError("Missing APP_KEY_ID: set it to the kid registered with Queek (`php artisan app:register --public-key=…`).");
158
+ }
159
+ const privateKeyPem = (options.privateKeyPem ?? env.APP_PRIVATE_KEY ?? "").trim();
160
+ if (privateKeyPem === "") {
161
+ throw new InvalidAppCredentialError("Missing APP_PRIVATE_KEY: set it to the app's PEM-encoded RSA private key.");
162
+ }
163
+ try {
164
+ const key = createPrivateKey(privateKeyPem);
165
+ if (key.asymmetricKeyType !== "rsa") {
166
+ throw new InvalidAppCredentialError(`Invalid APP_PRIVATE_KEY: asymmetric key type is ${key.asymmetricKeyType ?? "unknown"}, need rsa.`);
167
+ }
168
+ }
169
+ catch (error) {
170
+ if (error instanceof InvalidAppCredentialError)
171
+ throw error;
172
+ throw new InvalidAppCredentialError(`Invalid APP_PRIVATE_KEY: not a parseable PEM private key (${error instanceof Error ? error.message : "undecodable"}).`);
173
+ }
174
+ // The PEM stays in this object only; callers must never log it (see
175
+ // `signAppJwt`: the only consumer, and it never emits the key).
176
+ return { appSlug, keyId, privateKeyPem };
177
+ }
178
+ function base64Url(input) {
179
+ return Buffer.from(input).toString("base64url");
180
+ }
181
+ /**
182
+ * Sign an app JWT (RS256, `node:crypto` only): header `{alg RS256, kid}`,
183
+ * claims `{iss = app slug, iat = now − 60 s, exp = iat + 540 s}` — the
184
+ * `exp − iat` window (540 s) stays under the 600 s wire bound. Returns the
185
+ * compact JWT; NEVER logs it or the private key.
186
+ */
187
+ export function signAppJwt(options) {
188
+ const now = Math.floor(options.nowSeconds ?? Date.now() / 1000);
189
+ const iat = now - APP_JWT_SKEW_SECONDS;
190
+ const exp = iat + APP_JWT_TTL_SECONDS;
191
+ const header = base64Url(JSON.stringify({ alg: "RS256", typ: "JWT", kid: options.credential.keyId }));
192
+ const payload = base64Url(JSON.stringify({ iss: options.credential.appSlug, iat, exp }));
193
+ const signingInput = `${header}.${payload}`;
194
+ let signature;
195
+ try {
196
+ const signer = createSign("RSA-SHA256");
197
+ signer.update(signingInput, "utf8");
198
+ signature = signer.sign(options.credential.privateKeyPem);
199
+ }
200
+ catch (error) {
201
+ throw new InvalidAppCredentialError(`Could not sign the app JWT (is APP_PRIVATE_KEY the matching RSA key for kid ${JSON.stringify(options.credential.keyId)}?): ${error instanceof Error ? error.message : "signing failed"}`);
202
+ }
203
+ return `${signingInput}.${signature.toString("base64url")}`;
204
+ }
205
+ //# sourceMappingURL=app-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-auth.js","sourceRoot":"","sources":["../src/app-auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Lean in-process background work for one container: answer 200 first, then
3
+ * keep processing the event in this process (no queue, no Redis, no new
4
+ * infrastructure). A detached task that rejects reports through `onError`
5
+ * (usually the structured logger) instead of crashing the process or
6
+ * vanishing silently.
7
+ *
8
+ * Limits, stated plainly: in-flight work dies with the container (a deploy
9
+ * mid-processing loses it — the SENDER's retry redelivers, which is why
10
+ * every consumer must still dedupe), and two replicas would process the
11
+ * same redelivery twice (dedupe is per-container). Reach for a real queue
12
+ * when replicas or an outbound queue need shared state.
13
+ */
14
+ export interface DetachOptions {
15
+ label: string;
16
+ onError: (error: unknown) => void;
17
+ }
18
+ export declare function detach(promise: Promise<unknown>, options: DetachOptions): void;
19
+ /** How many background tasks are currently running (health/debug only). */
20
+ export declare function backgroundTaskCount(): number;
21
+ /**
22
+ * Jittered per-installation catch-up for one container (S1, SDK 0.2.0):
23
+ * iterate every stored installation and run `forInstallation` (a token
24
+ * proof-call, a resync batch, an inbox poll — whatever the app needs
25
+ * hourly), with:
26
+ *
27
+ * - one uniform `0–maxStartJitterMs` start sleep (default 600 s, matching
28
+ * the mint bucket's /600 divisor so N containers spread over ~10 min),
29
+ * - per-installation error isolation (one failure never stops the run;
30
+ * failures are collected in the returned summary),
31
+ * - at most `concurrency` installations in flight (default 2 — keep it ≤
32
+ * the store pool size; the Postgres store runs max 2),
33
+ * - a 429 from `forInstallation` (a `QueekApiError` with `retryAfterMs`)
34
+ * honored once per installation (sleep, retry once), then recorded as a
35
+ * failure like any other error.
36
+ *
37
+ * Resolves to `{ ok, failed }` — it never throws for an installation's
38
+ * failure (only `listInstallations()` itself failing throws, since there
39
+ * is nothing to iterate).
40
+ */
41
+ export interface CatchupInstallation {
42
+ installationId: string;
43
+ installationPid: string;
44
+ }
45
+ export interface CatchupFailure {
46
+ installationId: string;
47
+ error: unknown;
48
+ }
49
+ export interface CatchupResult {
50
+ ok: string[];
51
+ failed: CatchupFailure[];
52
+ }
53
+ export interface CatchupOptions {
54
+ /** Installations to visit. Defaults to the store's `listInstallations()`. */
55
+ installations?: CatchupInstallation[] | Promise<CatchupInstallation[]>;
56
+ store?: import("./store.js").InstallationStore;
57
+ forInstallation: (installation: CatchupInstallation) => Promise<void>;
58
+ /** Uniform start-jitter ceiling in ms (default 600_000). Set 0 in tests. */
59
+ maxStartJitterMs?: number;
60
+ /** Max in-flight installations (default 2; keep ≤ pool size). */
61
+ concurrency?: number;
62
+ onError?: (failure: CatchupFailure) => void;
63
+ sleep?: (ms: number) => Promise<void>;
64
+ /** Injectable uniform [0,1) for jitter. Defaults to `Math.random`. */
65
+ random?: () => number;
66
+ }
67
+ export declare function runInstallationCatchup(options: CatchupOptions): Promise<CatchupResult>;
68
+ //# sourceMappingURL=background.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"background.d.ts","sourceRoot":"","sources":["../src/background.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACnC;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI,CAS9E;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,MAAM,EAAE,cAAc,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,aAAa,CAAC,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;IACvE,KAAK,CAAC,EAAE,OAAO,YAAY,EAAE,iBAAiB,CAAC;IAC/C,eAAe,EAAE,CAAC,YAAY,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAC5C,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CACvB;AAMD,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA+C5F"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Lean in-process background work for one container: answer 200 first, then
3
+ * keep processing the event in this process (no queue, no Redis, no new
4
+ * infrastructure). A detached task that rejects reports through `onError`
5
+ * (usually the structured logger) instead of crashing the process or
6
+ * vanishing silently.
7
+ *
8
+ * Limits, stated plainly: in-flight work dies with the container (a deploy
9
+ * mid-processing loses it — the SENDER's retry redelivers, which is why
10
+ * every consumer must still dedupe), and two replicas would process the
11
+ * same redelivery twice (dedupe is per-container). Reach for a real queue
12
+ * when replicas or an outbound queue need shared state.
13
+ */
14
+ const inFlight = new Set();
15
+ export function detach(promise, options) {
16
+ inFlight.add(promise);
17
+ promise
18
+ .catch((error) => {
19
+ options.onError(error);
20
+ })
21
+ .finally(() => {
22
+ inFlight.delete(promise);
23
+ });
24
+ }
25
+ /** How many background tasks are currently running (health/debug only). */
26
+ export function backgroundTaskCount() {
27
+ return inFlight.size;
28
+ }
29
+ function defaultSleep(ms) {
30
+ return new Promise((resolve) => setTimeout(resolve, ms));
31
+ }
32
+ export async function runInstallationCatchup(options) {
33
+ const sleep = options.sleep ?? defaultSleep;
34
+ const random = options.random ?? Math.random;
35
+ const maxStartJitterMs = options.maxStartJitterMs ?? 600_000;
36
+ const concurrency = Math.max(1, Math.floor(options.concurrency ?? 2));
37
+ if (maxStartJitterMs > 0) {
38
+ await sleep(Math.floor(random() * maxStartJitterMs));
39
+ }
40
+ const installations = options.installations ?? (await options.store?.listInstallations()) ?? [];
41
+ const resolved = await installations;
42
+ const result = { ok: [], failed: [] };
43
+ let next = 0;
44
+ async function worker() {
45
+ while (next < resolved.length) {
46
+ const installation = resolved[next];
47
+ next += 1;
48
+ try {
49
+ try {
50
+ await options.forInstallation(installation);
51
+ }
52
+ catch (error) {
53
+ // Honor ONE 429 per installation (the server's Retry-After when
54
+ // present, else a 1 s breather), then treat a repeat like any
55
+ // other failure.
56
+ const fields = error !== null && typeof error === "object" ? error : null;
57
+ if (fields?.isRateLimited === true) {
58
+ const retryAfterMs = fields.retryAfterMs;
59
+ await sleep(typeof retryAfterMs === "number" ? retryAfterMs : 1_000);
60
+ await options.forInstallation(installation);
61
+ }
62
+ else {
63
+ throw error;
64
+ }
65
+ }
66
+ result.ok.push(installation.installationId);
67
+ }
68
+ catch (error) {
69
+ const failure = { installationId: installation.installationId, error };
70
+ result.failed.push(failure);
71
+ options.onError?.(failure);
72
+ }
73
+ }
74
+ }
75
+ await Promise.all(Array.from({ length: Math.min(concurrency, resolved.length) }, () => worker()));
76
+ return result;
77
+ }
78
+ //# sourceMappingURL=background.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"background.js","sourceRoot":"","sources":["../src/background.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ import type { paths } from "./merchant-schema.js";
2
+ /**
3
+ * Typed fetch client over Queek's public Merchant API.
4
+ *
5
+ * - Base URL comes from the install handoff (`api_base`). The backend sends
6
+ * the BARE store host (`AppInstallService::installPayload()` passes
7
+ * `rtrim(config('app.url'))` verbatim, e.g. `https://api.usequeek.com`);
8
+ * the client appends `/api/v1/merchant` when it is absent, so a bare host
9
+ * and a full merchant base both work. Never hardcoded otherwise, so test
10
+ * stores ride their own host.
11
+ * - `apiBase` is validated at construction — https only, no credentials,
12
+ * host on the allowlist — and throws BEFORE any fetch, so a
13
+ * signed-but-stale or attacker-influenced handoff can never point
14
+ * `X-Client-Key` at an arbitrary host.
15
+ * - Auth is the installation credential only: `X-Client-Key: sk_…`
16
+ * (server-side; the key is bound to ONE store, so no vendor id is sent).
17
+ * - Writes (POST/PUT/PATCH/DELETE) carry an `Idempotency-Key` (generated
18
+ * when the caller does not supply one) because Queek's `idempotent`
19
+ * middleware replays same-key+same-body writes instead of duplicating
20
+ * them. GET/HEAD never send one.
21
+ * - Errors are thrown as `QueekApiError`, typed from Queek's error envelope
22
+ * (`{error: {code, message, field?, errors?, doc_url?, request_id?}}` plus
23
+ * the legacy top-level `error_code`/`message`/`errors` keys — the contract
24
+ * is additive, so both are read). Switch on `code`, never on `message`.
25
+ * - 429s expose `retryAfterMs` parsed from `Retry-After` (seconds or
26
+ * HTTP-date). Plain `request()` never retries; `requestWithRetry()`
27
+ * retries 429s and network errors with the SAME idempotency key.
28
+ *
29
+ * Typed surface: `getStore()` is typed from the generated Merchant API
30
+ * schema (`openapi/merchant.json` → `merchant-schema.ts` via
31
+ * `pnpm gen:merchant`); `request()` is the generic escape hatch.
32
+ */
33
+ export type MerchantPaths = paths;
34
+ /** 200 JSON body of `GET <P>` for operations shaped `{responses: {200: {content: {"application/json": T}}}}`. */
35
+ export type OperationResponse<P extends keyof paths, M extends keyof paths[P]> = paths[P][M] extends {
36
+ responses: {
37
+ 200: {
38
+ content: {
39
+ "application/json": infer T;
40
+ };
41
+ };
42
+ };
43
+ } ? T : unknown;
44
+ export type StoreProfile = OperationResponse<"/store", "get">;
45
+ /** Path of the public Merchant API below the store host. */
46
+ export declare const MERCHANT_API_PATH = "/api/v1/merchant";
47
+ /** Hosts a handoff `api_base` may point at without extra configuration. */
48
+ export declare const DEFAULT_API_HOSTS: string[];
49
+ /** Thrown at client construction when `apiBase` fails validation — before any fetch. */
50
+ export declare class InvalidApiBaseError extends Error {
51
+ readonly code = "invalid_api_base";
52
+ constructor(message: string);
53
+ }
54
+ /**
55
+ * Extra allowed `apiBase` hosts for test/local backends, from
56
+ * `QUEEK_API_HOSTS` (comma-separated bare hostnames). Parsed strictly: any
57
+ * entry that is not a bare hostname throws — nothing is silently skipped.
58
+ */
59
+ export declare function apiHostsFromEnv(env?: NodeJS.ProcessEnv): string[];
60
+ /**
61
+ * DEV-ONLY extra allowed hosts (e.g. an HTTPS tunnel to a local backend),
62
+ * from `QUEEK_DEV_API_HOSTS`. REFUSED when NODE_ENV=production: a dev
63
+ * tunnel host must never be allowlisted in production, even by accident.
64
+ * Throws at client construction, before any fetch.
65
+ */
66
+ export declare function devApiHostsFromEnv(env?: NodeJS.ProcessEnv): string[];
67
+ /**
68
+ * Validate the handoff `apiBase` and normalize it to the full merchant base.
69
+ * Throws `InvalidApiBaseError` on: unparseable URL, non-https scheme,
70
+ * embedded credentials, an explicit port on a DEFAULT host (production
71
+ * hosts are 443 — ports ride only on dev/test hosts), or a host outside
72
+ * `DEFAULT_API_HOSTS + allowedApiHosts + QUEEK_API_HOSTS +
73
+ * QUEEK_DEV_API_HOSTS`.
74
+ */
75
+ export declare function resolveApiBase(raw: string, allowedApiHosts?: string[]): string;
76
+ export interface QueekClientOptions {
77
+ /** Handoff `api_base` (bare store host or full merchant base). Validated at construction. */
78
+ apiBase: string;
79
+ apiKey: string;
80
+ fetchImpl?: typeof fetch;
81
+ /** Sent as User-Agent. Defaults to `queek-app/1.0`. */
82
+ userAgent?: string;
83
+ /** Extra allowed `apiBase` hosts (test/local backends). Production default always applies. */
84
+ allowedApiHosts?: string[];
85
+ }
86
+ export interface RequestOptions {
87
+ query?: Record<string, string | number | boolean | undefined>;
88
+ body?: unknown;
89
+ /** Write idempotency (POST/PUT/PATCH/DELETE). Generated when omitted. */
90
+ idempotencyKey?: string;
91
+ requestId?: string;
92
+ signal?: AbortSignal;
93
+ }
94
+ export declare function newIdempotencyKey(): string;
95
+ export interface QueekErrorDetails {
96
+ status: number;
97
+ code: string;
98
+ message: string;
99
+ field?: string;
100
+ errors?: unknown;
101
+ docUrl?: string;
102
+ requestId?: string;
103
+ retryAfterMs?: number;
104
+ }
105
+ export declare class QueekApiError extends Error {
106
+ readonly status: number;
107
+ readonly code: string;
108
+ readonly field?: string;
109
+ readonly errors?: unknown;
110
+ readonly docUrl?: string;
111
+ readonly requestId?: string;
112
+ /** Set on 429s (and only there): how long to wait before retrying. */
113
+ readonly retryAfterMs?: number;
114
+ constructor(details: QueekErrorDetails);
115
+ get isRateLimited(): boolean;
116
+ get isAuthFailure(): boolean;
117
+ }
118
+ /**
119
+ * Build a `QueekApiError` from a status + parsed body + headers. Shared
120
+ * with the installation-token mint path (`tokens.ts`), so the merchant and
121
+ * app-credential endpoints parse errors identically.
122
+ */
123
+ export declare function queekApiErrorFromResponse(status: number, body: unknown, headers: Headers): QueekApiError;
124
+ export interface RetryOptions {
125
+ /** Total attempts including the first (default 3). Must be >= 1. */
126
+ maxAttempts?: number;
127
+ /** Base backoff between attempts in ms (default 250); doubles per attempt. */
128
+ baseDelayMs?: number;
129
+ /** Backoff ceiling in ms (default 5000). A 429 `Retry-After` can exceed it. */
130
+ maxDelayMs?: number;
131
+ /** Injectable clock for tests. Defaults to a real setTimeout sleep. */
132
+ sleep?: (ms: number) => Promise<void>;
133
+ }
134
+ export interface QueekClient {
135
+ /** `GET /store` — the handed-off key proving itself. Typed from the Merchant API schema. */
136
+ getStore(signal?: AbortSignal): Promise<StoreProfile>;
137
+ /** Generic typed escape hatch: `request<T>("GET", "/orders", { query })`. Never retries. */
138
+ request<T>(method: string, path: string, options?: RequestOptions): Promise<T>;
139
+ /**
140
+ * `request()` with bounded retries on 429s and network errors. The SAME
141
+ * idempotency key is reused across attempts (generated once when the
142
+ * caller does not supply one), so a retry can never duplicate a write;
143
+ * a 429 `Retry-After` is honoured over the backoff schedule.
144
+ */
145
+ requestWithRetry<T>(method: string, path: string, options?: RequestOptions & RetryOptions): Promise<T>;
146
+ }
147
+ export declare function createQueekClient(clientOptions: QueekClientOptions): QueekClient;
148
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}