@sekyro/web 0.0.0-stage → 0.1.0-rc.1

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 (68) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +20 -0
  3. package/README.md +92 -2
  4. package/dist/capture-constraints-BujkZcfb.d.cts +21 -0
  5. package/dist/capture-constraints-BujkZcfb.d.ts +21 -0
  6. package/dist/capture-session-v3-BJ8ciaWl.d.ts +457 -0
  7. package/dist/capture-session-v3-CAoWM8iT.d.cts +457 -0
  8. package/dist/chunk-LOTAJDRE.js +1094 -0
  9. package/dist/chunk-LOTAJDRE.js.map +1 -0
  10. package/dist/chunk-MWNFSNMP.js +52 -0
  11. package/dist/chunk-MWNFSNMP.js.map +1 -0
  12. package/dist/chunk-NILNKLK6.js +50 -0
  13. package/dist/chunk-NILNKLK6.js.map +1 -0
  14. package/dist/chunk-NU4YOLCN.js +796 -0
  15. package/dist/chunk-NU4YOLCN.js.map +1 -0
  16. package/dist/chunk-NWF46H7K.js +359 -0
  17. package/dist/chunk-NWF46H7K.js.map +1 -0
  18. package/dist/chunk-WY6U7AIK.js +193 -0
  19. package/dist/chunk-WY6U7AIK.js.map +1 -0
  20. package/dist/chunk-YVFF4Q4G.js +327 -0
  21. package/dist/chunk-YVFF4Q4G.js.map +1 -0
  22. package/dist/client/index.cjs +357 -0
  23. package/dist/client/index.cjs.map +1 -0
  24. package/dist/client/index.d.cts +39 -0
  25. package/dist/client/index.d.ts +39 -0
  26. package/dist/client/index.js +15 -0
  27. package/dist/client/index.js.map +1 -0
  28. package/dist/core/index.cjs +1605 -0
  29. package/dist/core/index.cjs.map +1 -0
  30. package/dist/core/index.d.cts +87 -0
  31. package/dist/core/index.d.ts +87 -0
  32. package/dist/core/index.js +139 -0
  33. package/dist/core/index.js.map +1 -0
  34. package/dist/core/v3/detect-worker.cjs +392 -0
  35. package/dist/core/v3/detect-worker.cjs.map +1 -0
  36. package/dist/core/v3/detect-worker.d.cts +1 -0
  37. package/dist/core/v3/detect-worker.d.ts +1 -0
  38. package/dist/core/v3/detect-worker.js +13 -0
  39. package/dist/core/v3/detect-worker.js.map +1 -0
  40. package/dist/create-verification--ldwakXV.d.cts +341 -0
  41. package/dist/create-verification-BlAPWeW5.d.ts +341 -0
  42. package/dist/detect-worker-CKXHlXx9.d.cts +105 -0
  43. package/dist/detect-worker-CKXHlXx9.d.ts +105 -0
  44. package/dist/detector-scanic-4C6IEW2K.js +787 -0
  45. package/dist/detector-scanic-4C6IEW2K.js.map +1 -0
  46. package/dist/hosted-client-BpvBqu9f.d.cts +408 -0
  47. package/dist/hosted-client-BpvBqu9f.d.ts +408 -0
  48. package/dist/index.cjs +2580 -0
  49. package/dist/index.cjs.map +1 -0
  50. package/dist/index.d.cts +6 -0
  51. package/dist/index.d.ts +6 -0
  52. package/dist/index.js +28 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/internal/index.cjs +4110 -0
  55. package/dist/internal/index.cjs.map +1 -0
  56. package/dist/internal/index.d.cts +431 -0
  57. package/dist/internal/index.d.ts +431 -0
  58. package/dist/internal/index.js +647 -0
  59. package/dist/internal/index.js.map +1 -0
  60. package/dist/popup/index.cjs +76 -0
  61. package/dist/popup/index.cjs.map +1 -0
  62. package/dist/popup/index.d.cts +66 -0
  63. package/dist/popup/index.d.ts +66 -0
  64. package/dist/popup/index.js +11 -0
  65. package/dist/popup/index.js.map +1 -0
  66. package/dist/scanic-ml-stub-S2OZ6HEF.js +10 -0
  67. package/dist/scanic-ml-stub-S2OZ6HEF.js.map +1 -0
  68. package/package.json +118 -4
