@smileid/usesmileid-nodejs 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +335 -0
- package/dist/client/auth.d.ts +32 -0
- package/dist/client/auth.js +83 -0
- package/dist/client/client.d.ts +52 -0
- package/dist/client/client.js +155 -0
- package/dist/client/config.d.ts +40 -0
- package/dist/client/config.js +52 -0
- package/dist/client/transport.d.ts +64 -0
- package/dist/client/transport.js +192 -0
- package/dist/errors/index.d.ts +91 -0
- package/dist/errors/index.js +143 -0
- package/dist/generated/models/index.d.ts +293 -0
- package/dist/generated/models/index.js +70 -0
- package/dist/generated/operations/index.d.ts +26 -0
- package/dist/generated/operations/index.js +421 -0
- package/dist/helpers/binary.d.ts +22 -0
- package/dist/helpers/binary.js +75 -0
- package/dist/helpers/case.d.ts +11 -0
- package/dist/helpers/case.js +29 -0
- package/dist/helpers/consent.d.ts +20 -0
- package/dist/helpers/consent.js +22 -0
- package/dist/helpers/jwt.d.ts +10 -0
- package/dist/helpers/jwt.js +30 -0
- package/dist/helpers/multipart.d.ts +49 -0
- package/dist/helpers/multipart.js +67 -0
- package/dist/helpers/poll.d.ts +13 -0
- package/dist/helpers/poll.js +35 -0
- package/dist/helpers/validation.d.ts +28 -0
- package/dist/helpers/validation.js +86 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +38 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +4 -0
- package/package.json +46 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client configuration and resolution (spec §2.1).
|
|
3
|
+
*/
|
|
4
|
+
import type { Environment } from '../generated/models/index.js';
|
|
5
|
+
/** The `fetch` implementation the SDK uses. Injectable for testing/proxies. */
|
|
6
|
+
export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
7
|
+
/** Public client configuration (spec §2.1). */
|
|
8
|
+
export interface SmileIDConfig {
|
|
9
|
+
/** Numeric partner id, no leading zeros. */
|
|
10
|
+
partnerId: string;
|
|
11
|
+
/** Partner API key. */
|
|
12
|
+
apiKey: string;
|
|
13
|
+
/** Environment. Sandbox by default. */
|
|
14
|
+
environment?: Environment;
|
|
15
|
+
/** Used when a call omits callbackUrl. */
|
|
16
|
+
defaultCallbackUrl?: string;
|
|
17
|
+
/** Explicit base URL override; wins over environment. */
|
|
18
|
+
baseUrl?: string;
|
|
19
|
+
/** Per-request total timeout in milliseconds. Default 30000. */
|
|
20
|
+
timeout?: number;
|
|
21
|
+
/** Retries for idempotent operations only. Default 2. */
|
|
22
|
+
maxRetries?: number;
|
|
23
|
+
/** Injectable fetch implementation. Defaults to the global fetch. */
|
|
24
|
+
fetch?: FetchLike;
|
|
25
|
+
}
|
|
26
|
+
/** Base URLs by environment (spec §2.1 — not in the OpenAPI spec, confirm before release). */
|
|
27
|
+
export declare const BASE_URLS: Record<Environment, string>;
|
|
28
|
+
/** Fully-resolved configuration with defaults applied. */
|
|
29
|
+
export interface ResolvedConfig {
|
|
30
|
+
partnerId: string;
|
|
31
|
+
apiKey: string;
|
|
32
|
+
environment: Environment;
|
|
33
|
+
defaultCallbackUrl: string | null;
|
|
34
|
+
baseUrl: string;
|
|
35
|
+
timeout: number;
|
|
36
|
+
maxRetries: number;
|
|
37
|
+
fetch: FetchLike;
|
|
38
|
+
}
|
|
39
|
+
/** Validate and resolve raw config into {@link ResolvedConfig}. */
|
|
40
|
+
export declare function resolveConfig(config: SmileIDConfig): ResolvedConfig;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Client configuration and resolution (spec §2.1).
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BASE_URLS = void 0;
|
|
7
|
+
exports.resolveConfig = resolveConfig;
|
|
8
|
+
const index_js_1 = require("../errors/index.js");
|
|
9
|
+
const validation_js_1 = require("../helpers/validation.js");
|
|
10
|
+
/** Base URLs by environment (spec §2.1 — not in the OpenAPI spec, confirm before release). */
|
|
11
|
+
exports.BASE_URLS = {
|
|
12
|
+
sandbox: 'https://testapi.smileidentity.com',
|
|
13
|
+
production: 'https://api.smileidentity.com',
|
|
14
|
+
};
|
|
15
|
+
const PARTNER_ID_PATTERN = /^[1-9]\d*$/;
|
|
16
|
+
/** Validate and resolve raw config into {@link ResolvedConfig}. */
|
|
17
|
+
function resolveConfig(config) {
|
|
18
|
+
if (!config.partnerId || !PARTNER_ID_PATTERN.test(config.partnerId)) {
|
|
19
|
+
throw new TypeError('partnerId must be a numeric string with no leading zeros.');
|
|
20
|
+
}
|
|
21
|
+
if (!config.apiKey) {
|
|
22
|
+
throw new TypeError('apiKey is required.');
|
|
23
|
+
}
|
|
24
|
+
const environment = config.environment ?? 'sandbox';
|
|
25
|
+
// Runtime guard for plain-JavaScript callers; the TS union covers TS callers.
|
|
26
|
+
if (environment !== 'sandbox' && environment !== 'production') {
|
|
27
|
+
throw new index_js_1.ValidationError({
|
|
28
|
+
message: 'environment must be "sandbox" or "production".',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const rawBaseUrl = config.baseUrl ?? exports.BASE_URLS[environment];
|
|
32
|
+
// Fleet standard: absolute https, no query or fragment. No escape hatch.
|
|
33
|
+
(0, validation_js_1.assertHttpsUrl)(rawBaseUrl, 'baseUrl', { forbidQueryAndFragment: true });
|
|
34
|
+
const baseUrl = rawBaseUrl.replace(/\/+$/, '');
|
|
35
|
+
if (config.defaultCallbackUrl !== undefined) {
|
|
36
|
+
(0, validation_js_1.assertHttpsUrl)(config.defaultCallbackUrl, 'defaultCallbackUrl');
|
|
37
|
+
}
|
|
38
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
39
|
+
if (!fetchImpl) {
|
|
40
|
+
throw new TypeError('No fetch implementation available. Node 18+ provides a global fetch, or pass one via config.fetch.');
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
partnerId: config.partnerId,
|
|
44
|
+
apiKey: config.apiKey,
|
|
45
|
+
environment,
|
|
46
|
+
defaultCallbackUrl: config.defaultCallbackUrl ?? null,
|
|
47
|
+
baseUrl,
|
|
48
|
+
timeout: config.timeout ?? 30000,
|
|
49
|
+
maxRetries: config.maxRetries ?? 2,
|
|
50
|
+
fetch: fetchImpl,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single internal transport (spec §2.2, §2.6, §2A).
|
|
3
|
+
*
|
|
4
|
+
* Every resource method funnels through here. It builds the URL, attaches auth
|
|
5
|
+
* and telemetry headers, serializes the body, sends, applies
|
|
6
|
+
* the retry policy, and parses the response into a typed result or error. It is
|
|
7
|
+
* the only layer that touches HTTP.
|
|
8
|
+
*/
|
|
9
|
+
import { TokenManager } from './auth.js';
|
|
10
|
+
import type { ResolvedConfig } from './config.js';
|
|
11
|
+
import type { SerializedMultipart } from '../helpers/multipart.js';
|
|
12
|
+
/** A fully-described request for {@link Transport.execute}. */
|
|
13
|
+
export interface RequestPlan {
|
|
14
|
+
method: 'GET' | 'POST';
|
|
15
|
+
/** Path with placeholders already substituted (e.g. /v3/status/job_123). */
|
|
16
|
+
path: string;
|
|
17
|
+
authenticated: boolean;
|
|
18
|
+
needsPartnerIdHeader: boolean;
|
|
19
|
+
/** True only for GETs and the token fetch (spec §2.6). */
|
|
20
|
+
idempotent: boolean;
|
|
21
|
+
query?: Record<string, string | undefined>;
|
|
22
|
+
/** Operation-specific headers (e.g. User-ID). */
|
|
23
|
+
headers?: Record<string, string | undefined>;
|
|
24
|
+
/** Multipart body, when the operation sends one. */
|
|
25
|
+
multipart?: SerializedMultipart;
|
|
26
|
+
/** When true, a final 404 returns the parsed body instead of raising (spec §6.8). */
|
|
27
|
+
allow404?: boolean;
|
|
28
|
+
/** Per-request options. */
|
|
29
|
+
timeout?: number;
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
}
|
|
32
|
+
/** The parsed outcome of a request. */
|
|
33
|
+
export interface TransportResult {
|
|
34
|
+
statusCode: number;
|
|
35
|
+
/** Parsed JSON body, or null if the body was empty / not JSON. */
|
|
36
|
+
json: unknown;
|
|
37
|
+
rawBody: string;
|
|
38
|
+
requestId: string | null;
|
|
39
|
+
}
|
|
40
|
+
/** Should this attempt be retried, given the operation and outcome (spec §2A). */
|
|
41
|
+
export declare function shouldRetry(idempotent: boolean, attempt: number, maxRetries: number, statusCode: number | null): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Backoff delay in ms: honour Retry-After when present (capped at 60s),
|
|
44
|
+
* else exponential + jitter.
|
|
45
|
+
*/
|
|
46
|
+
export declare function computeBackoff(attempt: number, retryAfterSeconds: number | null): number;
|
|
47
|
+
/**
|
|
48
|
+
* Parse a Retry-After header into seconds from now. Both RFC 7231 forms are
|
|
49
|
+
* honoured: delta-seconds ("2") and HTTP-date ("Wed, 01 Jul 2026 12:00:05 GMT").
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseRetryAfter(value: string | null): number | null;
|
|
52
|
+
export declare class Transport {
|
|
53
|
+
private readonly config;
|
|
54
|
+
private readonly sleep;
|
|
55
|
+
readonly tokenManager: TokenManager;
|
|
56
|
+
constructor(config: ResolvedConfig, sleep?: (ms: number) => Promise<void>);
|
|
57
|
+
/** The client-wide default callback URL, or null (spec §2.1). */
|
|
58
|
+
get defaultCallbackUrl(): string | null;
|
|
59
|
+
/** Telemetry headers, sent on every request (spec §2.4). */
|
|
60
|
+
telemetryHeaders(): Record<string, string>;
|
|
61
|
+
/** Execute a request plan, applying auth, retries, and error parsing. */
|
|
62
|
+
execute(plan: RequestPlan): Promise<TransportResult>;
|
|
63
|
+
private buildUrl;
|
|
64
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The single internal transport (spec §2.2, §2.6, §2A).
|
|
4
|
+
*
|
|
5
|
+
* Every resource method funnels through here. It builds the URL, attaches auth
|
|
6
|
+
* and telemetry headers, serializes the body, sends, applies
|
|
7
|
+
* the retry policy, and parses the response into a typed result or error. It is
|
|
8
|
+
* the only layer that touches HTTP.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.Transport = void 0;
|
|
12
|
+
exports.shouldRetry = shouldRetry;
|
|
13
|
+
exports.computeBackoff = computeBackoff;
|
|
14
|
+
exports.parseRetryAfter = parseRetryAfter;
|
|
15
|
+
const index_js_1 = require("../errors/index.js");
|
|
16
|
+
const auth_js_1 = require("./auth.js");
|
|
17
|
+
const version_js_1 = require("../version.js");
|
|
18
|
+
/** HTTP statuses that are retryable for idempotent operations (spec §2.6). */
|
|
19
|
+
const RETRYABLE_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
|
|
20
|
+
/** Base backoff in ms (SDK choice — not in the spec). */
|
|
21
|
+
const BACKOFF_BASE_MS = 50;
|
|
22
|
+
/** Cap on an honoured Retry-After delay (cross-SDK standard). */
|
|
23
|
+
const RETRY_AFTER_CAP_SECONDS = 60;
|
|
24
|
+
/** Should this attempt be retried, given the operation and outcome (spec §2A). */
|
|
25
|
+
function shouldRetry(idempotent, attempt, maxRetries, statusCode) {
|
|
26
|
+
if (attempt >= maxRetries)
|
|
27
|
+
return false;
|
|
28
|
+
if (!idempotent)
|
|
29
|
+
return false;
|
|
30
|
+
// 409 is explicitly excluded (business-state conflict, not transient).
|
|
31
|
+
if (statusCode === null)
|
|
32
|
+
return true; // connection error
|
|
33
|
+
return RETRYABLE_STATUSES.has(statusCode);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Backoff delay in ms: honour Retry-After when present (capped at 60s),
|
|
37
|
+
* else exponential + jitter.
|
|
38
|
+
*/
|
|
39
|
+
function computeBackoff(attempt, retryAfterSeconds) {
|
|
40
|
+
if (retryAfterSeconds !== null && retryAfterSeconds >= 0) {
|
|
41
|
+
return Math.min(retryAfterSeconds, RETRY_AFTER_CAP_SECONDS) * 1000;
|
|
42
|
+
}
|
|
43
|
+
const exponential = BACKOFF_BASE_MS * 2 ** attempt;
|
|
44
|
+
const jitter = Math.floor(Math.random() * BACKOFF_BASE_MS);
|
|
45
|
+
return exponential + jitter;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parse a Retry-After header into seconds from now. Both RFC 7231 forms are
|
|
49
|
+
* honoured: delta-seconds ("2") and HTTP-date ("Wed, 01 Jul 2026 12:00:05 GMT").
|
|
50
|
+
*/
|
|
51
|
+
function parseRetryAfter(value) {
|
|
52
|
+
if (!value)
|
|
53
|
+
return null;
|
|
54
|
+
const seconds = Number(value);
|
|
55
|
+
if (Number.isFinite(seconds))
|
|
56
|
+
return seconds;
|
|
57
|
+
const date = Date.parse(value);
|
|
58
|
+
if (Number.isFinite(date))
|
|
59
|
+
return Math.max(0, Math.round((date - Date.now()) / 1000));
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
class Transport {
|
|
63
|
+
config;
|
|
64
|
+
sleep;
|
|
65
|
+
tokenManager;
|
|
66
|
+
constructor(config, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
67
|
+
this.config = config;
|
|
68
|
+
this.sleep = sleep;
|
|
69
|
+
this.tokenManager = new auth_js_1.TokenManager(config.partnerId, config.apiKey, (plan) => this.execute(plan));
|
|
70
|
+
}
|
|
71
|
+
/** The client-wide default callback URL, or null (spec §2.1). */
|
|
72
|
+
get defaultCallbackUrl() {
|
|
73
|
+
return this.config.defaultCallbackUrl;
|
|
74
|
+
}
|
|
75
|
+
/** Telemetry headers, sent on every request (spec §2.4). */
|
|
76
|
+
telemetryHeaders() {
|
|
77
|
+
return {
|
|
78
|
+
'SmileID-Source-SDK': 'node',
|
|
79
|
+
'SmileID-Source-SDK-Version': version_js_1.VERSION,
|
|
80
|
+
'User-Agent': `smileid-sdk-node/${version_js_1.VERSION} (node/${process.version.replace(/^v/, '')})`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Execute a request plan, applying auth, retries, and error parsing. */
|
|
84
|
+
async execute(plan) {
|
|
85
|
+
const url = this.buildUrl(plan);
|
|
86
|
+
let attempt = 0;
|
|
87
|
+
let authRefreshed = false;
|
|
88
|
+
for (;;) {
|
|
89
|
+
if (plan.signal?.aborted) {
|
|
90
|
+
throw new index_js_1.ConnectionError({ message: 'Request aborted.' });
|
|
91
|
+
}
|
|
92
|
+
const headers = { ...this.telemetryHeaders() };
|
|
93
|
+
for (const [k, v] of Object.entries(plan.headers ?? {})) {
|
|
94
|
+
if (v !== undefined)
|
|
95
|
+
headers[k] = v;
|
|
96
|
+
}
|
|
97
|
+
if (plan.needsPartnerIdHeader)
|
|
98
|
+
headers['SmileID-Partner-ID'] = this.config.partnerId;
|
|
99
|
+
if (plan.authenticated) {
|
|
100
|
+
headers['SmileID-Token'] = await this.tokenManager.ensureToken();
|
|
101
|
+
}
|
|
102
|
+
let body;
|
|
103
|
+
if (plan.multipart) {
|
|
104
|
+
headers['Content-Type'] = plan.multipart.contentType;
|
|
105
|
+
body = plan.multipart.body;
|
|
106
|
+
}
|
|
107
|
+
const controller = new AbortController();
|
|
108
|
+
const timeoutMs = plan.timeout ?? this.config.timeout;
|
|
109
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
110
|
+
const onAbort = () => controller.abort();
|
|
111
|
+
plan.signal?.addEventListener('abort', onAbort);
|
|
112
|
+
let resp;
|
|
113
|
+
try {
|
|
114
|
+
resp = await this.config.fetch(url, {
|
|
115
|
+
method: plan.method,
|
|
116
|
+
headers,
|
|
117
|
+
body: body,
|
|
118
|
+
signal: controller.signal,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
// A caller-initiated abort is not a transient fault: never retry it.
|
|
123
|
+
if (plan.signal?.aborted) {
|
|
124
|
+
throw new index_js_1.ConnectionError({ message: 'Request aborted.' });
|
|
125
|
+
}
|
|
126
|
+
if (shouldRetry(plan.idempotent, attempt, this.config.maxRetries, null)) {
|
|
127
|
+
await this.sleep(computeBackoff(attempt, null));
|
|
128
|
+
attempt += 1;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
throw new index_js_1.ConnectionError({
|
|
132
|
+
message: err instanceof Error ? err.message : 'Network request failed.',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
clearTimeout(timer);
|
|
137
|
+
plan.signal?.removeEventListener('abort', onAbort);
|
|
138
|
+
}
|
|
139
|
+
const rawBody = await resp.text();
|
|
140
|
+
const requestId = resp.headers.get('x-request-id');
|
|
141
|
+
if (resp.status >= 200 && resp.status < 300) {
|
|
142
|
+
const json = safeJson(rawBody);
|
|
143
|
+
// Fleet standard: a success body must be a JSON object. The
|
|
144
|
+
// retrieve 404 → not_found path below is unaffected.
|
|
145
|
+
if (json === null || typeof json !== 'object' || Array.isArray(json)) {
|
|
146
|
+
throw new index_js_1.UnexpectedResponseError({
|
|
147
|
+
statusCode: resp.status,
|
|
148
|
+
message: 'Expected a JSON object in the response body.',
|
|
149
|
+
rawBody,
|
|
150
|
+
requestId,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return { statusCode: resp.status, json, rawBody, requestId };
|
|
154
|
+
}
|
|
155
|
+
// Refresh-on-401 once, then surface AuthenticationError (spec §2.3 item 5).
|
|
156
|
+
if (resp.status === 401 && plan.authenticated && !authRefreshed) {
|
|
157
|
+
this.tokenManager.invalidate();
|
|
158
|
+
authRefreshed = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (plan.allow404 && resp.status === 404) {
|
|
162
|
+
return { statusCode: resp.status, json: safeJson(rawBody), rawBody, requestId };
|
|
163
|
+
}
|
|
164
|
+
if (shouldRetry(plan.idempotent, attempt, this.config.maxRetries, resp.status)) {
|
|
165
|
+
const retryAfter = parseRetryAfter(resp.headers.get('retry-after'));
|
|
166
|
+
await this.sleep(computeBackoff(attempt, retryAfter));
|
|
167
|
+
attempt += 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
throw (0, index_js_1.parseError)({ statusCode: resp.status, rawBody, requestId });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
buildUrl(plan) {
|
|
174
|
+
const url = new URL(this.config.baseUrl + plan.path);
|
|
175
|
+
for (const [k, v] of Object.entries(plan.query ?? {})) {
|
|
176
|
+
if (v !== undefined)
|
|
177
|
+
url.searchParams.set(k, v);
|
|
178
|
+
}
|
|
179
|
+
return url.toString();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.Transport = Transport;
|
|
183
|
+
function safeJson(raw) {
|
|
184
|
+
if (!raw)
|
|
185
|
+
return null;
|
|
186
|
+
try {
|
|
187
|
+
return JSON.parse(raw);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error hierarchy (spec §7).
|
|
3
|
+
*
|
|
4
|
+
* One base class, {@link SmileIDError}, with typed subclasses keyed on HTTP
|
|
5
|
+
* status. Every error exposes the same accessor fields so callers can inspect
|
|
6
|
+
* a failure uniformly regardless of which wire error shape produced it.
|
|
7
|
+
*/
|
|
8
|
+
/** Fields shared by every SDK error. */
|
|
9
|
+
export interface SmileIDErrorFields {
|
|
10
|
+
/** HTTP status code, or null for connection / SDK-local errors. */
|
|
11
|
+
statusCode: number | null;
|
|
12
|
+
/** HTTP status text from the response body when present (e.g. "Bad Request"). */
|
|
13
|
+
status: string | null;
|
|
14
|
+
/** Human-readable message. */
|
|
15
|
+
message: string;
|
|
16
|
+
/** Machine code, present only on the services `{error, code}` wire shape. */
|
|
17
|
+
code: string | null;
|
|
18
|
+
/** Request id from a response header when one exists, else null. */
|
|
19
|
+
requestId: string | null;
|
|
20
|
+
/** The unparsed response body, when there was one. */
|
|
21
|
+
rawBody: string | null;
|
|
22
|
+
}
|
|
23
|
+
/** Base class for every error raised by the SDK. */
|
|
24
|
+
export declare class SmileIDError extends Error {
|
|
25
|
+
readonly statusCode: number | null;
|
|
26
|
+
readonly status: string | null;
|
|
27
|
+
readonly code: string | null;
|
|
28
|
+
readonly requestId: string | null;
|
|
29
|
+
readonly rawBody: string | null;
|
|
30
|
+
constructor(fields: Partial<SmileIDErrorFields> & {
|
|
31
|
+
message: string;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** 400 / 415 — malformed or unsupported request. Also used for local validation. */
|
|
35
|
+
export declare class InvalidRequestError extends SmileIDError {
|
|
36
|
+
}
|
|
37
|
+
/** 401 — authentication failed (raised after a failed token refresh). */
|
|
38
|
+
export declare class AuthenticationError extends SmileIDError {
|
|
39
|
+
}
|
|
40
|
+
/** 402 — insufficient wallet balance. */
|
|
41
|
+
export declare class PaymentRequiredError extends SmileIDError {
|
|
42
|
+
}
|
|
43
|
+
/** 403 — not authorized (includes the services `{error, code}` shape). */
|
|
44
|
+
export declare class PermissionError extends SmileIDError {
|
|
45
|
+
}
|
|
46
|
+
/** 404 — resource not found. Not raised by `verifications.retrieve` (see §6.8). */
|
|
47
|
+
export declare class NotFoundError extends SmileIDError {
|
|
48
|
+
}
|
|
49
|
+
/** 409 — business-state conflict (e.g. replay while still processing). Never auto-retried. */
|
|
50
|
+
export declare class ConflictError extends SmileIDError {
|
|
51
|
+
}
|
|
52
|
+
/** 413 — request payload too large. */
|
|
53
|
+
export declare class PayloadTooLargeError extends SmileIDError {
|
|
54
|
+
}
|
|
55
|
+
/** 429 — rate limited. */
|
|
56
|
+
export declare class RateLimitError extends SmileIDError {
|
|
57
|
+
}
|
|
58
|
+
/** 5xx — server-side API error. */
|
|
59
|
+
export declare class APIError extends SmileIDError {
|
|
60
|
+
}
|
|
61
|
+
/** Network failure or transport error with no HTTP response. */
|
|
62
|
+
export declare class ConnectionError extends SmileIDError {
|
|
63
|
+
}
|
|
64
|
+
/** A 2xx success response whose body is not a JSON object (fleet standard). */
|
|
65
|
+
export declare class UnexpectedResponseError extends SmileIDError {
|
|
66
|
+
}
|
|
67
|
+
/** SDK-local: raised by `verifications.waitUntilComplete` when the deadline passes. */
|
|
68
|
+
export declare class TimeoutError extends SmileIDError {
|
|
69
|
+
}
|
|
70
|
+
/** SDK-local: client-side validation failure raised before a request is sent. */
|
|
71
|
+
export declare class ValidationError extends InvalidRequestError {
|
|
72
|
+
}
|
|
73
|
+
/** Map an HTTP status code to the matching error class (spec §7 table). */
|
|
74
|
+
export declare function errorClassForStatus(status: number): new (fields: Partial<SmileIDErrorFields> & {
|
|
75
|
+
message: string;
|
|
76
|
+
}) => SmileIDError;
|
|
77
|
+
/** A minimal view of a parsed HTTP response, enough for {@link parseError}. */
|
|
78
|
+
export interface ErrorSource {
|
|
79
|
+
statusCode: number;
|
|
80
|
+
rawBody: string | null;
|
|
81
|
+
requestId: string | null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Turn a failed HTTP response into a typed error (spec §2A `parse_error`).
|
|
85
|
+
*
|
|
86
|
+
* Handles both wire shapes: `{status, message}` (used almost everywhere, and
|
|
87
|
+
* `{message, status}` on id_status — same keys, any order) and `{error, code}`
|
|
88
|
+
* (the three unauthenticated services endpoints). The class is chosen by HTTP
|
|
89
|
+
* status, never by body contents.
|
|
90
|
+
*/
|
|
91
|
+
export declare function parseError(source: ErrorSource): SmileIDError;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Error hierarchy (spec §7).
|
|
4
|
+
*
|
|
5
|
+
* One base class, {@link SmileIDError}, with typed subclasses keyed on HTTP
|
|
6
|
+
* status. Every error exposes the same accessor fields so callers can inspect
|
|
7
|
+
* a failure uniformly regardless of which wire error shape produced it.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.ValidationError = exports.TimeoutError = exports.UnexpectedResponseError = exports.ConnectionError = exports.APIError = exports.RateLimitError = exports.PayloadTooLargeError = exports.ConflictError = exports.NotFoundError = exports.PermissionError = exports.PaymentRequiredError = exports.AuthenticationError = exports.InvalidRequestError = exports.SmileIDError = void 0;
|
|
11
|
+
exports.errorClassForStatus = errorClassForStatus;
|
|
12
|
+
exports.parseError = parseError;
|
|
13
|
+
/** Base class for every error raised by the SDK. */
|
|
14
|
+
class SmileIDError extends Error {
|
|
15
|
+
statusCode;
|
|
16
|
+
status;
|
|
17
|
+
code;
|
|
18
|
+
requestId;
|
|
19
|
+
rawBody;
|
|
20
|
+
constructor(fields) {
|
|
21
|
+
super(fields.message);
|
|
22
|
+
this.name = new.target.name;
|
|
23
|
+
this.statusCode = fields.statusCode ?? null;
|
|
24
|
+
this.status = fields.status ?? null;
|
|
25
|
+
this.code = fields.code ?? null;
|
|
26
|
+
this.requestId = fields.requestId ?? null;
|
|
27
|
+
this.rawBody = fields.rawBody ?? null;
|
|
28
|
+
// Restore prototype chain when compiled down to older targets.
|
|
29
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.SmileIDError = SmileIDError;
|
|
33
|
+
/** 400 / 415 — malformed or unsupported request. Also used for local validation. */
|
|
34
|
+
class InvalidRequestError extends SmileIDError {
|
|
35
|
+
}
|
|
36
|
+
exports.InvalidRequestError = InvalidRequestError;
|
|
37
|
+
/** 401 — authentication failed (raised after a failed token refresh). */
|
|
38
|
+
class AuthenticationError extends SmileIDError {
|
|
39
|
+
}
|
|
40
|
+
exports.AuthenticationError = AuthenticationError;
|
|
41
|
+
/** 402 — insufficient wallet balance. */
|
|
42
|
+
class PaymentRequiredError extends SmileIDError {
|
|
43
|
+
}
|
|
44
|
+
exports.PaymentRequiredError = PaymentRequiredError;
|
|
45
|
+
/** 403 — not authorized (includes the services `{error, code}` shape). */
|
|
46
|
+
class PermissionError extends SmileIDError {
|
|
47
|
+
}
|
|
48
|
+
exports.PermissionError = PermissionError;
|
|
49
|
+
/** 404 — resource not found. Not raised by `verifications.retrieve` (see §6.8). */
|
|
50
|
+
class NotFoundError extends SmileIDError {
|
|
51
|
+
}
|
|
52
|
+
exports.NotFoundError = NotFoundError;
|
|
53
|
+
/** 409 — business-state conflict (e.g. replay while still processing). Never auto-retried. */
|
|
54
|
+
class ConflictError extends SmileIDError {
|
|
55
|
+
}
|
|
56
|
+
exports.ConflictError = ConflictError;
|
|
57
|
+
/** 413 — request payload too large. */
|
|
58
|
+
class PayloadTooLargeError extends SmileIDError {
|
|
59
|
+
}
|
|
60
|
+
exports.PayloadTooLargeError = PayloadTooLargeError;
|
|
61
|
+
/** 429 — rate limited. */
|
|
62
|
+
class RateLimitError extends SmileIDError {
|
|
63
|
+
}
|
|
64
|
+
exports.RateLimitError = RateLimitError;
|
|
65
|
+
/** 5xx — server-side API error. */
|
|
66
|
+
class APIError extends SmileIDError {
|
|
67
|
+
}
|
|
68
|
+
exports.APIError = APIError;
|
|
69
|
+
/** Network failure or transport error with no HTTP response. */
|
|
70
|
+
class ConnectionError extends SmileIDError {
|
|
71
|
+
}
|
|
72
|
+
exports.ConnectionError = ConnectionError;
|
|
73
|
+
/** A 2xx success response whose body is not a JSON object (fleet standard). */
|
|
74
|
+
class UnexpectedResponseError extends SmileIDError {
|
|
75
|
+
}
|
|
76
|
+
exports.UnexpectedResponseError = UnexpectedResponseError;
|
|
77
|
+
/** SDK-local: raised by `verifications.waitUntilComplete` when the deadline passes. */
|
|
78
|
+
class TimeoutError extends SmileIDError {
|
|
79
|
+
}
|
|
80
|
+
exports.TimeoutError = TimeoutError;
|
|
81
|
+
/** SDK-local: client-side validation failure raised before a request is sent. */
|
|
82
|
+
class ValidationError extends InvalidRequestError {
|
|
83
|
+
}
|
|
84
|
+
exports.ValidationError = ValidationError;
|
|
85
|
+
/** Map an HTTP status code to the matching error class (spec §7 table). */
|
|
86
|
+
function errorClassForStatus(status) {
|
|
87
|
+
if (status === 400 || status === 415)
|
|
88
|
+
return InvalidRequestError;
|
|
89
|
+
if (status === 401)
|
|
90
|
+
return AuthenticationError;
|
|
91
|
+
if (status === 402)
|
|
92
|
+
return PaymentRequiredError;
|
|
93
|
+
if (status === 403)
|
|
94
|
+
return PermissionError;
|
|
95
|
+
if (status === 404)
|
|
96
|
+
return NotFoundError;
|
|
97
|
+
if (status === 409)
|
|
98
|
+
return ConflictError;
|
|
99
|
+
if (status === 413)
|
|
100
|
+
return PayloadTooLargeError;
|
|
101
|
+
if (status === 429)
|
|
102
|
+
return RateLimitError;
|
|
103
|
+
if (status >= 500)
|
|
104
|
+
return APIError;
|
|
105
|
+
// Any other 4xx we do not model explicitly is treated as an invalid request.
|
|
106
|
+
return InvalidRequestError;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Turn a failed HTTP response into a typed error (spec §2A `parse_error`).
|
|
110
|
+
*
|
|
111
|
+
* Handles both wire shapes: `{status, message}` (used almost everywhere, and
|
|
112
|
+
* `{message, status}` on id_status — same keys, any order) and `{error, code}`
|
|
113
|
+
* (the three unauthenticated services endpoints). The class is chosen by HTTP
|
|
114
|
+
* status, never by body contents.
|
|
115
|
+
*/
|
|
116
|
+
function parseError(source) {
|
|
117
|
+
let body = null;
|
|
118
|
+
if (source.rawBody) {
|
|
119
|
+
try {
|
|
120
|
+
const parsed = JSON.parse(source.rawBody);
|
|
121
|
+
if (parsed && typeof parsed === 'object') {
|
|
122
|
+
body = parsed;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
body = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const message = (typeof body?.message === 'string' && body.message) ||
|
|
130
|
+
(typeof body?.error === 'string' && body.error) ||
|
|
131
|
+
`Request failed with status ${source.statusCode}`;
|
|
132
|
+
const code = typeof body?.code === 'string' ? body.code : null;
|
|
133
|
+
const status = typeof body?.status === 'string' ? body.status : null;
|
|
134
|
+
const ErrorClass = errorClassForStatus(source.statusCode);
|
|
135
|
+
return new ErrorClass({
|
|
136
|
+
statusCode: source.statusCode,
|
|
137
|
+
status,
|
|
138
|
+
message,
|
|
139
|
+
code,
|
|
140
|
+
requestId: source.requestId,
|
|
141
|
+
rawBody: source.rawBody,
|
|
142
|
+
});
|
|
143
|
+
}
|