@smsdora/otp 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SmsDora
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # @smsdora/otp
2
+
3
+ Official **SmsDora OTP** authentication SDK for Node.js. Send and verify one-time
4
+ passcodes over SMS through your SmsDora device fleet — with built-in retries,
5
+ idempotent sends, typed errors, and local verification-token validation.
6
+
7
+ - Zero runtime dependencies (uses built-in `fetch` + `node:crypto`)
8
+ - ESM **and** CommonJS, with full TypeScript types
9
+ - Node.js ≥ 18
10
+
11
+ > Server-side only. This SDK holds your **secret** API key — never ship it to a
12
+ > browser or mobile app. For client apps, use a SmsDora **publishable** key with
13
+ > the mobile SDKs.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @smsdora/otp
19
+ ```
20
+
21
+ ## Quick start
22
+
23
+ ```ts
24
+ import { SmsdoraOtp } from '@smsdora/otp';
25
+
26
+ const otp = new SmsdoraOtp({
27
+ apiKey: process.env.SMSDORA_SECRET_KEY!, // smsgw_... with otp:send + otp:verify
28
+ baseUrl: 'https://api.your-smsdora.com',
29
+ tokenSecret: process.env.OTP_TOKEN_SECRET, // optional — enables local verifyToken()
30
+ });
31
+
32
+ // 1. Send a code
33
+ const challenge = await otp.send({
34
+ recipient: '+14155550100',
35
+ purpose: 'login',
36
+ });
37
+ // challenge.id, challenge.expiresAt, challenge.recipient (masked)
38
+
39
+ // 2. Verify what the user typed
40
+ const result = await otp.verify({ challengeId: challenge.id, code: '123456' });
41
+
42
+ if (result.verified) {
43
+ // 3. Trust the verification locally (no extra network call)
44
+ const claims = otp.verifyToken(result.verificationToken!);
45
+ console.log('verified phone:', claims.sub, 'for', claims.purpose);
46
+ }
47
+ ```
48
+
49
+ ## API
50
+
51
+ ### `new SmsdoraOtp(options)`
52
+
53
+ | Option | Type | Default | Notes |
54
+ |---|---|---|---|
55
+ | `apiKey` | `string` | — | **Required.** Secret key with `otp:send` / `otp:verify`. |
56
+ | `baseUrl` | `string` | — | **Required.** Your SmsDora backend URL. |
57
+ | `tokenSecret` | `string` | — | Shared `OTP_TOKEN_SECRET`; needed for `verifyToken()`. |
58
+ | `timeoutMs` | `number` | `10000` | Per-request timeout. |
59
+ | `maxRetries` | `number` | `2` | Retries on network / 5xx / 429. |
60
+ | `fetch` | `typeof fetch` | global | Custom fetch implementation. |
61
+
62
+ ### `send(params) → Promise<OtpChallenge>`
63
+
64
+ ```ts
65
+ await otp.send({
66
+ recipient: '+14155550100',
67
+ purpose: 'login', // optional tag
68
+ codeLength: 6, // optional, 4–8
69
+ ttlSeconds: 300, // optional, 60–900
70
+ templateOverride: 'Your code is {{code}} ({{ttl}} min)', // optional, must contain {{code}}
71
+ metadata: { userId: 'u_123' }, // optional
72
+ idempotencyKey: 'order-42', // optional; auto-generated per call otherwise
73
+ });
74
+ ```
75
+
76
+ ### `verify(params) → Promise<VerifyResult>`
77
+
78
+ Returns `{ verified, status, attemptsRemaining, verificationToken? }`. A wrong
79
+ code resolves with `verified: false` (it does **not** throw); transport/auth
80
+ problems do throw.
81
+
82
+ ### `resend({ challengeId }) → Promise<OtpChallenge>`
83
+
84
+ Re-delivers a fresh code for a pending challenge. Respects the server's resend
85
+ cooldown and cap (throws `RateLimitError` when too soon / exhausted).
86
+
87
+ ### `getStatus(challengeId) → Promise<OtpChallenge>`
88
+
89
+ ### `verifyToken(token) → VerificationTokenPayload`
90
+
91
+ Validates a verification token **locally** (HS256, no network). Requires
92
+ `tokenSecret`. Throws `TokenVerificationError` if invalid/expired.
93
+
94
+ ### `verifyTokenRemote(token) → Promise<TokenCheckResult>`
95
+
96
+ Validates on the server with **single-use** enforcement (the token can't be
97
+ replayed). Use this when you need a hard one-time guarantee.
98
+
99
+ ## Error handling
100
+
101
+ Every thrown error extends `SmsdoraOtpError` and carries a stable `.code`:
102
+
103
+ ```ts
104
+ import { RateLimitError, DeliveryError, AuthError } from '@smsdora/otp';
105
+
106
+ try {
107
+ await otp.send({ recipient });
108
+ } catch (err) {
109
+ if (err instanceof RateLimitError) {
110
+ // err.retryAfterSeconds
111
+ } else if (err instanceof DeliveryError) {
112
+ // no online device / FCM failure — fall back to another channel
113
+ } else if (err instanceof AuthError) {
114
+ // bad key / missing scope
115
+ }
116
+ }
117
+ ```
118
+
119
+ | Class | `code` | When |
120
+ |---|---|---|
121
+ | `ConfigError` | `config_error` | Missing apiKey/baseUrl/tokenSecret. |
122
+ | `ValidationError` | `validation_error` | 400 — bad params. |
123
+ | `AuthError` | `auth_error` | 401/403. |
124
+ | `NotFoundError` | `not_found` | 404. |
125
+ | `RateLimitError` | `rate_limited` | 429 — has `retryAfterSeconds`. |
126
+ | `DeliveryError` | `delivery_failed` | 502 — could not deliver. |
127
+ | `ServerError` | `server_error` | 5xx. |
128
+ | `NetworkError` | `network_error` | Timeout / connection failure. |
129
+ | `TokenVerificationError` | `token_invalid` | Bad/expired verification token. |
130
+
131
+ ## Express example
132
+
133
+ See [`examples/express-passwordless.ts`](./examples/express-passwordless.ts)
134
+ for a complete phone-login flow.
135
+
136
+ ## License
137
+
138
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,361 @@
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/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AuthError: () => AuthError,
24
+ ConfigError: () => ConfigError,
25
+ DeliveryError: () => DeliveryError,
26
+ NetworkError: () => NetworkError,
27
+ NotFoundError: () => NotFoundError,
28
+ RateLimitError: () => RateLimitError,
29
+ ServerError: () => ServerError,
30
+ SmsdoraOtp: () => SmsdoraOtp,
31
+ SmsdoraOtpError: () => SmsdoraOtpError,
32
+ TokenVerificationError: () => TokenVerificationError,
33
+ ValidationError: () => ValidationError,
34
+ verifyVerificationToken: () => verifyVerificationToken
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/client.ts
39
+ var import_node_crypto2 = require("crypto");
40
+
41
+ // src/errors.ts
42
+ var SmsdoraOtpError = class extends Error {
43
+ code;
44
+ status;
45
+ requestId;
46
+ constructor(message, ctx) {
47
+ super(message, ctx.cause !== void 0 ? { cause: ctx.cause } : void 0);
48
+ this.name = new.target.name;
49
+ this.code = ctx.code;
50
+ this.status = ctx.status;
51
+ this.requestId = ctx.requestId;
52
+ }
53
+ };
54
+ var ConfigError = class extends SmsdoraOtpError {
55
+ constructor(message) {
56
+ super(message, { code: "config_error" });
57
+ }
58
+ };
59
+ var AuthError = class extends SmsdoraOtpError {
60
+ constructor(message, ctx = {}) {
61
+ super(message, { ...ctx, code: "auth_error" });
62
+ }
63
+ };
64
+ var ValidationError = class extends SmsdoraOtpError {
65
+ constructor(message, ctx = {}) {
66
+ super(message, { ...ctx, code: "validation_error" });
67
+ }
68
+ };
69
+ var NotFoundError = class extends SmsdoraOtpError {
70
+ constructor(message, ctx = {}) {
71
+ super(message, { ...ctx, code: "not_found" });
72
+ }
73
+ };
74
+ var RateLimitError = class extends SmsdoraOtpError {
75
+ retryAfterSeconds;
76
+ constructor(message, ctx = {}) {
77
+ super(message, { ...ctx, code: "rate_limited" });
78
+ this.retryAfterSeconds = ctx.retryAfterSeconds;
79
+ }
80
+ };
81
+ var DeliveryError = class extends SmsdoraOtpError {
82
+ constructor(message, ctx = {}) {
83
+ super(message, { ...ctx, code: "delivery_failed" });
84
+ }
85
+ };
86
+ var ServerError = class extends SmsdoraOtpError {
87
+ constructor(message, ctx = {}) {
88
+ super(message, { ...ctx, code: "server_error" });
89
+ }
90
+ };
91
+ var NetworkError = class extends SmsdoraOtpError {
92
+ constructor(message, ctx = {}) {
93
+ super(message, { ...ctx, code: "network_error" });
94
+ }
95
+ };
96
+ var TokenVerificationError = class extends SmsdoraOtpError {
97
+ constructor(message, ctx = {}) {
98
+ super(message, { ...ctx, code: "token_invalid" });
99
+ }
100
+ };
101
+
102
+ // src/http.ts
103
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
104
+ var HttpClient = class {
105
+ constructor(config) {
106
+ this.config = config;
107
+ }
108
+ config;
109
+ async request(method, path, options = {}) {
110
+ const url = `${this.config.baseUrl.replace(/\/+$/, "")}${path}`;
111
+ const headers = {
112
+ "x-api-key": this.config.apiKey,
113
+ accept: "application/json"
114
+ };
115
+ let bodyStr;
116
+ if (options.body !== void 0) {
117
+ headers["content-type"] = "application/json";
118
+ bodyStr = JSON.stringify(options.body);
119
+ }
120
+ if (options.idempotencyKey) {
121
+ headers["idempotency-key"] = options.idempotencyKey;
122
+ }
123
+ let lastError;
124
+ for (let attempt = 0; attempt <= this.config.maxRetries; attempt += 1) {
125
+ if (attempt > 0) {
126
+ await delay(this.backoffMs(attempt, lastError));
127
+ }
128
+ let response;
129
+ try {
130
+ response = await this.fetchWithTimeout(url, method, headers, bodyStr);
131
+ } catch (err) {
132
+ lastError = new NetworkError(
133
+ err instanceof Error && err.name === "AbortError" ? `Request timed out after ${this.config.timeoutMs}ms` : `Network request failed: ${err instanceof Error ? err.message : String(err)}`,
134
+ { cause: err }
135
+ );
136
+ if (attempt < this.config.maxRetries) continue;
137
+ throw lastError;
138
+ }
139
+ const envelope = await this.parseBody(response);
140
+ if (response.ok) {
141
+ return envelope?.data ?? envelope;
142
+ }
143
+ const error = this.toError(response, envelope);
144
+ if (RETRYABLE_STATUSES.has(response.status) && attempt < this.config.maxRetries) {
145
+ lastError = error;
146
+ continue;
147
+ }
148
+ throw error;
149
+ }
150
+ throw lastError ?? new NetworkError("Request failed");
151
+ }
152
+ async fetchWithTimeout(url, method, headers, body) {
153
+ const controller = new AbortController();
154
+ const timer = setTimeout(() => controller.abort(), this.config.timeoutMs);
155
+ try {
156
+ return await this.config.fetchImpl(url, {
157
+ method,
158
+ headers,
159
+ body,
160
+ signal: controller.signal
161
+ });
162
+ } finally {
163
+ clearTimeout(timer);
164
+ }
165
+ }
166
+ async parseBody(response) {
167
+ const text = await response.text();
168
+ if (!text) return null;
169
+ try {
170
+ return JSON.parse(text);
171
+ } catch {
172
+ return { message: text };
173
+ }
174
+ }
175
+ toError(response, envelope) {
176
+ const message = normalizeMessage(envelope) ?? `HTTP ${response.status}`;
177
+ const ctx = {
178
+ status: response.status,
179
+ requestId: response.headers.get("x-request-id") ?? void 0
180
+ };
181
+ switch (response.status) {
182
+ case 400:
183
+ return new ValidationError(message, ctx);
184
+ case 401:
185
+ case 403:
186
+ return new AuthError(message, ctx);
187
+ case 404:
188
+ return new NotFoundError(message, ctx);
189
+ case 429:
190
+ return new RateLimitError(message, {
191
+ ...ctx,
192
+ retryAfterSeconds: envelope?.retryAfterSeconds ?? parseRetryAfter(response.headers.get("retry-after"))
193
+ });
194
+ case 502:
195
+ return new DeliveryError(message, ctx);
196
+ default:
197
+ return new ServerError(message, ctx);
198
+ }
199
+ }
200
+ backoffMs(attempt, lastError) {
201
+ if (lastError instanceof RateLimitError && lastError.retryAfterSeconds) {
202
+ return Math.min(lastError.retryAfterSeconds * 1e3, 1e4);
203
+ }
204
+ const base = 200 * 2 ** (attempt - 1);
205
+ return Math.round(base * (0.5 + Math.random() * 0.5));
206
+ }
207
+ };
208
+ function normalizeMessage(envelope) {
209
+ if (!envelope?.message) return void 0;
210
+ return Array.isArray(envelope.message) ? envelope.message.join("; ") : envelope.message;
211
+ }
212
+ function parseRetryAfter(header) {
213
+ if (!header) return void 0;
214
+ const seconds = Number(header);
215
+ return Number.isFinite(seconds) ? seconds : void 0;
216
+ }
217
+ function delay(ms) {
218
+ return new Promise((resolve) => setTimeout(resolve, ms));
219
+ }
220
+
221
+ // src/token.ts
222
+ var import_node_crypto = require("crypto");
223
+ function base64UrlToBuffer(input) {
224
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
225
+ const pad = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
226
+ return Buffer.from(padded + pad, "base64");
227
+ }
228
+ function verifyVerificationToken(token, secret) {
229
+ const parts = token.split(".");
230
+ if (parts.length !== 3) {
231
+ throw new TokenVerificationError("Malformed token");
232
+ }
233
+ const [headerB64, payloadB64, signatureB64] = parts;
234
+ let header;
235
+ try {
236
+ header = JSON.parse(base64UrlToBuffer(headerB64).toString("utf8"));
237
+ } catch {
238
+ throw new TokenVerificationError("Malformed token header");
239
+ }
240
+ if (header.alg !== "HS256") {
241
+ throw new TokenVerificationError(
242
+ `Unsupported token algorithm: ${header.alg ?? "unknown"}`
243
+ );
244
+ }
245
+ const expected = (0, import_node_crypto.createHmac)("sha256", secret).update(`${headerB64}.${payloadB64}`).digest();
246
+ const actual = base64UrlToBuffer(signatureB64);
247
+ if (expected.length !== actual.length || !(0, import_node_crypto.timingSafeEqual)(expected, actual)) {
248
+ throw new TokenVerificationError("Invalid token signature");
249
+ }
250
+ let payload;
251
+ try {
252
+ payload = JSON.parse(base64UrlToBuffer(payloadB64).toString("utf8"));
253
+ } catch {
254
+ throw new TokenVerificationError("Malformed token payload");
255
+ }
256
+ const now = Math.floor(Date.now() / 1e3);
257
+ if (typeof payload.exp === "number" && now >= payload.exp) {
258
+ throw new TokenVerificationError("Token has expired");
259
+ }
260
+ const nbf = payload.nbf;
261
+ if (typeof nbf === "number" && now < nbf) {
262
+ throw new TokenVerificationError("Token is not yet valid");
263
+ }
264
+ return payload;
265
+ }
266
+
267
+ // src/client.ts
268
+ var SmsdoraOtp = class {
269
+ http;
270
+ tokenSecret;
271
+ constructor(options) {
272
+ if (!options?.apiKey) {
273
+ throw new ConfigError("`apiKey` is required");
274
+ }
275
+ if (!options.baseUrl) {
276
+ throw new ConfigError("`baseUrl` is required");
277
+ }
278
+ const fetchImpl = options.fetch ?? globalThis.fetch;
279
+ if (typeof fetchImpl !== "function") {
280
+ throw new ConfigError(
281
+ "No global `fetch` available. Use Node >= 18, or pass a `fetch` implementation."
282
+ );
283
+ }
284
+ this.tokenSecret = options.tokenSecret;
285
+ this.http = new HttpClient({
286
+ apiKey: options.apiKey,
287
+ baseUrl: options.baseUrl,
288
+ timeoutMs: options.timeoutMs ?? 1e4,
289
+ maxRetries: options.maxRetries ?? 2,
290
+ fetchImpl
291
+ });
292
+ }
293
+ /** Send a one-time passcode to a phone number. */
294
+ async send(params) {
295
+ const { idempotencyKey, ...rest } = params;
296
+ return this.http.request("POST", "/otp/send", {
297
+ body: rest,
298
+ idempotencyKey: idempotencyKey ?? (0, import_node_crypto2.randomUUID)()
299
+ });
300
+ }
301
+ /** Re-deliver the code for an existing challenge (subject to cooldown/cap). */
302
+ async resend(params) {
303
+ return this.http.request("POST", "/otp/resend", {
304
+ body: params
305
+ });
306
+ }
307
+ /** Verify a code. Returns a result; does NOT throw on a wrong code. */
308
+ async verify(params) {
309
+ return this.http.request("POST", "/otp/verify", {
310
+ body: params
311
+ });
312
+ }
313
+ /** Fetch the current status of a challenge. */
314
+ async getStatus(challengeId) {
315
+ return this.http.request(
316
+ "GET",
317
+ `/otp/${encodeURIComponent(challengeId)}`
318
+ );
319
+ }
320
+ /**
321
+ * Validate a verification token locally (no network). Requires `tokenSecret`
322
+ * in the client options. Use this in your auth flow to trust a verification
323
+ * without an extra round-trip.
324
+ *
325
+ * @throws {ConfigError} if no `tokenSecret` was configured.
326
+ * @throws {TokenVerificationError} if the token is invalid/expired.
327
+ */
328
+ verifyToken(token) {
329
+ if (!this.tokenSecret) {
330
+ throw new ConfigError(
331
+ "`tokenSecret` must be set to verify tokens locally. Pass it in the client options, or use verifyTokenRemote()."
332
+ );
333
+ }
334
+ return verifyVerificationToken(token, this.tokenSecret);
335
+ }
336
+ /**
337
+ * Validate a verification token on the server with single-use enforcement.
338
+ * Marks the token consumed so it cannot be replayed.
339
+ */
340
+ async verifyTokenRemote(token) {
341
+ return this.http.request("POST", "/otp/verify-token", {
342
+ body: { token }
343
+ });
344
+ }
345
+ };
346
+ // Annotate the CommonJS export names for ESM import in node:
347
+ 0 && (module.exports = {
348
+ AuthError,
349
+ ConfigError,
350
+ DeliveryError,
351
+ NetworkError,
352
+ NotFoundError,
353
+ RateLimitError,
354
+ ServerError,
355
+ SmsdoraOtp,
356
+ SmsdoraOtpError,
357
+ TokenVerificationError,
358
+ ValidationError,
359
+ verifyVerificationToken
360
+ });
361
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/errors.ts","../src/http.ts","../src/token.ts"],"sourcesContent":["export { SmsdoraOtp } from './client.js';\nexport { verifyVerificationToken } from './token.js';\n\nexport {\n SmsdoraOtpError,\n ConfigError,\n AuthError,\n ValidationError,\n NotFoundError,\n RateLimitError,\n DeliveryError,\n ServerError,\n NetworkError,\n TokenVerificationError,\n type SmsdoraOtpErrorCode,\n} from './errors.js';\n\nexport type {\n SmsdoraOtpOptions,\n SendOtpParams,\n ResendOtpParams,\n VerifyOtpParams,\n OtpChallenge,\n OtpChannel,\n OtpStatus,\n VerifyResult,\n VerificationTokenPayload,\n TokenCheckResult,\n} from './types.js';\n","import { randomUUID } from 'node:crypto';\nimport { HttpClient } from './http.js';\nimport { ConfigError } from './errors.js';\nimport { verifyVerificationToken } from './token.js';\nimport type {\n OtpChallenge,\n ResendOtpParams,\n SendOtpParams,\n SmsdoraOtpOptions,\n TokenCheckResult,\n VerificationTokenPayload,\n VerifyOtpParams,\n VerifyResult,\n} from './types.js';\n\n/**\n * Client for the SmsDora OTP API.\n *\n * ```ts\n * const otp = new SmsdoraOtp({ apiKey: process.env.SMSDORA_KEY!, baseUrl });\n * const challenge = await otp.send({ recipient: '+14155550100', purpose: 'login' });\n * const result = await otp.verify({ challengeId: challenge.id, code });\n * if (result.verified) { ... }\n * ```\n */\nexport class SmsdoraOtp {\n private readonly http: HttpClient;\n private readonly tokenSecret?: string;\n\n constructor(options: SmsdoraOtpOptions) {\n if (!options?.apiKey) {\n throw new ConfigError('`apiKey` is required');\n }\n if (!options.baseUrl) {\n throw new ConfigError('`baseUrl` is required');\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new ConfigError(\n 'No global `fetch` available. Use Node >= 18, or pass a `fetch` implementation.',\n );\n }\n\n this.tokenSecret = options.tokenSecret;\n this.http = new HttpClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n timeoutMs: options.timeoutMs ?? 10_000,\n maxRetries: options.maxRetries ?? 2,\n fetchImpl,\n });\n }\n\n /** Send a one-time passcode to a phone number. */\n async send(params: SendOtpParams): Promise<OtpChallenge> {\n const { idempotencyKey, ...rest } = params;\n return this.http.request<OtpChallenge>('POST', '/otp/send', {\n body: rest,\n idempotencyKey: idempotencyKey ?? randomUUID(),\n });\n }\n\n /** Re-deliver the code for an existing challenge (subject to cooldown/cap). */\n async resend(params: ResendOtpParams): Promise<OtpChallenge> {\n return this.http.request<OtpChallenge>('POST', '/otp/resend', {\n body: params,\n });\n }\n\n /** Verify a code. Returns a result; does NOT throw on a wrong code. */\n async verify(params: VerifyOtpParams): Promise<VerifyResult> {\n return this.http.request<VerifyResult>('POST', '/otp/verify', {\n body: params,\n });\n }\n\n /** Fetch the current status of a challenge. */\n async getStatus(challengeId: string): Promise<OtpChallenge> {\n return this.http.request<OtpChallenge>(\n 'GET',\n `/otp/${encodeURIComponent(challengeId)}`,\n );\n }\n\n /**\n * Validate a verification token locally (no network). Requires `tokenSecret`\n * in the client options. Use this in your auth flow to trust a verification\n * without an extra round-trip.\n *\n * @throws {ConfigError} if no `tokenSecret` was configured.\n * @throws {TokenVerificationError} if the token is invalid/expired.\n */\n verifyToken(token: string): VerificationTokenPayload {\n if (!this.tokenSecret) {\n throw new ConfigError(\n '`tokenSecret` must be set to verify tokens locally. ' +\n 'Pass it in the client options, or use verifyTokenRemote().',\n );\n }\n return verifyVerificationToken(token, this.tokenSecret);\n }\n\n /**\n * Validate a verification token on the server with single-use enforcement.\n * Marks the token consumed so it cannot be replayed.\n */\n async verifyTokenRemote(token: string): Promise<TokenCheckResult> {\n return this.http.request<TokenCheckResult>('POST', '/otp/verify-token', {\n body: { token },\n });\n }\n}\n","/** Stable error codes — safe to branch on across SDK versions. */\nexport type SmsdoraOtpErrorCode =\n | 'config_error'\n | 'auth_error'\n | 'validation_error'\n | 'not_found'\n | 'rate_limited'\n | 'delivery_failed'\n | 'server_error'\n | 'network_error'\n | 'token_invalid';\n\nexport interface SmsdoraOtpErrorContext {\n code: SmsdoraOtpErrorCode;\n /** HTTP status, when the error originated from a response. */\n status?: number;\n /** Server request id, if provided. */\n requestId?: string;\n /** Underlying cause (e.g. a fetch error). */\n cause?: unknown;\n}\n\n/** Base class for every error the SDK throws. */\nexport class SmsdoraOtpError extends Error {\n readonly code: SmsdoraOtpErrorCode;\n readonly status?: number;\n readonly requestId?: string;\n\n constructor(message: string, ctx: SmsdoraOtpErrorContext) {\n super(message, ctx.cause !== undefined ? { cause: ctx.cause } : undefined);\n this.name = new.target.name;\n this.code = ctx.code;\n this.status = ctx.status;\n this.requestId = ctx.requestId;\n }\n}\n\n/** Missing/invalid client configuration (no apiKey, no tokenSecret, …). */\nexport class ConfigError extends SmsdoraOtpError {\n constructor(message: string) {\n super(message, { code: 'config_error' });\n }\n}\n\n/** 401 / 403 — bad or unauthorized API key, scope, or publishable restriction. */\nexport class AuthError extends SmsdoraOtpError {\n constructor(message: string, ctx: Omit<SmsdoraOtpErrorContext, 'code'> = {}) {\n super(message, { ...ctx, code: 'auth_error' });\n }\n}\n\n/** 400 — invalid request parameters. */\nexport class ValidationError extends SmsdoraOtpError {\n constructor(message: string, ctx: Omit<SmsdoraOtpErrorContext, 'code'> = {}) {\n super(message, { ...ctx, code: 'validation_error' });\n }\n}\n\n/** 404 — challenge/key not found. */\nexport class NotFoundError extends SmsdoraOtpError {\n constructor(message: string, ctx: Omit<SmsdoraOtpErrorContext, 'code'> = {}) {\n super(message, { ...ctx, code: 'not_found' });\n }\n}\n\n/** 429 — a rate/abuse cap was hit. Inspect {@link retryAfterSeconds}. */\nexport class RateLimitError extends SmsdoraOtpError {\n readonly retryAfterSeconds?: number;\n constructor(\n message: string,\n ctx: Omit<SmsdoraOtpErrorContext, 'code'> & { retryAfterSeconds?: number } = {},\n ) {\n super(message, { ...ctx, code: 'rate_limited' });\n this.retryAfterSeconds = ctx.retryAfterSeconds;\n }\n}\n\n/** 502 — the code could not be delivered (no online device / FCM failure). */\nexport class DeliveryError extends SmsdoraOtpError {\n constructor(message: string, ctx: Omit<SmsdoraOtpErrorContext, 'code'> = {}) {\n super(message, { ...ctx, code: 'delivery_failed' });\n }\n}\n\n/** 5xx — an unexpected server error. */\nexport class ServerError extends SmsdoraOtpError {\n constructor(message: string, ctx: Omit<SmsdoraOtpErrorContext, 'code'> = {}) {\n super(message, { ...ctx, code: 'server_error' });\n }\n}\n\n/** Transport failure — connection refused, DNS, timeout, aborted. */\nexport class NetworkError extends SmsdoraOtpError {\n constructor(message: string, ctx: Omit<SmsdoraOtpErrorContext, 'code'> = {}) {\n super(message, { ...ctx, code: 'network_error' });\n }\n}\n\n/** A verification token failed local or remote validation. */\nexport class TokenVerificationError extends SmsdoraOtpError {\n constructor(message: string, ctx: Omit<SmsdoraOtpErrorContext, 'code'> = {}) {\n super(message, { ...ctx, code: 'token_invalid' });\n }\n}\n","import {\n AuthError,\n DeliveryError,\n NetworkError,\n NotFoundError,\n RateLimitError,\n ServerError,\n ValidationError,\n type SmsdoraOtpErrorContext,\n} from './errors.js';\n\nexport interface HttpClientConfig {\n apiKey: string;\n baseUrl: string;\n timeoutMs: number;\n maxRetries: number;\n fetchImpl: typeof fetch;\n}\n\ninterface RequestOptions {\n body?: unknown;\n idempotencyKey?: string;\n}\n\ninterface ApiEnvelope<T> {\n statusCode?: number;\n message?: string | string[];\n data?: T;\n retryAfterSeconds?: number;\n error?: string;\n}\n\nconst RETRYABLE_STATUSES = new Set([429, 500, 502, 503, 504]);\n\n/** Thin fetch wrapper: auth headers, timeout, retry with backoff, error mapping. */\nexport class HttpClient {\n constructor(private readonly config: HttpClientConfig) {}\n\n async request<T>(\n method: string,\n path: string,\n options: RequestOptions = {},\n ): Promise<T> {\n const url = `${this.config.baseUrl.replace(/\\/+$/, '')}${path}`;\n const headers: Record<string, string> = {\n 'x-api-key': this.config.apiKey,\n accept: 'application/json',\n };\n let bodyStr: string | undefined;\n if (options.body !== undefined) {\n headers['content-type'] = 'application/json';\n bodyStr = JSON.stringify(options.body);\n }\n if (options.idempotencyKey) {\n headers['idempotency-key'] = options.idempotencyKey;\n }\n\n let lastError: unknown;\n for (let attempt = 0; attempt <= this.config.maxRetries; attempt += 1) {\n if (attempt > 0) {\n await delay(this.backoffMs(attempt, lastError));\n }\n\n let response: Response;\n try {\n response = await this.fetchWithTimeout(url, method, headers, bodyStr);\n } catch (err) {\n lastError = new NetworkError(\n err instanceof Error && err.name === 'AbortError'\n ? `Request timed out after ${this.config.timeoutMs}ms`\n : `Network request failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n { cause: err },\n );\n if (attempt < this.config.maxRetries) continue;\n throw lastError;\n }\n\n const envelope = await this.parseBody<T>(response);\n\n if (response.ok) {\n return (envelope?.data ?? (envelope as unknown as T)) as T;\n }\n\n const error = this.toError(response, envelope);\n if (RETRYABLE_STATUSES.has(response.status) && attempt < this.config.maxRetries) {\n lastError = error;\n continue;\n }\n throw error;\n }\n\n // Unreachable in practice — the loop always returns or throws.\n throw lastError ?? new NetworkError('Request failed');\n }\n\n private async fetchWithTimeout(\n url: string,\n method: string,\n headers: Record<string, string>,\n body: string | undefined,\n ): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.config.timeoutMs);\n try {\n return await this.config.fetchImpl(url, {\n method,\n headers,\n body,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async parseBody<T>(response: Response): Promise<ApiEnvelope<T> | null> {\n const text = await response.text();\n if (!text) return null;\n try {\n return JSON.parse(text) as ApiEnvelope<T>;\n } catch {\n return { message: text };\n }\n }\n\n private toError<T>(response: Response, envelope: ApiEnvelope<T> | null) {\n const message = normalizeMessage(envelope) ?? `HTTP ${response.status}`;\n const ctx: Omit<SmsdoraOtpErrorContext, 'code'> = {\n status: response.status,\n requestId: response.headers.get('x-request-id') ?? undefined,\n };\n\n switch (response.status) {\n case 400:\n return new ValidationError(message, ctx);\n case 401:\n case 403:\n return new AuthError(message, ctx);\n case 404:\n return new NotFoundError(message, ctx);\n case 429:\n return new RateLimitError(message, {\n ...ctx,\n retryAfterSeconds:\n envelope?.retryAfterSeconds ??\n parseRetryAfter(response.headers.get('retry-after')),\n });\n case 502:\n return new DeliveryError(message, ctx);\n default:\n return new ServerError(message, ctx);\n }\n }\n\n private backoffMs(attempt: number, lastError: unknown): number {\n // Honor an explicit retry-after on rate limits (capped at 10s).\n if (lastError instanceof RateLimitError && lastError.retryAfterSeconds) {\n return Math.min(lastError.retryAfterSeconds * 1000, 10_000);\n }\n // Exponential backoff with full jitter: ~200ms, 400ms, 800ms…\n const base = 200 * 2 ** (attempt - 1);\n return Math.round(base * (0.5 + Math.random() * 0.5));\n }\n}\n\nfunction normalizeMessage<T>(envelope: ApiEnvelope<T> | null): string | undefined {\n if (!envelope?.message) return undefined;\n return Array.isArray(envelope.message)\n ? envelope.message.join('; ')\n : envelope.message;\n}\n\nfunction parseRetryAfter(header: string | null): number | undefined {\n if (!header) return undefined;\n const seconds = Number(header);\n return Number.isFinite(seconds) ? seconds : undefined;\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto';\nimport { TokenVerificationError } from './errors.js';\nimport type { VerificationTokenPayload } from './types.js';\n\nfunction base64UrlToBuffer(input: string): Buffer {\n const padded = input.replace(/-/g, '+').replace(/_/g, '/');\n const pad = padded.length % 4 === 0 ? '' : '='.repeat(4 - (padded.length % 4));\n return Buffer.from(padded + pad, 'base64');\n}\n\n/**\n * Verify a SmsDora verification token (HS256 JWT) locally — no network call.\n * Checks the signature against `secret` and enforces `exp` / `nbf`.\n *\n * @throws {TokenVerificationError} if malformed, wrong algorithm, bad\n * signature, or expired.\n */\nexport function verifyVerificationToken(\n token: string,\n secret: string,\n): VerificationTokenPayload {\n const parts = token.split('.');\n if (parts.length !== 3) {\n throw new TokenVerificationError('Malformed token');\n }\n const [headerB64, payloadB64, signatureB64] = parts as [\n string,\n string,\n string,\n ];\n\n let header: { alg?: string; typ?: string };\n try {\n header = JSON.parse(base64UrlToBuffer(headerB64).toString('utf8'));\n } catch {\n throw new TokenVerificationError('Malformed token header');\n }\n\n if (header.alg !== 'HS256') {\n throw new TokenVerificationError(\n `Unsupported token algorithm: ${header.alg ?? 'unknown'}`,\n );\n }\n\n const expected = createHmac('sha256', secret)\n .update(`${headerB64}.${payloadB64}`)\n .digest();\n const actual = base64UrlToBuffer(signatureB64);\n\n if (\n expected.length !== actual.length ||\n !timingSafeEqual(expected, actual)\n ) {\n throw new TokenVerificationError('Invalid token signature');\n }\n\n let payload: VerificationTokenPayload;\n try {\n payload = JSON.parse(base64UrlToBuffer(payloadB64).toString('utf8'));\n } catch {\n throw new TokenVerificationError('Malformed token payload');\n }\n\n const now = Math.floor(Date.now() / 1000);\n if (typeof payload.exp === 'number' && now >= payload.exp) {\n throw new TokenVerificationError('Token has expired');\n }\n const nbf = (payload as { nbf?: number }).nbf;\n if (typeof nbf === 'number' && now < nbf) {\n throw new TokenVerificationError('Token is not yet valid');\n }\n\n return payload;\n}\n"],"mappings":"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","names":["import_node_crypto"]}