@@ -0,0 +1,357 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/client/index.ts
21
+ var client_exports = {};
22
+ __export(client_exports, {
23
+ HostedApiError: () => HostedApiError,
24
+ HostedClient: () => HostedClient,
25
+ KNOWN_CODES: () => KNOWN_CODES,
26
+ isKnownCode: () => isKnownCode,
27
+ stripMimeParams: () => stripMimeParams
28
+ });
29
+ module.exports = __toCommonJS(client_exports);
30
+
31
+ // src/client/errors.ts
32
+ var KNOWN_CODES = [
33
+ // auth / addressing (session-token.guard, controllers' own-session guards)
34
+ "session_token_invalid",
35
+ // 401 — missing/invalid/expired/revoked token
36
+ "session_token_required",
37
+ // 403 — non-session-token principal on a hosted route (defensive)
38
+ "session_token_session_mismatch",
39
+ // 403 — route :id differs from the token's bound session
40
+ "session_not_found",
41
+ // 404 — token's session missing/foreign (findOwned)
42
+ // rate limiting (session-token.guard — per-token + per-IP buckets)
43
+ "rate_limit_exceeded",
44
+ // 429 + Retry-After header (seconds)
45
+ // consent gate (Task 0 — submit AND media presign/complete)
46
+ "consent_required",
47
+ // 409 — record consent before uploading/submitting
48
+ // submit flow (sessions.service.submitCheck)
49
+ "session_not_active",
50
+ // 409 — session is terminal/review; no further submissions
51
+ "check_not_found",
52
+ // 404 — session has no such checkType
53
+ "check_already_finalized",
54
+ // 409 — re-submit of a terminal/review check
55
+ "check_out_of_order",
56
+ // 409 — only the frontier check accepts a submission
57
+ "check_type_unsupported",
58
+ // 400 — no registered executor for the checkType
59
+ "force_outcome_forbidden",
60
+ // 403 — forceOutcome on a live session (plan D-C)
61
+ "attestation_invalid",
62
+ // 400 (Task 1) — Turnstile/attestation failed; reload and retry
63
+ "nni_locked",
64
+ // 409 (Plan A Task 2) — session is bound to a server-provided NNI; a different client NNI was sent
65
+ // media (media.service)
66
+ "media_content_type_not_allowed",
67
+ // 400 — outside the per-kind MIME allowlist
68
+ "media_too_large",
69
+ // 413 — over the per-kind byte cap
70
+ "media_key_invalid",
71
+ // 400 — not a well-formed pending key
72
+ "media_key_forbidden",
73
+ // 403 — key names another session's pending space
74
+ "media_not_found",
75
+ // 404 — no uploaded object behind the key
76
+ "media_unreadable",
77
+ // 422 — magic-byte sniff rejected the object
78
+ "media_session_invalid",
79
+ // 400 — sessionId unusable as a key segment (never in practice)
80
+ // idempotency (idempotency.interceptor — Task 2)
81
+ "idempotency_key_collision",
82
+ // 409 — same key, different body hash
83
+ "idempotency_in_progress",
84
+ // 409 — concurrent identical request mid-flight (client auto-retries)
85
+ "idempotency_key_required",
86
+ // 400 — only on @Idempotent({required:true}) routes (none hosted yet)
87
+ // validation + fallback
88
+ "validation_error",
89
+ // 400 — ValibotPipe rejection (camelToSnake of 'ValidationError')
90
+ "internal_error",
91
+ // 500 — unhandled server error
92
+ // client-synthesized (never sent by the backend)
93
+ "unknown_error",
94
+ // unparseable (non-JSON) response body — error OR 2xx (defensive)
95
+ "upload_failed",
96
+ // the bare Spaces PUT answered non-2xx
97
+ "network_error",
98
+ // fetch rejected: connection drop / DNS / CORS / mid-transfer (status 0)
99
+ "timeout"
100
+ // AbortSignal.timeout fired — API calls 30 s default, Spaces PUT 15 min (status 0)
101
+ ];
102
+ function isKnownCode(code) {
103
+ return KNOWN_CODES.includes(code);
104
+ }
105
+ var HostedApiError = class extends Error {
106
+ constructor(args) {
107
+ super(args.message);
108
+ this.name = "HostedApiError";
109
+ this.status = args.status;
110
+ this.code = args.code;
111
+ if (args.retryAfterSeconds !== void 0) this.retryAfterSeconds = args.retryAfterSeconds;
112
+ }
113
+ };
114
+
115
+ // src/client/hosted-client.ts
116
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
117
+ var SPACES_PUT_TIMEOUT_MS = 9e5;
118
+ var UPLOAD_ERROR_SNIPPET_CHARS = 200;
119
+ var IN_PROGRESS_RETRY_ATTEMPTS = 5;
120
+ var IN_PROGRESS_RETRY_DELAY_MS = 400;
121
+ function stripMimeParams(contentType) {
122
+ return (contentType.split(";")[0] ?? "").trim();
123
+ }
124
+ var MEDIA_INPUT_FIELD = {
125
+ liveness: "clipKey",
126
+ face_match: "imageKey",
127
+ doc_ocr: "imageKey"
128
+ };
129
+ var HostedClient = class {
130
+ constructor(opts) {
131
+ this.sessionId = null;
132
+ this.bootstrap = null;
133
+ this.apiOrigin = opts.apiOrigin.replace(/\/+$/, "");
134
+ this.sessionToken = opts.sessionToken;
135
+ const impl = opts.fetch;
136
+ this.fetchImpl = (input, init) => (impl ?? globalThis.fetch).call(globalThis, input, init);
137
+ this.requestTimeoutMs = opts.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
138
+ this.retryAttempts = opts.inProgressRetry?.attempts ?? IN_PROGRESS_RETRY_ATTEMPTS;
139
+ this.retryDelayMs = opts.inProgressRetry?.delayMs ?? IN_PROGRESS_RETRY_DELAY_MS;
140
+ }
141
+ /** Token-addressed bootstrap — the hosted page's first call. Caches the sessionId. */
142
+ async getSession() {
143
+ const view = await this.api("GET", "/hosted/session");
144
+ this.sessionId = view.sessionId;
145
+ return view;
146
+ }
147
+ /**
148
+ * Record consent once (201; an idempotent replay answers 200 with
149
+ * `alreadyRecorded: true`). Until recorded, media presign/complete AND check
150
+ * submission answer 409 `consent_required`.
151
+ */
152
+ async recordConsent(locale) {
153
+ return this.api("POST", "/hosted/consent", { body: { locale } });
154
+ }
155
+ /**
156
+ * Presign a Spaces PUT for a new pending object. `contentType` is stripped of
157
+ * MIME parameters defensively (the allowlist holds bare types; the engine's
158
+ * `clipMimeType` carries codec params) — PUT with the SAME bare type, or the
159
+ * signed-header check rejects the upload.
160
+ */
161
+ async presign(kind, contentType, contentLength) {
162
+ const id = await this.ensureSessionId();
163
+ const body = { kind, contentType: stripMimeParams(contentType), contentLength };
164
+ return this.api("POST", `/sessions/${id}/media`, { body });
165
+ }
166
+ /**
167
+ * Bare PUT to the presigned Spaces URL: EXACTLY the signed headers — the
168
+ * declared Content-Type and the blob body. NO `x-session-token` (the
169
+ * signature IS the credential; an extra header breaks nothing but leaks the
170
+ * token to a third-party host) and no JSON envelope.
171
+ *
172
+ * `contentType` is param-stripped here too (idempotent for bare types): the
173
+ * presign strips internally, so a caller composing manually with the engine's
174
+ * parameterful `clipMimeType` would otherwise sign `video/webm` but PUT
175
+ * `video/webm;codecs=…` — a signed-header mismatch S3 rejects at runtime.
176
+ *
177
+ * Timeout: 15 min (`AbortSignal.timeout(900_000)`, matching the presign TTL)
178
+ * rather than the API calls' 30 s — a 15 MB clip on a slow link legitimately
179
+ * exceeds 30 s, but a stalled in-flight socket must still be bounded (the TTL
180
+ * alone only bounds when a PUT can START).
181
+ *
182
+ * Failures are ALWAYS typed: non-2xx throws {@link HostedApiError} with the
183
+ * client-synthesized `upload_failed` (message carries a truncated S3 body
184
+ * snippet — e.g. SignatureDoesNotMatch); a mid-transfer drop → `network_error`;
185
+ * the timeout → `timeout`.
186
+ */
187
+ async uploadToSpaces(uploadUrl, blob, contentType) {
188
+ let res;
189
+ try {
190
+ res = await this.fetchImpl(uploadUrl, {
191
+ method: "PUT",
192
+ headers: { "content-type": stripMimeParams(contentType) },
193
+ body: blob,
194
+ signal: AbortSignal.timeout(SPACES_PUT_TIMEOUT_MS)
195
+ });
196
+ } catch (err) {
197
+ throw asTransportError(err);
198
+ }
199
+ if (!res.ok) {
200
+ const snippet = (await res.text().catch(() => "")).slice(0, UPLOAD_ERROR_SNIPPET_CHARS);
201
+ throw new HostedApiError({
202
+ status: res.status,
203
+ code: "upload_failed",
204
+ message: `media upload failed (HTTP ${res.status})${snippet ? `: ${snippet}` : ""}`
205
+ });
206
+ }
207
+ }
208
+ /** Finalize an upload: the backend sniffs + promotes; returns the PROMOTED key to submit. */
209
+ async completeMedia(key) {
210
+ const id = await this.ensureSessionId();
211
+ return this.api("POST", `/sessions/${id}/media/complete`, {
212
+ body: { key }
213
+ });
214
+ }
215
+ /**
216
+ * Submit one check. Sends `{input, forceOutcome?}` (SubmitCheckSchema) and the
217
+ * optional `x-idempotency-key` header. Returns the whitelisted SessionView.
218
+ */
219
+ async submitCheck(checkType, input, opts = {}) {
220
+ const id = await this.ensureSessionId();
221
+ const body = {
222
+ input,
223
+ ...opts.forceOutcome !== void 0 ? { forceOutcome: opts.forceOutcome } : {}
224
+ };
225
+ return this.api("POST", `/sessions/${id}/checks/${checkType}`, {
226
+ body,
227
+ ...opts.idempotencyKey !== void 0 ? { idempotencyKey: opts.idempotencyKey } : {}
228
+ });
229
+ }
230
+ /**
231
+ * Convenience composition: presign → PUT → complete → submit, passing the
232
+ * PROMOTED key (from complete's response — never the pending presign key)
233
+ * into the check's media input field (`clipKey` for liveness, `imageKey` for
234
+ * face_match/doc_ocr). `extraInput` carries the remaining input fields
235
+ * (liveness `attestation`, doc_ocr `side`). The media content type is
236
+ * param-stripped ONCE and used for both presign and PUT, so the signed
237
+ * header always matches.
238
+ */
239
+ async uploadAndSubmit(checkType, media, extraInput, opts = {}) {
240
+ const bareType = stripMimeParams(media.contentType);
241
+ const presigned = await this.presign(media.kind, bareType, media.blob.size);
242
+ await this.uploadToSpaces(presigned.uploadUrl, media.blob, bareType);
243
+ const completed = await this.completeMedia(presigned.key);
244
+ const input = {
245
+ ...extraInput,
246
+ [MEDIA_INPUT_FIELD[checkType]]: completed.key
247
+ };
248
+ return this.submitCheck(checkType, input, opts);
249
+ }
250
+ // ── transport ──────────────────────────────────────────────────────────────
251
+ /** The token's own sessionId — cached from getSession(), lazily bootstrapped. */
252
+ async ensureSessionId() {
253
+ if (this.sessionId !== null) return this.sessionId;
254
+ this.bootstrap ?? (this.bootstrap = this.getSession().finally(() => {
255
+ this.bootstrap = null;
256
+ }));
257
+ return (await this.bootstrap).sessionId;
258
+ }
259
+ /**
260
+ * One API request: `${apiOrigin}/api/v2${path}`, `x-session-token` always,
261
+ * AbortSignal timeout (30 s default), `{success, data}` unwrap on 2xx, flat
262
+ * `{code, message}` → {@link HostedApiError} on non-2xx. EVERY failure is
263
+ * typed: a rejected fetch → `network_error`, the timeout → `timeout`, an
264
+ * unparseable 2xx body → `unknown_error` — the page never sees a raw
265
+ * TypeError/DOMException. The ONLY automatic retry is 409
266
+ * `idempotency_in_progress` (bounded, short delay).
267
+ */
268
+ async api(method, path, opts = {}) {
269
+ const url = `${this.apiOrigin}/api/v2${path}`;
270
+ const headers = { "x-session-token": this.sessionToken };
271
+ if (opts.body !== void 0) headers["content-type"] = "application/json";
272
+ if (opts.idempotencyKey !== void 0) headers["x-idempotency-key"] = opts.idempotencyKey;
273
+ for (let attempt = 0; ; attempt++) {
274
+ let res;
275
+ try {
276
+ res = await this.fetchImpl(url, {
277
+ method,
278
+ headers,
279
+ ...opts.body !== void 0 ? { body: JSON.stringify(opts.body) } : {},
280
+ signal: AbortSignal.timeout(this.requestTimeoutMs)
281
+ });
282
+ } catch (err2) {
283
+ throw asTransportError(err2);
284
+ }
285
+ if (res.ok) {
286
+ let raw;
287
+ try {
288
+ raw = await res.json();
289
+ } catch {
290
+ throw new HostedApiError({
291
+ status: res.status,
292
+ code: "unknown_error",
293
+ message: `response body was not valid JSON (HTTP ${res.status})`
294
+ });
295
+ }
296
+ return unwrap(raw);
297
+ }
298
+ const err = await parseErrorResponse(res);
299
+ if (err.code === "idempotency_in_progress" && attempt < this.retryAttempts) {
300
+ if (this.retryDelayMs > 0) await sleep(this.retryDelayMs);
301
+ continue;
302
+ }
303
+ throw err;
304
+ }
305
+ }
306
+ };
307
+ function asTransportError(err) {
308
+ const name = typeof err === "object" && err !== null ? err.name : void 0;
309
+ if (name === "TimeoutError") {
310
+ return new HostedApiError({ status: 0, code: "timeout", message: "request timed out" });
311
+ }
312
+ const detail = err instanceof Error ? err.message : String(err);
313
+ return new HostedApiError({
314
+ status: 0,
315
+ code: "network_error",
316
+ message: `network request failed: ${detail}`
317
+ });
318
+ }
319
+ function unwrap(raw) {
320
+ if (raw !== null && typeof raw === "object" && "success" in raw && "data" in raw) {
321
+ return raw.data;
322
+ }
323
+ return raw;
324
+ }
325
+ async function parseErrorResponse(res) {
326
+ let code = "unknown_error";
327
+ let message = `request failed (HTTP ${res.status})`;
328
+ try {
329
+ const body = await res.json();
330
+ if (body !== null && typeof body === "object") {
331
+ const env = body;
332
+ if (typeof env.code === "string" && env.code.length > 0) code = env.code;
333
+ if (typeof env.message === "string" && env.message.length > 0) message = env.message;
334
+ }
335
+ } catch {
336
+ }
337
+ const retryAfterRaw = res.headers.get("retry-after");
338
+ const retryAfter = retryAfterRaw === null ? Number.NaN : Number.parseInt(retryAfterRaw, 10);
339
+ return new HostedApiError({
340
+ status: res.status,
341
+ code,
342
+ message,
343
+ ...Number.isFinite(retryAfter) && retryAfter >= 0 ? { retryAfterSeconds: retryAfter } : {}
344
+ });
345
+ }
346
+ function sleep(ms) {
347
+ return new Promise((resolve) => setTimeout(resolve, ms));
348
+ }
349
+ // Annotate the CommonJS export names for ESM import in node:
350
+ 0 && (module.exports = {
351
+ HostedApiError,
352
+ HostedClient,
353
+ KNOWN_CODES,
354
+ isKnownCode,
355
+ stripMimeParams
356
+ });
357
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/client/index.ts","../../src/client/errors.ts","../../src/client/hosted-client.ts"],"sourcesContent":["// @sekyro/web/client — typed API client for the hosted\n// session-token surface (Plan 6 Task 5). Framework-free browser code.\nexport {\n HostedClient,\n stripMimeParams,\n type HostedClientOptions,\n type MediaCheckType,\n type SubmitOptions,\n type UploadMedia,\n} from \"./hosted-client\";\nexport { HostedApiError, KNOWN_CODES, isKnownCode, type KnownCode } from \"./errors\";\nexport type {\n AttestationInput,\n CheckInputByType,\n CheckReason,\n CheckState,\n CheckType,\n CompleteMediaResult,\n ConsentLocale,\n DocOcrInput,\n DocReason,\n DocSide,\n FaceMatchInput,\n FaceMatchReason,\n HostedOtpView,\n HostedSessionView,\n HostedStepView,\n LivenessInput,\n LivenessReason,\n MediaKind,\n PhoneOtpInput,\n PhoneOtpReason,\n PopulationInput,\n PopulationReason,\n PresignRequest,\n PresignUploadResult,\n RecordConsentResult,\n SessionOutcome,\n SessionStatus,\n SessionStepView,\n SessionView,\n SubmitCheckBody,\n} from \"./types\";\n","// Public error map for the session-token surface (Plan 6 Task 5).\n//\n// The backend's AllExceptionsFilter (audience 'public') emits the FLAT envelope\n// `{code, message}` — no details, no request_id, no traceId. `code` is the\n// snake_cased `error` key of the thrown HttpException body (extractError →\n// camelToSnake). 429 responses additionally carry a `Retry-After` header in\n// SECONDS (SessionTokenGuard / RateLimitGuard).\n//\n// KNOWN_CODES below is the VERIFIED set reachable through the five hosted\n// endpoints (grep of `error: '…'` sites in identity-backend @ Tasks 0+1+2 —\n// hosted.controller, checks.controller, media.{controller,service},\n// sessions.service submit flow, session-token.guard, valibot.pipe,\n// check-executor registry, idempotency.interceptor, attestation mapping),\n// plus four CLIENT-SYNTHESIZED codes that never come from the backend:\n// - 'unknown_error' — a response body that was not parseable JSON (proxy HTML\n// on an error, or a malformed 2xx body)\n// - 'upload_failed' — the bare Spaces PUT answered non-2xx (S3 XML, no envelope)\n// - 'network_error' — fetch itself rejected: connection drop / DNS / CORS /\n// mid-transfer failure (status: 0 — no response arrived)\n// - 'timeout' — the request's AbortSignal.timeout fired (status: 0)\n// Task 7's error-copy map must carry entries for all four from day one.\n//\n// Deliberately EXCLUDED (verified unreachable on this surface today):\n// - payment_required / account_suspended — BillingInterceptor gates only\n// handlers decorated @BillableCheck, and no handler is decorated yet.\n// - not_found — the generic unmatched-route 404; a correctly configured client\n// never produces it. Unknown codes route through `isKnownCode() === false`.\n\n/**\n * Every error code the hosted page can receive from the session-token surface.\n * A closed string-literal union — switch over {@link KnownCode} with a `never`\n * default arm for compile-checked exhaustiveness.\n */\nexport const KNOWN_CODES = [\n // auth / addressing (session-token.guard, controllers' own-session guards)\n \"session_token_invalid\", // 401 — missing/invalid/expired/revoked token\n \"session_token_required\", // 403 — non-session-token principal on a hosted route (defensive)\n \"session_token_session_mismatch\", // 403 — route :id differs from the token's bound session\n \"session_not_found\", // 404 — token's session missing/foreign (findOwned)\n // rate limiting (session-token.guard — per-token + per-IP buckets)\n \"rate_limit_exceeded\", // 429 + Retry-After header (seconds)\n // consent gate (Task 0 — submit AND media presign/complete)\n \"consent_required\", // 409 — record consent before uploading/submitting\n // submit flow (sessions.service.submitCheck)\n \"session_not_active\", // 409 — session is terminal/review; no further submissions\n \"check_not_found\", // 404 — session has no such checkType\n \"check_already_finalized\", // 409 — re-submit of a terminal/review check\n \"check_out_of_order\", // 409 — only the frontier check accepts a submission\n \"check_type_unsupported\", // 400 — no registered executor for the checkType\n \"force_outcome_forbidden\", // 403 — forceOutcome on a live session (plan D-C)\n \"attestation_invalid\", // 400 (Task 1) — Turnstile/attestation failed; reload and retry\n \"nni_locked\", // 409 (Plan A Task 2) — session is bound to a server-provided NNI; a different client NNI was sent\n // media (media.service)\n \"media_content_type_not_allowed\", // 400 — outside the per-kind MIME allowlist\n \"media_too_large\", // 413 — over the per-kind byte cap\n \"media_key_invalid\", // 400 — not a well-formed pending key\n \"media_key_forbidden\", // 403 — key names another session's pending space\n \"media_not_found\", // 404 — no uploaded object behind the key\n \"media_unreadable\", // 422 — magic-byte sniff rejected the object\n \"media_session_invalid\", // 400 — sessionId unusable as a key segment (never in practice)\n // idempotency (idempotency.interceptor — Task 2)\n \"idempotency_key_collision\", // 409 — same key, different body hash\n \"idempotency_in_progress\", // 409 — concurrent identical request mid-flight (client auto-retries)\n \"idempotency_key_required\", // 400 — only on @Idempotent({required:true}) routes (none hosted yet)\n // validation + fallback\n \"validation_error\", // 400 — ValibotPipe rejection (camelToSnake of 'ValidationError')\n \"internal_error\", // 500 — unhandled server error\n // client-synthesized (never sent by the backend)\n \"unknown_error\", // unparseable (non-JSON) response body — error OR 2xx (defensive)\n \"upload_failed\", // the bare Spaces PUT answered non-2xx\n \"network_error\", // fetch rejected: connection drop / DNS / CORS / mid-transfer (status 0)\n \"timeout\", // AbortSignal.timeout fired — API calls 30 s default, Spaces PUT 15 min (status 0)\n] as const;\n\n/** Closed union of {@link KNOWN_CODES} — exhaustively switchable. */\nexport type KnownCode = (typeof KNOWN_CODES)[number];\n\n/** Narrow an arbitrary wire code to the known union (unknown codes stay `string`). */\nexport function isKnownCode(code: string): code is KnownCode {\n return (KNOWN_CODES as readonly string[]).includes(code);\n}\n\n/**\n * Typed transport error: EVERY failure surfaces as one of these — non-2xx\n * responses, rejected fetches, timeouts, and unparseable bodies alike. The\n * hosted page never sees a raw TypeError/DOMException from the client.\n *\n * - `code` — the public envelope's `code`, or a client-synthesized fallback\n * ('unknown_error' / 'upload_failed' / 'network_error' / 'timeout'). Typed\n * `string` because the wire may grow codes; narrow with {@link isKnownCode}\n * before an exhaustive switch.\n * - `status` — the HTTP status, or 0 when no response arrived at all\n * ('network_error' / 'timeout').\n * - `retryAfterSeconds` — parsed from the `Retry-After` header when present\n * (rate-limit 429s always set it).\n */\nexport class HostedApiError extends Error {\n readonly status: number;\n readonly code: string;\n readonly retryAfterSeconds?: number;\n\n constructor(args: { status: number; code: string; message: string; retryAfterSeconds?: number }) {\n super(args.message);\n this.name = \"HostedApiError\";\n this.status = args.status;\n this.code = args.code;\n if (args.retryAfterSeconds !== undefined) this.retryAfterSeconds = args.retryAfterSeconds;\n }\n}\n","// Typed API client for the hosted session-token surface (Plan 6 Task 5).\n// Framework-free browser code — fetch + AbortSignal only, same zero-React rule\n// as core/. Hand-authored against the CURRENT identity-backend (Tasks 0+1+2);\n// see types.ts / errors.ts for the verified wire shapes.\nimport { HostedApiError } from \"./errors\";\nimport type {\n CheckInputByType,\n CheckType,\n CompleteMediaResult,\n ConsentLocale,\n HostedSessionView,\n MediaKind,\n PresignRequest,\n PresignUploadResult,\n RecordConsentResult,\n SessionView,\n SubmitCheckBody,\n} from \"./types\";\n\n/** Default per-request timeout on API calls (override via `requestTimeoutMs`). */\nconst DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\n\n/**\n * Timeout on the Spaces PUT — matches the presign TTL (900 s): the signature is\n * unusable after that anyway, and a 15 MB clip at 20 KB/s still finishes in\n * ~13 min. Bounds a stalled in-flight socket (the TTL alone only bounds when a\n * PUT can START, not how long a hung transfer may sit).\n */\nconst SPACES_PUT_TIMEOUT_MS = 900_000;\n\n/** Max chars of an S3 error body quoted into an upload_failed message. */\nconst UPLOAD_ERROR_SNIPPET_CHARS = 200;\n\n/** Bounded auto-retry defaults for 409 `idempotency_in_progress` (retries AFTER the first try). */\nconst IN_PROGRESS_RETRY_ATTEMPTS = 5;\nconst IN_PROGRESS_RETRY_DELAY_MS = 400;\n\n/**\n * Strip MIME parameters: `video/webm;codecs=vp8,opus` → `video/webm`.\n *\n * The capture engine (Task 4) emits `clipMimeType` WITH codec parameters, but\n * the presign allowlist holds BARE types only (['video/webm','video/mp4'] /\n * ['image/jpeg','image/png']) and the presigned PUT signs the exact declared\n * Content-Type. Exported so pages can normalize once and stay byte-consistent\n * between presign and PUT.\n */\nexport function stripMimeParams(contentType: string): string {\n return (contentType.split(\";\")[0] ?? \"\").trim();\n}\n\n/** Media-bearing check types → the input field the promoted key goes into. */\nconst MEDIA_INPUT_FIELD = {\n liveness: \"clipKey\",\n face_match: \"imageKey\",\n doc_ocr: \"imageKey\",\n} as const;\n\n/** Check types that take an uploaded media key (population_registry does not). */\nexport type MediaCheckType = keyof typeof MEDIA_INPUT_FIELD;\n\nexport interface SubmitOptions {\n /**\n * Sent as `x-idempotency-key` (the header IdempotencyInterceptor reads).\n * One key per LOGICAL submit — a flaky-network retry with the same key\n * replays the first response without burning another attempt.\n */\n idempotencyKey?: string;\n /** SANDBOX-ONLY deterministic override (plan D-C); a live session 403s it. */\n forceOutcome?: \"pass\" | \"fail\";\n}\n\nexport interface UploadMedia {\n kind: MediaKind;\n blob: Blob;\n /** May carry codec params (engine `clipMimeType`) — stripped before presign AND PUT. */\n contentType: string;\n}\n\nexport interface HostedClientOptions {\n /** API origin, e.g. `https://api.sekyro.ai` — paths are `${apiOrigin}/api/v2/...`. */\n apiOrigin: string;\n /** The raw session token from the hosted URL (short code; legacy `cst_` during its TTL) — sent as `x-session-token` on every API call. */\n sessionToken: string;\n /** Injection seam (tests / non-window contexts). Defaults to `globalThis.fetch`. */\n fetch?: typeof globalThis.fetch;\n /** Per-request timeout for API calls (NOT the Spaces PUT). Default 30 000 ms. */\n requestTimeoutMs?: number;\n /**\n * Tuning for the ONLY automatic retry the client performs: 409\n * `idempotency_in_progress` (a concurrent identical request is mid-flight\n * server-side; the real response lands once it finishes). `attempts` counts\n * retries AFTER the first try. Defaults: 5 × 400 ms.\n */\n inProgressRetry?: { attempts?: number; delayMs?: number };\n}\n\n/**\n * Hand-authored typed client for the five session-token endpoints:\n *\n * GET /api/v2/hosted/session → {@link getSession}\n * POST /api/v2/hosted/consent → {@link recordConsent}\n * POST /api/v2/sessions/:id/media → {@link presign}\n * POST /api/v2/sessions/:id/media/complete → {@link completeMedia}\n * POST /api/v2/sessions/:id/checks/:checkType → {@link submitCheck}\n *\n * plus the bare presigned Spaces PUT ({@link uploadToSpaces}) and the\n * presign→PUT→complete→submit composition ({@link uploadAndSubmit}).\n *\n * Envelopes (verified against sessions-docs.e2e-spec.ts):\n * - success: `{success: true, data}` — unwrapped here, methods return the payload;\n * - error: flat `{code, message}` → thrown as {@link HostedApiError}.\n *\n * The media/checks routes are `:id`-addressed while the token itself is the\n * only credential the page starts with — the client learns its sessionId from\n * `GET /hosted/session` (token-addressed) and lazily bootstraps it when a\n * `:id` route is called first.\n *\n * No automatic retry EXCEPT bounded short-delay retry on 409\n * `idempotency_in_progress`. Everything else — including 429 with its parsed\n * `retryAfterSeconds` — is surfaced to the page to handle.\n */\nexport class HostedClient {\n private readonly apiOrigin: string;\n private readonly sessionToken: string;\n private readonly fetchImpl: typeof globalThis.fetch;\n private readonly requestTimeoutMs: number;\n private readonly retryAttempts: number;\n private readonly retryDelayMs: number;\n\n private sessionId: string | null = null;\n private bootstrap: Promise<HostedSessionView> | null = null;\n\n constructor(opts: HostedClientOptions) {\n this.apiOrigin = opts.apiOrigin.replace(/\\/+$/, \"\");\n this.sessionToken = opts.sessionToken;\n // Always wrap and `.call(globalThis, …)`: browsers throw \"Illegal\n // invocation\" when fetch runs with a `this` that isn't the global — both\n // for the default AND for an injected `window.fetch`.\n const impl = opts.fetch;\n this.fetchImpl = (input, init) => (impl ?? globalThis.fetch).call(globalThis, input, init);\n this.requestTimeoutMs = opts.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n this.retryAttempts = opts.inProgressRetry?.attempts ?? IN_PROGRESS_RETRY_ATTEMPTS;\n this.retryDelayMs = opts.inProgressRetry?.delayMs ?? IN_PROGRESS_RETRY_DELAY_MS;\n }\n\n /** Token-addressed bootstrap — the hosted page's first call. Caches the sessionId. */\n async getSession(): Promise<HostedSessionView> {\n const view = await this.api<HostedSessionView>(\"GET\", \"/hosted/session\");\n this.sessionId = view.sessionId;\n return view;\n }\n\n /**\n * Record consent once (201; an idempotent replay answers 200 with\n * `alreadyRecorded: true`). Until recorded, media presign/complete AND check\n * submission answer 409 `consent_required`.\n */\n async recordConsent(locale: ConsentLocale): Promise<RecordConsentResult> {\n return this.api<RecordConsentResult>(\"POST\", \"/hosted/consent\", { body: { locale } });\n }\n\n /**\n * Presign a Spaces PUT for a new pending object. `contentType` is stripped of\n * MIME parameters defensively (the allowlist holds bare types; the engine's\n * `clipMimeType` carries codec params) — PUT with the SAME bare type, or the\n * signed-header check rejects the upload.\n */\n async presign(\n kind: MediaKind,\n contentType: string,\n contentLength: number,\n ): Promise<PresignUploadResult> {\n const id = await this.ensureSessionId();\n const body: PresignRequest = { kind, contentType: stripMimeParams(contentType), contentLength };\n return this.api<PresignUploadResult>(\"POST\", `/sessions/${id}/media`, { body });\n }\n\n /**\n * Bare PUT to the presigned Spaces URL: EXACTLY the signed headers — the\n * declared Content-Type and the blob body. NO `x-session-token` (the\n * signature IS the credential; an extra header breaks nothing but leaks the\n * token to a third-party host) and no JSON envelope.\n *\n * `contentType` is param-stripped here too (idempotent for bare types): the\n * presign strips internally, so a caller composing manually with the engine's\n * parameterful `clipMimeType` would otherwise sign `video/webm` but PUT\n * `video/webm;codecs=…` — a signed-header mismatch S3 rejects at runtime.\n *\n * Timeout: 15 min (`AbortSignal.timeout(900_000)`, matching the presign TTL)\n * rather than the API calls' 30 s — a 15 MB clip on a slow link legitimately\n * exceeds 30 s, but a stalled in-flight socket must still be bounded (the TTL\n * alone only bounds when a PUT can START).\n *\n * Failures are ALWAYS typed: non-2xx throws {@link HostedApiError} with the\n * client-synthesized `upload_failed` (message carries a truncated S3 body\n * snippet — e.g. SignatureDoesNotMatch); a mid-transfer drop → `network_error`;\n * the timeout → `timeout`.\n */\n async uploadToSpaces(uploadUrl: string, blob: Blob, contentType: string): Promise<void> {\n let res: Response;\n try {\n res = await this.fetchImpl(uploadUrl, {\n method: \"PUT\",\n headers: { \"content-type\": stripMimeParams(contentType) },\n body: blob,\n signal: AbortSignal.timeout(SPACES_PUT_TIMEOUT_MS),\n });\n } catch (err) {\n throw asTransportError(err);\n }\n if (!res.ok) {\n const snippet = (await res.text().catch(() => \"\")).slice(0, UPLOAD_ERROR_SNIPPET_CHARS);\n throw new HostedApiError({\n status: res.status,\n code: \"upload_failed\",\n message: `media upload failed (HTTP ${res.status})${snippet ? `: ${snippet}` : \"\"}`,\n });\n }\n }\n\n /** Finalize an upload: the backend sniffs + promotes; returns the PROMOTED key to submit. */\n async completeMedia(key: string): Promise<CompleteMediaResult> {\n const id = await this.ensureSessionId();\n return this.api<CompleteMediaResult>(\"POST\", `/sessions/${id}/media/complete`, {\n body: { key },\n });\n }\n\n /**\n * Submit one check. Sends `{input, forceOutcome?}` (SubmitCheckSchema) and the\n * optional `x-idempotency-key` header. Returns the whitelisted SessionView.\n */\n async submitCheck<T extends CheckType>(\n checkType: T,\n input: CheckInputByType[T],\n opts: SubmitOptions = {},\n ): Promise<SessionView> {\n const id = await this.ensureSessionId();\n const body: SubmitCheckBody = {\n input: input as unknown as Record<string, unknown>,\n ...(opts.forceOutcome !== undefined ? { forceOutcome: opts.forceOutcome } : {}),\n };\n return this.api<SessionView>(\"POST\", `/sessions/${id}/checks/${checkType}`, {\n body,\n ...(opts.idempotencyKey !== undefined ? { idempotencyKey: opts.idempotencyKey } : {}),\n });\n }\n\n /**\n * Convenience composition: presign → PUT → complete → submit, passing the\n * PROMOTED key (from complete's response — never the pending presign key)\n * into the check's media input field (`clipKey` for liveness, `imageKey` for\n * face_match/doc_ocr). `extraInput` carries the remaining input fields\n * (liveness `attestation`, doc_ocr `side`). The media content type is\n * param-stripped ONCE and used for both presign and PUT, so the signed\n * header always matches.\n */\n async uploadAndSubmit<T extends MediaCheckType>(\n checkType: T,\n media: UploadMedia,\n extraInput: Omit<CheckInputByType[T], (typeof MEDIA_INPUT_FIELD)[T]>,\n opts: SubmitOptions = {},\n ): Promise<SessionView> {\n const bareType = stripMimeParams(media.contentType);\n const presigned = await this.presign(media.kind, bareType, media.blob.size);\n await this.uploadToSpaces(presigned.uploadUrl, media.blob, bareType);\n const completed = await this.completeMedia(presigned.key);\n const input = {\n ...extraInput,\n [MEDIA_INPUT_FIELD[checkType]]: completed.key,\n } as unknown as CheckInputByType[T];\n return this.submitCheck(checkType, input, opts);\n }\n\n // ── transport ──────────────────────────────────────────────────────────────\n\n /** The token's own sessionId — cached from getSession(), lazily bootstrapped. */\n private async ensureSessionId(): Promise<string> {\n if (this.sessionId !== null) return this.sessionId;\n // Dedupe concurrent bootstraps; reset on settle so a failure can be retried.\n this.bootstrap ??= this.getSession().finally(() => {\n this.bootstrap = null;\n });\n return (await this.bootstrap).sessionId;\n }\n\n /**\n * One API request: `${apiOrigin}/api/v2${path}`, `x-session-token` always,\n * AbortSignal timeout (30 s default), `{success, data}` unwrap on 2xx, flat\n * `{code, message}` → {@link HostedApiError} on non-2xx. EVERY failure is\n * typed: a rejected fetch → `network_error`, the timeout → `timeout`, an\n * unparseable 2xx body → `unknown_error` — the page never sees a raw\n * TypeError/DOMException. The ONLY automatic retry is 409\n * `idempotency_in_progress` (bounded, short delay).\n */\n private async api<T>(\n method: \"GET\" | \"POST\",\n path: string,\n opts: { body?: unknown; idempotencyKey?: string } = {},\n ): Promise<T> {\n const url = `${this.apiOrigin}/api/v2${path}`;\n const headers: Record<string, string> = { \"x-session-token\": this.sessionToken };\n if (opts.body !== undefined) headers[\"content-type\"] = \"application/json\";\n if (opts.idempotencyKey !== undefined) headers[\"x-idempotency-key\"] = opts.idempotencyKey;\n\n for (let attempt = 0; ; attempt++) {\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method,\n headers,\n ...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}),\n signal: AbortSignal.timeout(this.requestTimeoutMs),\n });\n } catch (err) {\n throw asTransportError(err);\n }\n\n if (res.ok) {\n let raw: unknown;\n try {\n raw = await res.json();\n } catch {\n throw new HostedApiError({\n status: res.status,\n code: \"unknown_error\",\n message: `response body was not valid JSON (HTTP ${res.status})`,\n });\n }\n return unwrap<T>(raw);\n }\n\n const err = await parseErrorResponse(res);\n if (err.code === \"idempotency_in_progress\" && attempt < this.retryAttempts) {\n if (this.retryDelayMs > 0) await sleep(this.retryDelayMs);\n continue;\n }\n throw err;\n }\n }\n}\n\n/**\n * Normalize a rejected fetch into the typed contract (status 0 — no response\n * arrived): AbortSignal.timeout aborts with a DOMException named 'TimeoutError'\n * → `timeout`; everything else (TypeError on connection drop / DNS / CORS /\n * mid-transfer failure) → `network_error`. Duck-typed on `.name` because\n * DOMException does not inherit Error in every environment (e.g. jsdom).\n */\nfunction asTransportError(err: unknown): HostedApiError {\n const name = typeof err === \"object\" && err !== null ? (err as { name?: unknown }).name : undefined;\n if (name === \"TimeoutError\") {\n return new HostedApiError({ status: 0, code: \"timeout\", message: \"request timed out\" });\n }\n const detail = err instanceof Error ? err.message : String(err);\n return new HostedApiError({\n status: 0,\n code: \"network_error\",\n message: `network request failed: ${detail}`,\n });\n}\n\n/**\n * Unwrap the global success envelope `{success: true, data}` (ResponseTransform-\n * Interceptor wraps every object body that lacks a `success` key). Defensive:\n * a body without the envelope is returned as-is.\n */\nfunction unwrap<T>(raw: unknown): T {\n if (raw !== null && typeof raw === \"object\" && \"success\" in raw && \"data\" in raw) {\n return (raw as { data: T }).data;\n }\n return raw as T;\n}\n\n/**\n * Parse a non-2xx response into a {@link HostedApiError}: the public envelope\n * is flat `{code, message}`; a non-JSON body (proxy HTML, etc.) degrades to the\n * client-synthesized `unknown_error`. `Retry-After` (seconds) is parsed when\n * present — rate-limit 429s always set it.\n */\nasync function parseErrorResponse(res: Response): Promise<HostedApiError> {\n let code = \"unknown_error\";\n let message = `request failed (HTTP ${res.status})`;\n try {\n const body: unknown = await res.json();\n if (body !== null && typeof body === \"object\") {\n const env = body as { code?: unknown; message?: unknown };\n if (typeof env.code === \"string\" && env.code.length > 0) code = env.code;\n if (typeof env.message === \"string\" && env.message.length > 0) message = env.message;\n }\n } catch {\n // Not JSON — keep the defensive fallback.\n }\n const retryAfterRaw = res.headers.get(\"retry-after\");\n const retryAfter = retryAfterRaw === null ? Number.NaN : Number.parseInt(retryAfterRaw, 10);\n return new HostedApiError({\n status: res.status,\n code,\n message,\n ...(Number.isFinite(retryAfter) && retryAfter >= 0 ? { retryAfterSeconds: retryAfter } : {}),\n });\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":["err"]}
@@ -0,0 +1,39 @@
1
+ export { A as AttestationInput, i as CheckInputByType, C as CheckReason, a as CheckState, b as CheckType, j as CompleteMediaResult, k as ConsentLocale, l as DocOcrInput, D as DocReason, m as DocSide, n as FaceMatchInput, F as FaceMatchReason, g as HostedClient, o as HostedClientOptions, H as HostedOtpView, c as HostedSessionView, d as HostedStepView, p as LivenessInput, L as LivenessReason, M as MediaCheckType, q as MediaKind, r as PhoneOtpInput, P as PhoneOtpReason, s as PopulationInput, e as PopulationReason, t as PresignRequest, u as PresignUploadResult, R as RecordConsentResult, S as SessionOutcome, f as SessionStatus, v as SessionStepView, h as SessionView, w as SubmitCheckBody, x as SubmitOptions, U as UploadMedia, y as stripMimeParams } from '../hosted-client-BpvBqu9f.cjs';
2
+
3
+ /**
4
+ * Every error code the hosted page can receive from the session-token surface.
5
+ * A closed string-literal union — switch over {@link KnownCode} with a `never`
6
+ * default arm for compile-checked exhaustiveness.
7
+ */
8
+ declare const KNOWN_CODES: readonly ["session_token_invalid", "session_token_required", "session_token_session_mismatch", "session_not_found", "rate_limit_exceeded", "consent_required", "session_not_active", "check_not_found", "check_already_finalized", "check_out_of_order", "check_type_unsupported", "force_outcome_forbidden", "attestation_invalid", "nni_locked", "media_content_type_not_allowed", "media_too_large", "media_key_invalid", "media_key_forbidden", "media_not_found", "media_unreadable", "media_session_invalid", "idempotency_key_collision", "idempotency_in_progress", "idempotency_key_required", "validation_error", "internal_error", "unknown_error", "upload_failed", "network_error", "timeout"];
9
+ /** Closed union of {@link KNOWN_CODES} — exhaustively switchable. */
10
+ type KnownCode = (typeof KNOWN_CODES)[number];
11
+ /** Narrow an arbitrary wire code to the known union (unknown codes stay `string`). */
12
+ declare function isKnownCode(code: string): code is KnownCode;
13
+ /**
14
+ * Typed transport error: EVERY failure surfaces as one of these — non-2xx
15
+ * responses, rejected fetches, timeouts, and unparseable bodies alike. The
16
+ * hosted page never sees a raw TypeError/DOMException from the client.
17
+ *
18
+ * - `code` — the public envelope's `code`, or a client-synthesized fallback
19
+ * ('unknown_error' / 'upload_failed' / 'network_error' / 'timeout'). Typed
20
+ * `string` because the wire may grow codes; narrow with {@link isKnownCode}
21
+ * before an exhaustive switch.
22
+ * - `status` — the HTTP status, or 0 when no response arrived at all
23
+ * ('network_error' / 'timeout').
24
+ * - `retryAfterSeconds` — parsed from the `Retry-After` header when present
25
+ * (rate-limit 429s always set it).
26
+ */
27
+ declare class HostedApiError extends Error {
28
+ readonly status: number;
29
+ readonly code: string;
30
+ readonly retryAfterSeconds?: number;
31
+ constructor(args: {
32
+ status: number;
33
+ code: string;
34
+ message: string;
35
+ retryAfterSeconds?: number;
36
+ });
37
+ }
38
+
39
+ export { HostedApiError, KNOWN_CODES, type KnownCode, isKnownCode };
@@ -0,0 +1,39 @@
1
+ export { A as AttestationInput, i as CheckInputByType, C as CheckReason, a as CheckState, b as CheckType, j as CompleteMediaResult, k as ConsentLocale, l as DocOcrInput, D as DocReason, m as DocSide, n as FaceMatchInput, F as FaceMatchReason, g as HostedClient, o as HostedClientOptions, H as HostedOtpView, c as HostedSessionView, d as HostedStepView, p as LivenessInput, L as LivenessReason, M as MediaCheckType, q as MediaKind, r as PhoneOtpInput, P as PhoneOtpReason, s as PopulationInput, e as PopulationReason, t as PresignRequest, u as PresignUploadResult, R as RecordConsentResult, S as SessionOutcome, f as SessionStatus, v as SessionStepView, h as SessionView, w as SubmitCheckBody, x as SubmitOptions, U as UploadMedia, y as stripMimeParams } from '../hosted-client-BpvBqu9f.js';
2
+
3
+ /**
4
+ * Every error code the hosted page can receive from the session-token surface.
5
+ * A closed string-literal union — switch over {@link KnownCode} with a `never`
6
+ * default arm for compile-checked exhaustiveness.
7
+ */
8
+ declare const KNOWN_CODES: readonly ["session_token_invalid", "session_token_required", "session_token_session_mismatch", "session_not_found", "rate_limit_exceeded", "consent_required", "session_not_active", "check_not_found", "check_already_finalized", "check_out_of_order", "check_type_unsupported", "force_outcome_forbidden", "attestation_invalid", "nni_locked", "media_content_type_not_allowed", "media_too_large", "media_key_invalid", "media_key_forbidden", "media_not_found", "media_unreadable", "media_session_invalid", "idempotency_key_collision", "idempotency_in_progress", "idempotency_key_required", "validation_error", "internal_error", "unknown_error", "upload_failed", "network_error", "timeout"];
9
+ /** Closed union of {@link KNOWN_CODES} — exhaustively switchable. */
10
+ type KnownCode = (typeof KNOWN_CODES)[number];
11
+ /** Narrow an arbitrary wire code to the known union (unknown codes stay `string`). */
12
+ declare function isKnownCode(code: string): code is KnownCode;
13
+ /**
14
+ * Typed transport error: EVERY failure surfaces as one of these — non-2xx
15
+ * responses, rejected fetches, timeouts, and unparseable bodies alike. The
16
+ * hosted page never sees a raw TypeError/DOMException from the client.
17
+ *
18
+ * - `code` — the public envelope's `code`, or a client-synthesized fallback
19
+ * ('unknown_error' / 'upload_failed' / 'network_error' / 'timeout'). Typed
20
+ * `string` because the wire may grow codes; narrow with {@link isKnownCode}
21
+ * before an exhaustive switch.
22
+ * - `status` — the HTTP status, or 0 when no response arrived at all
23
+ * ('network_error' / 'timeout').
24
+ * - `retryAfterSeconds` — parsed from the `Retry-After` header when present
25
+ * (rate-limit 429s always set it).
26
+ */
27
+ declare class HostedApiError extends Error {
28
+ readonly status: number;
29
+ readonly code: string;
30
+ readonly retryAfterSeconds?: number;
31
+ constructor(args: {
32
+ status: number;
33
+ code: string;
34
+ message: string;
35
+ retryAfterSeconds?: number;
36
+ });
37
+ }
38
+
39
+ export { HostedApiError, KNOWN_CODES, type KnownCode, isKnownCode };
@@ -0,0 +1,15 @@
1
+ import {
2
+ HostedApiError,
3
+ HostedClient,
4
+ KNOWN_CODES,
5
+ isKnownCode,
6
+ stripMimeParams
7
+ } from "../chunk-YVFF4Q4G.js";
8
+ export {
9
+ HostedApiError,
10
+ HostedClient,
11
+ KNOWN_CODES,
12
+ isKnownCode,
13
+ stripMimeParams
14
+ };
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}