@usequeek/app-sdk 0.6.1 → 0.7.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 +1 -1
- package/README.md +339 -330
- package/dist/app-auth.d.ts +15 -17
- package/dist/app-auth.js +20 -23
- package/dist/auth-fetch.d.ts +14 -5
- package/dist/auth-fetch.js +15 -6
- package/dist/background.d.ts +10 -2
- package/dist/background.js +9 -1
- package/dist/browser.d.ts +2 -3
- package/dist/browser.js +2 -3
- package/dist/client.d.ts +138 -16
- package/dist/client.js +200 -43
- package/dist/collected.d.ts +13 -15
- package/dist/collected.js +4 -5
- package/dist/crypto.d.ts +11 -1
- package/dist/crypto.js +25 -10
- package/dist/delivery.d.ts +22 -9
- package/dist/delivery.js +26 -21
- package/dist/frame.d.ts +11 -12
- package/dist/frame.js +11 -12
- package/dist/handoff.d.ts +35 -39
- package/dist/handoff.js +9 -10
- package/dist/hono.d.ts +3 -4
- package/dist/hono.js +3 -4
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/install-handlers.d.ts +28 -9
- package/dist/install-handlers.js +48 -18
- package/dist/logger.d.ts +7 -1
- package/dist/logger.js +8 -2
- package/dist/merchant-schema.d.ts +15678 -6236
- package/dist/merchant-schema.js +0 -1
- package/dist/proxy.d.ts +21 -24
- package/dist/proxy.js +20 -22
- package/dist/react.d.ts +10 -2
- package/dist/react.js +11 -3
- package/dist/resync.d.ts +15 -5
- package/dist/resync.js +22 -20
- package/dist/scopes.d.ts +36 -27
- package/dist/scopes.js +37 -40
- package/dist/server.d.ts +0 -1
- package/dist/server.js +0 -1
- package/dist/session.d.ts +35 -17
- package/dist/session.js +32 -15
- package/dist/signatures.d.ts +31 -5
- package/dist/signatures.js +36 -9
- package/dist/store.d.ts +26 -26
- package/dist/store.js +16 -20
- package/dist/theme.d.ts +1 -2
- package/dist/theme.js +1 -2
- package/dist/tokens.d.ts +18 -10
- package/dist/tokens.js +32 -57
- package/dist/webhooks.d.ts +30 -6
- package/dist/webhooks.js +27 -10
- package/package.json +21 -4
- package/dist/app-auth.d.ts.map +0 -1
- package/dist/app-auth.js.map +0 -1
- package/dist/auth-fetch.d.ts.map +0 -1
- package/dist/auth-fetch.js.map +0 -1
- package/dist/background.d.ts.map +0 -1
- package/dist/background.js.map +0 -1
- package/dist/browser.d.ts.map +0 -1
- package/dist/browser.js.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/collected.d.ts.map +0 -1
- package/dist/collected.js.map +0 -1
- package/dist/crypto.d.ts.map +0 -1
- package/dist/crypto.js.map +0 -1
- package/dist/delivery.d.ts.map +0 -1
- package/dist/delivery.js.map +0 -1
- package/dist/frame.d.ts.map +0 -1
- package/dist/frame.js.map +0 -1
- package/dist/handoff.d.ts.map +0 -1
- package/dist/handoff.js.map +0 -1
- package/dist/hono.d.ts.map +0 -1
- package/dist/hono.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/install-handlers.d.ts.map +0 -1
- package/dist/install-handlers.js.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/merchant-schema.d.ts.map +0 -1
- package/dist/merchant-schema.js.map +0 -1
- package/dist/proxy.d.ts.map +0 -1
- package/dist/proxy.js.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/react.js.map +0 -1
- package/dist/resync.d.ts.map +0 -1
- package/dist/resync.js.map +0 -1
- package/dist/scopes.d.ts.map +0 -1
- package/dist/scopes.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js.map +0 -1
- package/dist/signatures.d.ts.map +0 -1
- package/dist/signatures.js.map +0 -1
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/theme.d.ts.map +0 -1
- package/dist/theme.js.map +0 -1
- package/dist/tokens.d.ts.map +0 -1
- package/dist/tokens.js.map +0 -1
- package/dist/webhooks.d.ts.map +0 -1
- package/dist/webhooks.js.map +0 -1
package/dist/frame.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Framed-page bridge client, v1
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Framed-page bridge client, v1: the browser-safe half of the dashboard
|
|
3
|
+
* bridge. Carries NO secret and performs NO verification — it only shapes
|
|
4
|
+
* and addresses messages. The dashboard enforces origin+source binding and
|
|
5
|
+
* mints tokens; the app's server verifies them via `./server`
|
|
6
|
+
* (`verifySessionToken`).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* `
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Compatible with the v0 protocol (`ready`/`token`/`resize`/`resize-ack`/
|
|
9
|
+
* `ack`, sources `queek-app`/`queek-merchant`, exact-origin postMessage):
|
|
10
|
+
* v1 only ADDS optional fields and new message types, and both directions
|
|
11
|
+
* ignore unknown types.
|
|
12
12
|
*
|
|
13
13
|
* DOM-free by design (structural minimal types): the app passes its own
|
|
14
14
|
* window/parent in, so this module needs no DOM lib and stays import-safe
|
|
@@ -449,7 +449,7 @@ export function sendReady(dashboardOrigin, target, options) {
|
|
|
449
449
|
export function sendResize(dashboardOrigin, target, height) {
|
|
450
450
|
sendBridgeMessage(dashboardOrigin, target, { source: APP_SOURCE, type: "resize", height });
|
|
451
451
|
}
|
|
452
|
-
/** Delivery confirmation
|
|
452
|
+
/** Delivery confirmation carrying no state (accepted, no further effect). */
|
|
453
453
|
export function sendAck(dashboardOrigin, target) {
|
|
454
454
|
sendBridgeMessage(dashboardOrigin, target, { source: APP_SOURCE, type: "ack" });
|
|
455
455
|
}
|
|
@@ -483,7 +483,7 @@ export function sendToast(dashboardOrigin, target, message, options) {
|
|
|
483
483
|
export function sendSaveBar(dashboardOrigin, target, state) {
|
|
484
484
|
sendBridgeMessage(dashboardOrigin, target, { source: APP_SOURCE, type: "save-bar", state });
|
|
485
485
|
}
|
|
486
|
-
/** Report an in-app move so the dashboard can mirror it in its URL
|
|
486
|
+
/** Report an in-app move so the dashboard can mirror it in its URL. */
|
|
487
487
|
export function sendNavigated(dashboardOrigin, target, path) {
|
|
488
488
|
sendBridgeMessage(dashboardOrigin, target, { source: APP_SOURCE, type: "navigated", path });
|
|
489
489
|
}
|
|
@@ -728,4 +728,3 @@ export function clipOutbound(message) {
|
|
|
728
728
|
}
|
|
729
729
|
}
|
|
730
730
|
}
|
|
731
|
-
//# sourceMappingURL=frame.js.map
|
package/dist/handoff.d.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Typed payloads for Queek's server-to-server install handoff.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* (`installPayload()`, `uninstallPayload()`, `settingsPayload()`,
|
|
6
|
-
* `handoffBody()`). The signed body envelope is:
|
|
4
|
+
* The signed body envelope is:
|
|
7
5
|
*
|
|
8
6
|
* { id, type, api_version: "v1", created_at, data }
|
|
9
7
|
*
|
|
10
8
|
* `type` is `app/installed` | `app/uninstalled` | `app/settings_updated`
|
|
11
|
-
* | `app/resync` | `app/scopes_update`. The handoff POST carries the
|
|
12
|
-
* (`webhook-id`, `webhook-timestamp`,
|
|
13
|
-
* APP's signing secret (`whsec_…`,
|
|
9
|
+
* | `app/resync` | `app/scopes_update`. The handoff POST carries the
|
|
10
|
+
* Standard Webhooks headers (`webhook-id`, `webhook-timestamp`,
|
|
11
|
+
* `webhook-signature`) signed with the APP's signing secret (`whsec_…`,
|
|
12
|
+
* minted when the app is registered on the Queek Developer page), NOT the
|
|
14
13
|
* per-installation webhook secret. The per-installation `webhook_secret`
|
|
15
14
|
* arrives INSIDE the install payload (`data.webhook_secret`) — once — so
|
|
16
15
|
* the app can verify the topic events it is about to receive.
|
|
17
16
|
*
|
|
18
|
-
* `app/resync` is the installation resync handoff (
|
|
19
|
-
* `
|
|
20
|
-
* `settings_url` falling back to `install_url`): the install-shaped data
|
|
17
|
+
* `app/resync` is the installation resync handoff (delivered to the app's
|
|
18
|
+
* `settings_url`, falling back to `install_url`): the install-shaped data
|
|
21
19
|
* plus the resync-only `secret_rotated` flag, over the same signed channel
|
|
22
20
|
* with the same verification rules. Both the `/install` and `/settings`
|
|
23
21
|
* handlers accept it.
|
|
22
|
+
*
|
|
23
|
+
* See https://docs.usequeek.com/docs/apps/sdk
|
|
24
24
|
*/
|
|
25
25
|
export declare const INSTALL_EVENT = "app/installed";
|
|
26
26
|
export declare const UNINSTALL_EVENT = "app/uninstalled";
|
|
@@ -43,45 +43,43 @@ export interface InstallData {
|
|
|
43
43
|
/** The Merchant API base for this store (e.g. https://api.usequeek.com/api/v1/merchant). */
|
|
44
44
|
api_base: string;
|
|
45
45
|
/**
|
|
46
|
-
* No store-callable credential crosses the handoff
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
46
|
+
* No store-callable credential crosses the handoff: the app mints
|
|
47
|
+
* short-lived installation tokens with its asymmetric app key
|
|
48
|
+
* (`acquireToken()`) and caches them encrypted in its own database. The
|
|
49
|
+
* same envelope redelivers `webhook_secret` + the non-secret settings
|
|
50
|
+
* snapshot on resync.
|
|
51
51
|
*/
|
|
52
52
|
scopes: string[];
|
|
53
53
|
settings: Record<string, unknown>;
|
|
54
54
|
/** The installation endpoint's `whsec_…` secret, handed over ONCE per rotation. */
|
|
55
55
|
webhook_secret: string | null;
|
|
56
56
|
/**
|
|
57
|
-
* The installation's `whsec_…` proxy secret
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
57
|
+
* The installation's `whsec_…` proxy secret, handed over ONCE per
|
|
58
|
+
* install/resync over the signed channel. Queek signs storefront proxy
|
|
59
|
+
* reads with it (see `proxy.ts`), and an app can sign claims back to
|
|
60
|
+
* Queek with it (for example slot-claims); Queek verifies the HMAC
|
|
61
|
+
* against the same installation secret. Null for apps without a proxy —
|
|
62
|
+
* the key's absence is the signal.
|
|
63
63
|
*/
|
|
64
64
|
proxy_secret: string | null;
|
|
65
65
|
/**
|
|
66
|
-
* The installation's `embsec_…` embed secret
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* without a merchant page.
|
|
66
|
+
* The installation's `embsec_…` embed secret, handed over on
|
|
67
|
+
* install/resync: the HS256 key of the dashboard session tokens its
|
|
68
|
+
* embedded merchant page receives (verify with `@usequeek/app-sdk/server`
|
|
69
|
+
* `verifySessionToken`). Null for apps without a merchant page.
|
|
71
70
|
*/
|
|
72
71
|
embed_secret?: string | null;
|
|
73
72
|
/**
|
|
74
73
|
* The app's own id, exactly as the session token signs it (`app_id`
|
|
75
|
-
* claim) — the verifier binds it.
|
|
76
|
-
* `AppInstallService::installPayload()` / `resyncPayload()`.
|
|
74
|
+
* claim) — the verifier binds it.
|
|
77
75
|
*/
|
|
78
76
|
app_id?: string | null;
|
|
79
77
|
webhook_url: string | null;
|
|
80
78
|
webhook_topics: string[];
|
|
81
79
|
/**
|
|
82
|
-
* Resync-only
|
|
83
|
-
*
|
|
84
|
-
*
|
|
80
|
+
* Resync-only: whether the delivery rotated the endpoint secret
|
|
81
|
+
* (`webhook_secret` holds the new secret when true, null when the
|
|
82
|
+
* installation has no webhook endpoint). Absent on install.
|
|
85
83
|
*/
|
|
86
84
|
secret_rotated?: boolean | null;
|
|
87
85
|
}
|
|
@@ -111,20 +109,19 @@ export type SettingsEnvelope = HandoffEnvelope<typeof SETTINGS_EVENT, SettingsDa
|
|
|
111
109
|
*/
|
|
112
110
|
export type ResyncEnvelope = HandoffEnvelope<typeof RESYNC_EVENT, InstallData>;
|
|
113
111
|
/**
|
|
114
|
-
* The installation `p_id`
|
|
115
|
-
* scopes_update handoff carries ONLY the `p_id` (never the UUID
|
|
116
|
-
*
|
|
117
|
-
*
|
|
112
|
+
* The installation `p_id` Queek addresses a handoff to. The
|
|
113
|
+
* scopes_update handoff carries ONLY the `p_id` (never the UUID), so `id`
|
|
114
|
+
* is optional here: present on install-shaped payloads, absent on
|
|
115
|
+
* `app/scopes_update`.
|
|
118
116
|
*/
|
|
119
117
|
export interface HandoffPidRef {
|
|
120
|
-
/**
|
|
118
|
+
/** A string on the wire; the SDK normalises via `normalisePid`, so a JSON integer still matches. */
|
|
121
119
|
p_id: string | number;
|
|
122
120
|
id?: string;
|
|
123
121
|
}
|
|
124
122
|
/**
|
|
125
|
-
* The grant-change handoff (
|
|
126
|
-
*
|
|
127
|
-
* the same signed channel): the installation `p_id` + the EFFECTIVE grant
|
|
123
|
+
* The grant-change handoff (sent per grant change over the same signed
|
|
124
|
+
* channel): the installation `p_id` + the EFFECTIVE grant
|
|
128
125
|
* (grant ∩ tracked manifest — what the installation's tokens now carry,
|
|
129
126
|
* byte for byte). Carries no settings and no secrets: the app refreshes
|
|
130
127
|
* its cached grant from `scopes` and drops its cached token when the
|
|
@@ -140,4 +137,3 @@ export interface ScopesUpdateData {
|
|
|
140
137
|
}
|
|
141
138
|
export type ScopesUpdateEnvelope = HandoffEnvelope<typeof SCOPES_UPDATE_EVENT, ScopesUpdateData>;
|
|
142
139
|
export type HandoffEnvelopeAny = InstallEnvelope | UninstallEnvelope | SettingsEnvelope | ResyncEnvelope | ScopesUpdateEnvelope;
|
|
143
|
-
//# sourceMappingURL=handoff.d.ts.map
|
package/dist/handoff.js
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Typed payloads for Queek's server-to-server install handoff.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* (`installPayload()`, `uninstallPayload()`, `settingsPayload()`,
|
|
6
|
-
* `handoffBody()`). The signed body envelope is:
|
|
4
|
+
* The signed body envelope is:
|
|
7
5
|
*
|
|
8
6
|
* { id, type, api_version: "v1", created_at, data }
|
|
9
7
|
*
|
|
10
8
|
* `type` is `app/installed` | `app/uninstalled` | `app/settings_updated`
|
|
11
|
-
* | `app/resync` | `app/scopes_update`. The handoff POST carries the
|
|
12
|
-
* (`webhook-id`, `webhook-timestamp`,
|
|
13
|
-
* APP's signing secret (`whsec_…`,
|
|
9
|
+
* | `app/resync` | `app/scopes_update`. The handoff POST carries the
|
|
10
|
+
* Standard Webhooks headers (`webhook-id`, `webhook-timestamp`,
|
|
11
|
+
* `webhook-signature`) signed with the APP's signing secret (`whsec_…`,
|
|
12
|
+
* minted when the app is registered on the Queek Developer page), NOT the
|
|
14
13
|
* per-installation webhook secret. The per-installation `webhook_secret`
|
|
15
14
|
* arrives INSIDE the install payload (`data.webhook_secret`) — once — so
|
|
16
15
|
* the app can verify the topic events it is about to receive.
|
|
17
16
|
*
|
|
18
|
-
* `app/resync` is the installation resync handoff (
|
|
19
|
-
* `
|
|
20
|
-
* `settings_url` falling back to `install_url`): the install-shaped data
|
|
17
|
+
* `app/resync` is the installation resync handoff (delivered to the app's
|
|
18
|
+
* `settings_url`, falling back to `install_url`): the install-shaped data
|
|
21
19
|
* plus the resync-only `secret_rotated` flag, over the same signed channel
|
|
22
20
|
* with the same verification rules. Both the `/install` and `/settings`
|
|
23
21
|
* handlers accept it.
|
|
22
|
+
*
|
|
23
|
+
* See https://docs.usequeek.com/docs/apps/sdk
|
|
24
24
|
*/
|
|
25
25
|
export const INSTALL_EVENT = "app/installed";
|
|
26
26
|
export const UNINSTALL_EVENT = "app/uninstalled";
|
|
27
27
|
export const SETTINGS_EVENT = "app/settings_updated";
|
|
28
28
|
export const RESYNC_EVENT = "app/resync";
|
|
29
29
|
export const SCOPES_UPDATE_EVENT = "app/scopes_update";
|
|
30
|
-
//# sourceMappingURL=handoff.js.map
|
package/dist/hono.d.ts
CHANGED
|
@@ -19,14 +19,13 @@ export declare function createInstallHandlers(options: InstallHandlerOptions): H
|
|
|
19
19
|
/** Mount the signed topic-delivery receiver (`POST /`). */
|
|
20
20
|
export declare function createWebhookHandler(options: WebhookHandlerOptions): Hono;
|
|
21
21
|
/**
|
|
22
|
-
* Mount the signed app-proxy reader (`GET
|
|
23
|
-
*
|
|
22
|
+
* Mount the signed app-proxy reader (`GET` only, proxy reads are
|
|
23
|
+
* read-only) — a thin wrapper built ONLY on the layer-2 Web-standard handler
|
|
24
24
|
* (`handleProxyRequest` in the root entry). Mount it where the proxy lives
|
|
25
25
|
* (`app.route("/proxy", createProxyHandler(...))`); `options.path` stays
|
|
26
|
-
* the Queek-side canonical path (`/apps/<subpath>/<rest>`)
|
|
26
|
+
* the Queek-side canonical path (`/apps/<subpath>/<rest>`) Queek
|
|
27
27
|
* signed, and `onVerified` owns the shopper-facing body.
|
|
28
28
|
*/
|
|
29
29
|
export declare function createProxyHandler(options: ProxyRequestOptions & {
|
|
30
30
|
onVerified: ProxyResponder;
|
|
31
31
|
}): Hono;
|
|
32
|
-
//# sourceMappingURL=hono.d.ts.map
|
package/dist/hono.js
CHANGED
|
@@ -26,11 +26,11 @@ export function createWebhookHandler(options) {
|
|
|
26
26
|
return app;
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
* Mount the signed app-proxy reader (`GET
|
|
30
|
-
*
|
|
29
|
+
* Mount the signed app-proxy reader (`GET` only, proxy reads are
|
|
30
|
+
* read-only) — a thin wrapper built ONLY on the layer-2 Web-standard handler
|
|
31
31
|
* (`handleProxyRequest` in the root entry). Mount it where the proxy lives
|
|
32
32
|
* (`app.route("/proxy", createProxyHandler(...))`); `options.path` stays
|
|
33
|
-
* the Queek-side canonical path (`/apps/<subpath>/<rest>`)
|
|
33
|
+
* the Queek-side canonical path (`/apps/<subpath>/<rest>`) Queek
|
|
34
34
|
* signed, and `onVerified` owns the shopper-facing body.
|
|
35
35
|
*/
|
|
36
36
|
export function createProxyHandler(options) {
|
|
@@ -39,4 +39,3 @@ export function createProxyHandler(options) {
|
|
|
39
39
|
app.get("*", (c) => handleProxyRequest(c.req.raw, requestOptions, onVerified));
|
|
40
40
|
return app;
|
|
41
41
|
}
|
|
42
|
-
//# sourceMappingURL=hono.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
export { API_KEY_EXPIRED_CODE, API_KEY_REVOKED_CODE, APP_INSTALLATION_GONE_CODE, APP_INSTALLATION_PENDING_CODE, APP_JWT_SKEW_SECONDS, APP_JWT_TTL_SECONDS, APP_PRIVATE_KEY_FORMS, APP_TOKEN_REVOKED_CODE, type AppCredential, AppMintHaltedError, decodePrivateKeyInput, INSUFFICIENT_SCOPE_CODE, INVALID_CLIENT_CODE, INVALID_CLIENT_KEY_CODE, InvalidAppCredentialError, isAppTokenRevoked, isInstallationGone, isInstallationPending, isInsufficientScope, isInvalidClient, isResyncCooldown, isTokenRefusal, loadAppCredential, MAX_MINT_ATTEMPTS, MINT_BACKOFF_BASE_MS, MINT_BACKOFF_MAX_MS, RESYNC_COOLDOWN_CODE, RETRY_JITTER_MAX_MS, type SignAppJwtOptions, signAppJwt, TOKEN_VALIDITY_SKEW_SECONDS, TOO_MANY_REQUESTS_CODE, UnknownInstallationError, } from "./app-auth.js";
|
|
12
12
|
export { type AuthFetchOptions, BRIDGE_TOKEN_TIMEOUT_MS, type InstalledAuth, installAuthFetch, LAUNCH_TOKEN_PARAM, readLaunchToken, stripLaunchToken, } from "./auth-fetch.js";
|
|
13
13
|
export { backgroundTaskCount, type CatchupFailure, type CatchupInstallation, type CatchupOptions, type CatchupResult, type DetachOptions, detach, runInstallationCatchup, } from "./background.js";
|
|
14
|
-
export { apiHostsFromEnv, createQueekClient, DEFAULT_API_HOSTS, devApiHostsFromEnv, InvalidApiBaseError, isWriteMethod, MERCHANT_API_PATH, type MerchantPaths, newIdempotencyKey, type OperationResponse, QueekApiError, type QueekClient, type QueekClientOptions, type QueekErrorDetails, queekApiErrorFromResponse, type RequestOptions, type ResolvedPaths, type RetryOptions, resolveApiBase, type StoreProfile, } from "./client.js";
|
|
14
|
+
export { apiHostsFromEnv, type CursorPage, createQueekClient, DEFAULT_API_HOSTS, DEFAULT_MAX_RETRY_AFTER_MS, DEFAULT_REQUEST_TIMEOUT_MS, devApiHostsFromEnv, InvalidApiBaseError, isWriteMethod, MERCHANT_API_PATH, type MerchantPaths, newIdempotencyKey, type OperationResponse, type PaginateOptions, paginate, QueekApiError, type QueekClient, type QueekClientOptions, type QueekErrorDetails, queekApiErrorFromResponse, type RequestOptions, type ResolvedPaths, type RetryOptions, resolveApiBase, type StoreProfile, } from "./client.js";
|
|
15
15
|
export { type AlertBody, type AlertSeverity, type CollectedDefinition, type CollectedDefinitionCreate, type CollectedDefinitionUpdate, type CollectedField, collectedDefinitions, createRecord, type MerchantAlert, type SetupNotice, type SetupNoticeBody, type SetupNoticeItem, type SetupNoticeStatus, type SuccessEnvelope, sendAlert, setSetupNotice, } from "./collected.js";
|
|
16
16
|
export { decryptSecret, encryptSecret, parseStoreKey, storeKeyFingerprint, } from "./crypto.js";
|
|
17
17
|
export { type CoreDelivery, type CoreHeaders, type DeliveryResult, decodeBody, type InstallDelivery, readHeader, } from "./delivery.js";
|
|
@@ -27,4 +27,3 @@ export { createInstallationStore, createPostgresPool, defaultClearCachedTokenIfM
|
|
|
27
27
|
export { applyTheme, getThemeModeFromUrl, installThemeListener, rememberThemeMode, THEME_PARAM, THEME_STORAGE_KEY, type ThemeDocument, type ThemeDocumentElement, type ThemeListenerOptions, type ThemeStorage, themeBootstrapScript, } from "./theme.js";
|
|
28
28
|
export { APP_API_PATH, AppTokenProvider, type AppTokens, createAppTokenProvider, createInstallationClient, type InstallationClientOptions, mintPath, resolveAppApiBase, type TokenProviderOptions, } from "./tokens.js";
|
|
29
29
|
export { handleWebhookDelivery, handleWebhookRequest, QUEEK_TOPIC_HEADER, type QueekWebhookEnvelope, type SecretResolution, type WebhookHandlerContext, type WebhookHandlerFn, type WebhookHandlerOptions, } from "./webhooks.js";
|
|
30
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
export { API_KEY_EXPIRED_CODE, API_KEY_REVOKED_CODE, APP_INSTALLATION_GONE_CODE, APP_INSTALLATION_PENDING_CODE, APP_JWT_SKEW_SECONDS, APP_JWT_TTL_SECONDS, APP_PRIVATE_KEY_FORMS, APP_TOKEN_REVOKED_CODE, AppMintHaltedError, decodePrivateKeyInput, INSUFFICIENT_SCOPE_CODE, INVALID_CLIENT_CODE, INVALID_CLIENT_KEY_CODE, InvalidAppCredentialError, isAppTokenRevoked, isInstallationGone, isInstallationPending, isInsufficientScope, isInvalidClient, isResyncCooldown, isTokenRefusal, loadAppCredential, MAX_MINT_ATTEMPTS, MINT_BACKOFF_BASE_MS, MINT_BACKOFF_MAX_MS, RESYNC_COOLDOWN_CODE, RETRY_JITTER_MAX_MS, signAppJwt, TOKEN_VALIDITY_SKEW_SECONDS, TOO_MANY_REQUESTS_CODE, UnknownInstallationError, } from "./app-auth.js";
|
|
12
12
|
export { BRIDGE_TOKEN_TIMEOUT_MS, installAuthFetch, LAUNCH_TOKEN_PARAM, readLaunchToken, stripLaunchToken, } from "./auth-fetch.js";
|
|
13
13
|
export { backgroundTaskCount, detach, runInstallationCatchup, } from "./background.js";
|
|
14
|
-
export { apiHostsFromEnv, createQueekClient, DEFAULT_API_HOSTS, devApiHostsFromEnv, InvalidApiBaseError, isWriteMethod, MERCHANT_API_PATH, newIdempotencyKey, QueekApiError, queekApiErrorFromResponse, resolveApiBase, } from "./client.js";
|
|
14
|
+
export { apiHostsFromEnv, createQueekClient, DEFAULT_API_HOSTS, DEFAULT_MAX_RETRY_AFTER_MS, DEFAULT_REQUEST_TIMEOUT_MS, devApiHostsFromEnv, InvalidApiBaseError, isWriteMethod, MERCHANT_API_PATH, newIdempotencyKey, paginate, QueekApiError, queekApiErrorFromResponse, resolveApiBase, } from "./client.js";
|
|
15
15
|
export { collectedDefinitions, createRecord, sendAlert, setSetupNotice, } from "./collected.js";
|
|
16
16
|
export { decryptSecret, encryptSecret, parseStoreKey, storeKeyFingerprint, } from "./crypto.js";
|
|
17
17
|
export { decodeBody, readHeader, } from "./delivery.js";
|
|
@@ -27,4 +27,3 @@ export { createInstallationStore, createPostgresPool, defaultClearCachedTokenIfM
|
|
|
27
27
|
export { applyTheme, getThemeModeFromUrl, installThemeListener, rememberThemeMode, THEME_PARAM, THEME_STORAGE_KEY, themeBootstrapScript, } from "./theme.js";
|
|
28
28
|
export { APP_API_PATH, AppTokenProvider, createAppTokenProvider, createInstallationClient, mintPath, resolveAppApiBase, } from "./tokens.js";
|
|
29
29
|
export { handleWebhookDelivery, handleWebhookRequest, QUEEK_TOPIC_HEADER, } from "./webhooks.js";
|
|
30
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { type DeliveryResult, type InstallDelivery } from "./delivery.js";
|
|
1
|
+
import { type DeliveryErrorHandler, type DeliveryResult, type InstallDelivery } from "./delivery.js";
|
|
2
2
|
import { type InstallData, type InstallEnvelope, type ScopesUpdateEnvelope, type SettingsEnvelope, type UninstallEnvelope } from "./handoff.js";
|
|
3
3
|
import type { InstallationRecord, InstallationStore } from "./store.js";
|
|
4
4
|
/**
|
|
5
5
|
* Framework-agnostic handlers for Queek's signed server-to-server handoff
|
|
6
|
-
*
|
|
7
|
-
* layer 1 `handleInstallDelivery(input, options)` takes plain data
|
|
6
|
+
* in three thin layers: layer 1 `handleInstallDelivery(input, options)` takes plain data
|
|
8
7
|
* (untouched body bytes + headers, plus method/path for routing) and
|
|
9
8
|
* returns a plain `{ status, body }` result — zero request/response types,
|
|
10
9
|
* so whatever request object each framework supports works; layer 2
|
|
@@ -13,12 +12,12 @@ import type { InstallationRecord, InstallationStore } from "./store.js";
|
|
|
13
12
|
*
|
|
14
13
|
* Every handoff — install, uninstall, settings, resync — carries the
|
|
15
14
|
* Standard Webhooks headers signed with the APP signing secret (`whsec_…`,
|
|
16
|
-
* minted
|
|
17
|
-
* installation is durably stored; anything else
|
|
18
|
-
* just-minted key and mark the install failed
|
|
15
|
+
* minted when the app is registered on the Queek Developer page). The app
|
|
16
|
+
* answers 2xx only after the installation is durably stored; anything else
|
|
17
|
+
* makes Queek revoke the just-minted key and mark the install failed
|
|
18
|
+
* (retry = a fresh install).
|
|
19
19
|
*
|
|
20
|
-
* The resync handoff (`type: app/resync
|
|
21
|
-
* `AppInstallService::resyncPayload()`) redelivers the install-shaped data
|
|
20
|
+
* The resync handoff (`type: app/resync`) redelivers the install-shaped data
|
|
22
21
|
* — rotated `webhook_secret`, non-secret settings, and recovery copies of
|
|
23
22
|
* `proxy_secret` / `embed_secret` / `app_id` — to the app's `settings_url`,
|
|
24
23
|
* falling back to `install_url`. Both `install` and `settings` accept it
|
|
@@ -50,12 +49,18 @@ export interface InstallCallbacks {
|
|
|
50
49
|
/** Defaults to refreshing the cached grant (dropping the cached token when it moved). */
|
|
51
50
|
onScopesUpdate?: (envelope: ScopesUpdateEnvelope) => Promise<void>;
|
|
52
51
|
}
|
|
52
|
+
/** Options for the install handoff handlers. `appSecret` and `store` are required; every callback has a safe default. */
|
|
53
53
|
export interface InstallHandlerOptions extends InstallCallbacks {
|
|
54
54
|
/** The app signing secret (`whsec_…`) from Queek registration. */
|
|
55
55
|
appSecret: string;
|
|
56
56
|
store: InstallationStore;
|
|
57
57
|
nowSeconds?: number;
|
|
58
58
|
maxSkewSeconds?: number;
|
|
59
|
+
/**
|
|
60
|
+
* Called with the error a callback or store write threw, before the 500
|
|
61
|
+
* goes back. Without it the error is logged to stderr (secrets redacted).
|
|
62
|
+
*/
|
|
63
|
+
onError?: DeliveryErrorHandler;
|
|
59
64
|
}
|
|
60
65
|
/**
|
|
61
66
|
* Build the storable installation record from install `data`. Timestamps
|
|
@@ -94,12 +99,26 @@ export declare function saveResyncedInstallation(existing: InstallationRecord, d
|
|
|
94
99
|
* `uninstall`, or `settings`, taken from the input path's last segment)
|
|
95
100
|
* from plain data — no `Request`, no framework. Same options, same
|
|
96
101
|
* behaviour, same errors/status codes at every layer.
|
|
102
|
+
*
|
|
103
|
+
* Answers 401 for a missing header, stale timestamp or bad signature, 400
|
|
104
|
+
* for a body that is not the expected envelope, 409 for a repeated
|
|
105
|
+
* `webhook-id`, 404 for an unknown route, and 500 when your callback or the
|
|
106
|
+
* store write throws (Queek then retries).
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* const result = await handleInstallDelivery(
|
|
110
|
+
* { rawBody, headers, method: "POST", path: "/queek/install" },
|
|
111
|
+
* { appSecret: process.env.APP_SIGNING_SECRET ?? "", store },
|
|
112
|
+
* );
|
|
113
|
+
* // reply with result.status and result.body
|
|
97
114
|
*/
|
|
98
115
|
export declare function handleInstallDelivery(input: InstallDelivery, options: InstallHandlerOptions): Promise<DeliveryResult>;
|
|
99
116
|
/**
|
|
100
117
|
* Layer 2: the Web-standard wrapper, built ONLY on layer 1 — reads
|
|
101
118
|
* `await request.arrayBuffer()` + headers, calls the core, builds the
|
|
102
119
|
* `Response`.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* export const POST = (request: Request) => handleInstallRequest(request, { appSecret, store });
|
|
103
123
|
*/
|
|
104
124
|
export declare function handleInstallRequest(request: Request, options: InstallHandlerOptions): Promise<Response>;
|
|
105
|
-
//# sourceMappingURL=install-handlers.d.ts.map
|
package/dist/install-handlers.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decodeBody, readHeader, toResponse, } from "./delivery.js";
|
|
1
|
+
import { decodeBody, readHeader, reportDeliveryFailure, toResponse, } from "./delivery.js";
|
|
2
2
|
import { INSTALL_EVENT, RESYNC_EVENT, SCOPES_UPDATE_EVENT, SETTINGS_EVENT, UNINSTALL_EVENT, } from "./handoff.js";
|
|
3
3
|
import { MAX_TIMESTAMP_SKEW_SECONDS, verifyQueekSignatureDetailed, WEBHOOK_ID_HEADER, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_TIMESTAMP_HEADER, } from "./signatures.js";
|
|
4
4
|
import { normaliseNullablePid, normalisePid } from "./store.js";
|
|
@@ -14,8 +14,8 @@ export function buildInstallationRecord(data, nowIso = new Date().toISOString())
|
|
|
14
14
|
function installationRecordFromInstall(data, nowIso) {
|
|
15
15
|
return {
|
|
16
16
|
installationId: data.installation.id,
|
|
17
|
-
// The installation p_id arrives as a string on the wire
|
|
18
|
-
//
|
|
17
|
+
// The installation p_id arrives as a string on the wire.
|
|
18
|
+
// `normalisePid` stays as tolerance: a JSON integer would reach
|
|
19
19
|
// here as a JS number, which `node:sqlite` would store as `"1021.0"`
|
|
20
20
|
// in the TEXT column — never equal to the proxy `kid` — so non-string
|
|
21
21
|
// forms still normalise to integer strings.
|
|
@@ -24,7 +24,7 @@ function installationRecordFromInstall(data, nowIso) {
|
|
|
24
24
|
storePid: normaliseNullablePid(data.store.p_id),
|
|
25
25
|
storeName: data.store.name,
|
|
26
26
|
apiBase: data.api_base,
|
|
27
|
-
// No credential crosses the handoff
|
|
27
|
+
// No credential crosses the handoff: a fresh row caches no token
|
|
28
28
|
// (the first call mints one); see `saveResyncedInstallation` for the
|
|
29
29
|
// existing-row path, which keeps the cached token unless the grant
|
|
30
30
|
// changed.
|
|
@@ -106,6 +106,18 @@ export function saveResyncedInstallation(existing, data, nowIso = new Date().toI
|
|
|
106
106
|
* `uninstall`, or `settings`, taken from the input path's last segment)
|
|
107
107
|
* from plain data — no `Request`, no framework. Same options, same
|
|
108
108
|
* behaviour, same errors/status codes at every layer.
|
|
109
|
+
*
|
|
110
|
+
* Answers 401 for a missing header, stale timestamp or bad signature, 400
|
|
111
|
+
* for a body that is not the expected envelope, 409 for a repeated
|
|
112
|
+
* `webhook-id`, 404 for an unknown route, and 500 when your callback or the
|
|
113
|
+
* store write throws (Queek then retries).
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* const result = await handleInstallDelivery(
|
|
117
|
+
* { rawBody, headers, method: "POST", path: "/queek/install" },
|
|
118
|
+
* { appSecret: process.env.APP_SIGNING_SECRET ?? "", store },
|
|
119
|
+
* );
|
|
120
|
+
* // reply with result.status and result.body
|
|
109
121
|
*/
|
|
110
122
|
export async function handleInstallDelivery(input, options) {
|
|
111
123
|
if ((input.method ?? "POST").toUpperCase() !== "POST") {
|
|
@@ -132,6 +144,9 @@ function trailingSegment(path) {
|
|
|
132
144
|
* Layer 2: the Web-standard wrapper, built ONLY on layer 1 — reads
|
|
133
145
|
* `await request.arrayBuffer()` + headers, calls the core, builds the
|
|
134
146
|
* `Response`.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* export const POST = (request: Request) => handleInstallRequest(request, { appSecret, store });
|
|
135
150
|
*/
|
|
136
151
|
export async function handleInstallRequest(request, options) {
|
|
137
152
|
return toResponse(await handleInstallDelivery({
|
|
@@ -169,6 +184,11 @@ function parseEnvelope(rawBody) {
|
|
|
169
184
|
return null;
|
|
170
185
|
}
|
|
171
186
|
}
|
|
187
|
+
function hasInstallationId(data) {
|
|
188
|
+
return (typeof data === "object" &&
|
|
189
|
+
data !== null &&
|
|
190
|
+
typeof data.installation?.id === "string");
|
|
191
|
+
}
|
|
172
192
|
function isInstallPayload(data) {
|
|
173
193
|
return (!!data &&
|
|
174
194
|
typeof data === "object" &&
|
|
@@ -196,14 +216,14 @@ function isScopesUpdatePayload(data) {
|
|
|
196
216
|
}
|
|
197
217
|
/**
|
|
198
218
|
* Default grant-change apply: find the row by the handoff `p_id` (the
|
|
199
|
-
* scopes_update payload carries no UUID
|
|
219
|
+
* scopes_update payload carries no UUID) and
|
|
200
220
|
* refresh its cached grant, dropping the cached token when the grant
|
|
201
221
|
* moved. Unknown `p_id` answers 404 (released, like the settings path).
|
|
202
222
|
*/
|
|
203
223
|
async function applyScopesUpdate(store, data) {
|
|
204
|
-
// The installation p_id arrives as a string on the wire
|
|
205
|
-
//
|
|
206
|
-
//
|
|
224
|
+
// The installation p_id arrives as a string on the wire — normalise
|
|
225
|
+
// before comparing anyway, exactly like the install mapping does, so a
|
|
226
|
+
// non-conforming integer form still matches.
|
|
207
227
|
const pid = normalisePid(data.installation.p_id);
|
|
208
228
|
const rows = await store.listInstallations();
|
|
209
229
|
const existing = rows.find((row) => row.installationPid === pid) ?? null;
|
|
@@ -243,8 +263,9 @@ async function scopesDelivery(rawBody, headers, options) {
|
|
|
243
263
|
}
|
|
244
264
|
}
|
|
245
265
|
}
|
|
246
|
-
catch {
|
|
266
|
+
catch (error) {
|
|
247
267
|
await options.store.releaseWebhookId(checked.id);
|
|
268
|
+
reportDeliveryFailure(options.onError, error, { stage: "scopes_update", eventId: checked.id });
|
|
248
269
|
return { status: 500, body: { ok: false, error: "scopes_update_failed" } };
|
|
249
270
|
}
|
|
250
271
|
return { status: 200, body: { ok: true } };
|
|
@@ -256,8 +277,8 @@ async function installDelivery(rawBody, headers, options) {
|
|
|
256
277
|
const envelope = parseEnvelope(rawBody);
|
|
257
278
|
if (!envelope)
|
|
258
279
|
return { status: 400, body: { ok: false, error: "invalid_envelope" } };
|
|
259
|
-
// The grant-change handoff (`type: app/scopes_update
|
|
260
|
-
//
|
|
280
|
+
// The grant-change handoff (`type: app/scopes_update`) is delivered to
|
|
281
|
+
// the settings URL first,
|
|
261
282
|
// falling back to the install URL — both routes accept it with the same
|
|
262
283
|
// verification, claim, and callback-failure rules as every handoff.
|
|
263
284
|
if (envelope.type === SCOPES_UPDATE_EVENT) {
|
|
@@ -265,8 +286,8 @@ async function installDelivery(rawBody, headers, options) {
|
|
|
265
286
|
}
|
|
266
287
|
// The platform resync handoff redelivers the install-shaped data under
|
|
267
288
|
// `type: app/resync` (same signed channel, same verification): it lands
|
|
268
|
-
// here as well as on `settings` (
|
|
269
|
-
//
|
|
289
|
+
// here as well as on `settings` (Queek aims at `settings_url` first,
|
|
290
|
+
// falling back to `install_url`).
|
|
270
291
|
if (envelope.type !== INSTALL_EVENT && envelope.type !== RESYNC_EVENT) {
|
|
271
292
|
return { status: 400, body: { ok: false, error: "unexpected event type" } };
|
|
272
293
|
}
|
|
@@ -291,10 +312,11 @@ async function installDelivery(rawBody, headers, options) {
|
|
|
291
312
|
await applyResync(options.store, envelope.data);
|
|
292
313
|
}
|
|
293
314
|
}
|
|
294
|
-
catch {
|
|
315
|
+
catch (error) {
|
|
295
316
|
// Non-2xx on purpose: Queek revokes the key and the merchant retries
|
|
296
317
|
// as a fresh install. The claim is released so the retry can land.
|
|
297
318
|
await options.store.releaseWebhookId(checked.id);
|
|
319
|
+
reportDeliveryFailure(options.onError, error, { stage: "install", eventId: checked.id });
|
|
298
320
|
return { status: 500, body: { ok: false, error: "install_failed" } };
|
|
299
321
|
}
|
|
300
322
|
return { status: 200, body: { ok: true } };
|
|
@@ -309,6 +331,9 @@ async function uninstallDelivery(rawBody, headers, options) {
|
|
|
309
331
|
if (envelope.type !== UNINSTALL_EVENT) {
|
|
310
332
|
return { status: 400, body: { ok: false, error: "unexpected event type" } };
|
|
311
333
|
}
|
|
334
|
+
if (!hasInstallationId(envelope.data)) {
|
|
335
|
+
return { status: 400, body: { ok: false, error: "invalid uninstall payload" } };
|
|
336
|
+
}
|
|
312
337
|
if (!(await options.store.claimWebhookId(checked.id))) {
|
|
313
338
|
return { status: 409, body: { ok: false, error: "duplicate delivery" } };
|
|
314
339
|
}
|
|
@@ -320,8 +345,9 @@ async function uninstallDelivery(rawBody, headers, options) {
|
|
|
320
345
|
await options.store.deleteInstallation(envelope.data.installation.id);
|
|
321
346
|
}
|
|
322
347
|
}
|
|
323
|
-
catch {
|
|
348
|
+
catch (error) {
|
|
324
349
|
await options.store.releaseWebhookId(checked.id);
|
|
350
|
+
reportDeliveryFailure(options.onError, error, { stage: "uninstall", eventId: checked.id });
|
|
325
351
|
return { status: 500, body: { ok: false, error: "uninstall_failed" } };
|
|
326
352
|
}
|
|
327
353
|
return { status: 200, body: { ok: true } };
|
|
@@ -355,12 +381,16 @@ async function settingsDelivery(rawBody, headers, options) {
|
|
|
355
381
|
try {
|
|
356
382
|
await applyResync(options.store, envelope.data);
|
|
357
383
|
}
|
|
358
|
-
catch {
|
|
384
|
+
catch (error) {
|
|
359
385
|
await options.store.releaseWebhookId(checked.id);
|
|
386
|
+
reportDeliveryFailure(options.onError, error, { stage: "settings", eventId: checked.id });
|
|
360
387
|
return { status: 500, body: { ok: false, error: "settings_failed" } };
|
|
361
388
|
}
|
|
362
389
|
return { status: 200, body: { ok: true } };
|
|
363
390
|
}
|
|
391
|
+
if (!hasInstallationId(envelope.data)) {
|
|
392
|
+
return { status: 400, body: { ok: false, error: "invalid settings payload" } };
|
|
393
|
+
}
|
|
364
394
|
if (!(await options.store.claimWebhookId(checked.id))) {
|
|
365
395
|
return { status: 409, body: { ok: false, error: "duplicate delivery" } };
|
|
366
396
|
}
|
|
@@ -382,10 +412,10 @@ async function settingsDelivery(rawBody, headers, options) {
|
|
|
382
412
|
});
|
|
383
413
|
}
|
|
384
414
|
}
|
|
385
|
-
catch {
|
|
415
|
+
catch (error) {
|
|
386
416
|
await options.store.releaseWebhookId(checked.id);
|
|
417
|
+
reportDeliveryFailure(options.onError, error, { stage: "settings", eventId: checked.id });
|
|
387
418
|
return { status: 500, body: { ok: false, error: "settings_failed" } };
|
|
388
419
|
}
|
|
389
420
|
return { status: 200, body: { ok: true } };
|
|
390
421
|
}
|
|
391
|
-
//# sourceMappingURL=install-handlers.js.map
|
package/dist/logger.d.ts
CHANGED
|
@@ -23,5 +23,11 @@ export interface Logger {
|
|
|
23
23
|
warn(message: string, fields?: LogFields): void;
|
|
24
24
|
error(message: string, fields?: LogFields): void;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* A JSON-lines logger with secret redaction built in.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const logger = createLogger({ service: "my-app" });
|
|
31
|
+
* logger.info("order synced", { order: "ord_123" });
|
|
32
|
+
*/
|
|
26
33
|
export declare function createLogger(options: LoggerOptions): Logger;
|
|
27
|
-
//# sourceMappingURL=logger.d.ts.map
|
package/dist/logger.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
13
13
|
const SENSITIVE_KEY_RE = /key|secret|token|passwd|password|auth|credential|session/i;
|
|
14
14
|
// Long opaque values Queek and friends mint: sk_live_/sk_test_, pk_*, whsec_*, Bearer …
|
|
15
|
-
// — plus the
|
|
15
|
+
// — plus the app credential shapes, which must NEVER reach logs: a whole PEM
|
|
16
16
|
// private-key block and a bare RS256 JWT (eyJ….….…, with or without a Bearer prefix).
|
|
17
17
|
const SECRET_VALUE_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----|\b(eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+|sk_(live|test)_[A-Za-z0-9_-]+|pk_(live|test)_[A-Za-z0-9_-]+|whsec_[A-Za-z0-9+/=_-]+|Bearer\s+[A-Za-z0-9._~+/-]+=*)\b/g;
|
|
18
18
|
export const REDACTED = "[redacted]";
|
|
@@ -40,6 +40,13 @@ function redactUnknown(value, depth) {
|
|
|
40
40
|
}
|
|
41
41
|
return value;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* A JSON-lines logger with secret redaction built in.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* const logger = createLogger({ service: "my-app" });
|
|
48
|
+
* logger.info("order synced", { order: "ord_123" });
|
|
49
|
+
*/
|
|
43
50
|
export function createLogger(options) {
|
|
44
51
|
const threshold = LEVELS[options.level ?? process.env.LOG_LEVEL ?? "info"] ?? 1;
|
|
45
52
|
const sink = options.sink ??
|
|
@@ -72,4 +79,3 @@ export function createLogger(options) {
|
|
|
72
79
|
error: (message, fields) => emit("error", message, fields),
|
|
73
80
|
};
|
|
74
81
|
}
|
|
75
|
-
//# sourceMappingURL=logger.js.map
|