@erpgulf/auth-sdk 0.1.0-beta.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.
package/dist/index.js ADDED
@@ -0,0 +1,756 @@
1
+ // src/errors/AuthError.ts
2
+ var AuthError = class extends Error {
3
+ code;
4
+ httpStatus;
5
+ retryable;
6
+ constructor(code, message, options = {}) {
7
+ super(
8
+ message,
9
+ options.cause === void 0 ? void 0 : { cause: options.cause }
10
+ );
11
+ this.name = "AuthError";
12
+ this.code = code;
13
+ this.httpStatus = options.httpStatus;
14
+ this.retryable = options.retryable ?? false;
15
+ }
16
+ };
17
+ function isAuthError(error) {
18
+ return error instanceof AuthError;
19
+ }
20
+
21
+ // src/form/encodeForm.ts
22
+ function encodeForm(values) {
23
+ const form = new URLSearchParams();
24
+ for (const [key, value] of Object.entries(values)) {
25
+ if (value !== void 0) {
26
+ form.append(key, String(value));
27
+ }
28
+ }
29
+ return form.toString();
30
+ }
31
+
32
+ // src/master-token/MasterTokenManager.ts
33
+ var MASTER_TOKEN_EXPIRY_SKEW_MS = 3e4;
34
+ var MasterTokenManager = class {
35
+ #credential;
36
+ #inFlight;
37
+ expirySkewMs;
38
+ fetchCredential;
39
+ now;
40
+ constructor(options) {
41
+ this.fetchCredential = options.fetchCredential;
42
+ this.now = options.now ?? Date.now;
43
+ this.expirySkewMs = options.expirySkewMs ?? MASTER_TOKEN_EXPIRY_SKEW_MS;
44
+ }
45
+ async getCredential() {
46
+ if (this.isUsable(this.#credential)) {
47
+ return this.#credential;
48
+ }
49
+ if (this.#inFlight !== void 0) {
50
+ return this.#inFlight;
51
+ }
52
+ const acquisition = this.acquire();
53
+ this.#inFlight = acquisition;
54
+ try {
55
+ return await acquisition;
56
+ } finally {
57
+ if (this.#inFlight === acquisition) {
58
+ this.#inFlight = void 0;
59
+ }
60
+ }
61
+ }
62
+ invalidate(expectedAccessToken) {
63
+ if (expectedAccessToken === void 0 || this.#credential?.accessToken === expectedAccessToken) {
64
+ this.#credential = void 0;
65
+ }
66
+ }
67
+ async acquire() {
68
+ const credential = await this.fetchCredential();
69
+ this.#credential = credential;
70
+ return credential;
71
+ }
72
+ isUsable(credential) {
73
+ return credential !== void 0 && this.now() < credential.expiresAt - this.expirySkewMs;
74
+ }
75
+ };
76
+
77
+ // src/url/baseUrl.ts
78
+ function normalizeBaseUrl(input, options = {}) {
79
+ const trimmed = input.trim();
80
+ let url;
81
+ try {
82
+ url = new URL(trimmed);
83
+ } catch {
84
+ throw invalidBaseUrl("Base URL must be a valid absolute URL.");
85
+ }
86
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
87
+ throw invalidBaseUrl("Base URL must use the HTTPS or HTTP protocol.");
88
+ }
89
+ if (url.protocol === "http:" && options.allowInsecureHttp !== true) {
90
+ throw invalidBaseUrl(
91
+ "HTTP base URLs require allowInsecureHttp: true and should only be used for development."
92
+ );
93
+ }
94
+ if (url.hostname.length === 0) {
95
+ throw invalidBaseUrl("Base URL must include a hostname.");
96
+ }
97
+ if (url.username.length > 0 || url.password.length > 0) {
98
+ throw invalidBaseUrl("Base URL must not contain embedded credentials.");
99
+ }
100
+ if (url.hash.length > 0) {
101
+ throw invalidBaseUrl("Base URL must not contain a fragment.");
102
+ }
103
+ if (url.search.length > 0) {
104
+ throw invalidBaseUrl("Base URL must not contain a query string.");
105
+ }
106
+ if (url.pathname !== "/") {
107
+ throw invalidBaseUrl("Base URL must not contain a path.");
108
+ }
109
+ return url.origin;
110
+ }
111
+ function joinUrl(baseUrl, endpoint) {
112
+ const normalizedEndpoint = endpoint.replace(/^\/+/, "");
113
+ if (normalizedEndpoint.length === 0) {
114
+ return baseUrl;
115
+ }
116
+ return `${baseUrl}/${normalizedEndpoint}`;
117
+ }
118
+ function invalidBaseUrl(message) {
119
+ return new AuthError("INVALID_BASE_URL", message);
120
+ }
121
+
122
+ // src/backend/frappe/endpoints.ts
123
+ var AUTH_ENDPOINTS = {
124
+ masterToken: "/api/method/employee_app.authentication.master_token",
125
+ loginPolicy: "/api/method/employee_app.authentication.get_employee_login_policy",
126
+ sendOtp: "/api/method/employee_app.authentication.generate_and_send_otp",
127
+ signUp: "/api/method/employee_app.authentication.sign_up_api",
128
+ signIn: "/api/method/employee_app.sign_in.sign_in_api"
129
+ };
130
+
131
+ // src/backend/frappe/schemas.ts
132
+ import { z } from "zod";
133
+ var backendPolicyValueSchema = z.enum(["Mandatory", "Optional", "No"]);
134
+ var tokenSchema = z.object({
135
+ access_token: z.string().min(1),
136
+ expires_in: z.number().int().positive(),
137
+ token_type: z.string().min(1),
138
+ scope: z.string(),
139
+ refresh_token: z.string().min(1)
140
+ });
141
+ var employeeSchema = z.object({
142
+ id: z.string().min(1),
143
+ employee_name: z.string().min(1),
144
+ phone: z.string().min(1),
145
+ email: z.string().nullable()
146
+ });
147
+ var masterTokenResponseSchema = z.object({
148
+ data: tokenSchema.extend({
149
+ // This value will enter an HTTP header. Reject whitespace, controls and
150
+ // non-ASCII values rather than letting fetch echo them in an exception.
151
+ access_token: z.string().regex(/^[\x21-\x7e]+$/u)
152
+ })
153
+ });
154
+ var loginPolicyResponseSchema = z.object({
155
+ status: z.literal("success"),
156
+ employee_id: z.string().min(1),
157
+ policy: z.object({
158
+ password_policy: backendPolicyValueSchema,
159
+ otp_policy: backendPolicyValueSchema
160
+ }),
161
+ employee_has_existing_password: z.boolean(),
162
+ employee_has_signed_up: z.boolean()
163
+ });
164
+ var sendOtpResponseSchema = z.object({
165
+ status: z.literal("success"),
166
+ message: z.string(),
167
+ otp_expires_in: z.number().int().positive()
168
+ });
169
+ var signUpResponseSchema = z.object({
170
+ status: z.literal("success"),
171
+ data: z.object({
172
+ token: tokenSchema,
173
+ employee: employeeSchema,
174
+ time: z.string().min(1)
175
+ })
176
+ });
177
+ var signInResponseSchema = z.object({
178
+ status: z.literal("success"),
179
+ data: z.object({
180
+ token: tokenSchema,
181
+ employee: employeeSchema,
182
+ password_policy: backendPolicyValueSchema,
183
+ otp_policy: backendPolicyValueSchema,
184
+ time: z.string().min(1)
185
+ })
186
+ });
187
+ var backendErrorResponseSchema = z.object({
188
+ status: z.literal("error"),
189
+ message: z.string()
190
+ });
191
+ var masterAuthenticationErrorSchema = z.object({
192
+ exc_type: z.literal("AuthenticationError")
193
+ });
194
+ var businessErrorMarkerSchema = z.object({
195
+ status: z.literal("error")
196
+ });
197
+
198
+ // src/backend/frappe/mappers.ts
199
+ function mapMasterCredential(value, now) {
200
+ const response = parseConfirmedResponse(
201
+ masterTokenResponseSchema,
202
+ value,
203
+ "master-token"
204
+ );
205
+ return {
206
+ accessToken: response.data.access_token,
207
+ expiresIn: response.data.expires_in,
208
+ expiresAt: now + response.data.expires_in * 1e3,
209
+ tokenType: normalizeTokenType(response.data.token_type),
210
+ scope: response.data.scope
211
+ };
212
+ }
213
+ function mapLoginPolicy(value) {
214
+ const response = parseConfirmedResponse(
215
+ loginPolicyResponseSchema,
216
+ value,
217
+ "login-policy"
218
+ );
219
+ return {
220
+ employeeId: response.employee_id,
221
+ employeeHasExistingPassword: response.employee_has_existing_password,
222
+ employeeHasSignedUp: response.employee_has_signed_up,
223
+ passwordPolicy: mapPasswordPolicy(response.policy.password_policy),
224
+ otpPolicy: mapOtpPolicy(response.policy.otp_policy)
225
+ };
226
+ }
227
+ function mapSendOtpResult(value) {
228
+ const response = parseConfirmedResponse(
229
+ sendOtpResponseSchema,
230
+ value,
231
+ "send-OTP"
232
+ );
233
+ return { expiresIn: response.otp_expires_in };
234
+ }
235
+ function mapSignUpResult(value) {
236
+ const response = parseConfirmedResponse(
237
+ signUpResponseSchema,
238
+ value,
239
+ "sign-up"
240
+ );
241
+ return mapBaseAuthResult(response);
242
+ }
243
+ function mapSignInResult(value) {
244
+ const response = parseConfirmedResponse(
245
+ signInResponseSchema,
246
+ value,
247
+ "sign-in"
248
+ );
249
+ return {
250
+ ...mapBaseAuthResult(response),
251
+ passwordPolicy: mapPasswordPolicy(response.data.password_policy),
252
+ otpPolicy: mapOtpPolicy(response.data.otp_policy)
253
+ };
254
+ }
255
+ function mapBaseAuthResult(response) {
256
+ return {
257
+ token: mapToken(response.data.token),
258
+ employee: mapEmployee(response.data.employee),
259
+ authenticatedAt: response.data.time
260
+ };
261
+ }
262
+ function mapToken(token) {
263
+ return {
264
+ accessToken: token.access_token,
265
+ refreshToken: token.refresh_token,
266
+ expiresIn: token.expires_in,
267
+ tokenType: normalizeTokenType(token.token_type),
268
+ scope: token.scope
269
+ };
270
+ }
271
+ function mapEmployee(employee) {
272
+ return {
273
+ id: employee.id,
274
+ name: employee.employee_name,
275
+ phone: employee.phone,
276
+ email: employee.email
277
+ };
278
+ }
279
+ function normalizeTokenType(value) {
280
+ return value.toLowerCase() === "bearer" ? "Bearer" : value;
281
+ }
282
+ function mapPasswordPolicy(value) {
283
+ return mapPolicy(value);
284
+ }
285
+ function mapOtpPolicy(value) {
286
+ return mapPolicy(value);
287
+ }
288
+ function mapPolicy(value) {
289
+ switch (value) {
290
+ case "Mandatory":
291
+ return "required";
292
+ case "Optional":
293
+ return "optional";
294
+ case "No":
295
+ return "disabled";
296
+ }
297
+ }
298
+ function parseConfirmedResponse(schema, value, operation) {
299
+ const result = schema.safeParse(value);
300
+ if (result.success) {
301
+ return result.data;
302
+ }
303
+ const unsupportedPolicy2 = result.error.issues.some(
304
+ (issue) => issue.code === "invalid_value" && (issue.path.at(-1) === "password_policy" || issue.path.at(-1) === "otp_policy")
305
+ );
306
+ if (unsupportedPolicy2) {
307
+ throw new AuthError(
308
+ "UNSUPPORTED_POLICY",
309
+ "Backend returned an unsupported authentication policy."
310
+ );
311
+ }
312
+ throw new AuthError(
313
+ "INVALID_RESPONSE",
314
+ `Backend returned a malformed ${operation} response.`
315
+ );
316
+ }
317
+
318
+ // src/backend/frappe/FrappeAuthBackend.ts
319
+ var FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
320
+ var FrappeAuthBackend = class {
321
+ baseUrl;
322
+ defaultHeaders;
323
+ #masterTokens;
324
+ now;
325
+ timeoutMs;
326
+ #transport;
327
+ constructor(options) {
328
+ this.baseUrl = options.baseUrl;
329
+ this.#transport = options.transport;
330
+ this.timeoutMs = options.timeoutMs;
331
+ this.now = options.now ?? Date.now;
332
+ this.defaultHeaders = createMetadataHeaders(options);
333
+ this.#masterTokens = new MasterTokenManager({
334
+ fetchCredential: () => this.fetchMasterCredential(),
335
+ now: this.now
336
+ });
337
+ }
338
+ async getLoginPolicy(input) {
339
+ const query = encodeForm({ mobile: input.mobileNumber });
340
+ const response = await this.requestWithMasterAuth(
341
+ (accessToken) => ({
342
+ method: "GET",
343
+ url: `${this.endpoint(AUTH_ENDPOINTS.loginPolicy)}?${query}`,
344
+ headers: this.formHeaders(accessToken),
345
+ timeoutMs: this.timeoutMs
346
+ }),
347
+ false
348
+ );
349
+ ensureSuccess(response, "login-policy", false);
350
+ return mapLoginPolicy(response.body);
351
+ }
352
+ async sendOtp(input) {
353
+ const response = await this.requestWithMasterAuth(
354
+ (accessToken) => ({
355
+ method: "POST",
356
+ url: this.endpoint(AUTH_ENDPOINTS.sendOtp),
357
+ headers: this.formHeaders(accessToken),
358
+ body: encodeForm({ mobile_no: input.mobileNumber }),
359
+ timeoutMs: this.timeoutMs
360
+ }),
361
+ true
362
+ );
363
+ ensureSuccess(response, "send-OTP", true);
364
+ return mapSendOtpResult(response.body);
365
+ }
366
+ async signUp(input) {
367
+ const response = await this.requestTransport(
368
+ {
369
+ method: "POST",
370
+ url: this.endpoint(AUTH_ENDPOINTS.signUp),
371
+ headers: this.formHeaders(),
372
+ body: encodeForm({
373
+ mobile_no: input.mobileNumber,
374
+ otp: input.otp,
375
+ password: input.password
376
+ }),
377
+ timeoutMs: this.timeoutMs
378
+ },
379
+ true
380
+ );
381
+ ensureSuccess(response, "sign-up", true);
382
+ return mapSignUpResult(response.body);
383
+ }
384
+ async signIn(input) {
385
+ const response = await this.requestWithMasterAuth(
386
+ (accessToken) => ({
387
+ method: "POST",
388
+ url: this.endpoint(AUTH_ENDPOINTS.signIn),
389
+ headers: this.formHeaders(accessToken),
390
+ body: encodeForm({
391
+ mobile_no: input.mobileNumber,
392
+ otp: input.otp,
393
+ password: input.password
394
+ }),
395
+ timeoutMs: this.timeoutMs
396
+ }),
397
+ true
398
+ );
399
+ if (response.status >= 500) {
400
+ ensureSuccess(response, "sign-in", true);
401
+ }
402
+ const backendError = backendErrorResponseSchema.safeParse(response.body);
403
+ if (backendError.success) {
404
+ throw mapSignInError(backendError.data.message, response.status);
405
+ }
406
+ ensureSuccess(response, "sign-in", true);
407
+ return mapSignInResult(response.body);
408
+ }
409
+ endpoint(path) {
410
+ return joinUrl(this.baseUrl, path);
411
+ }
412
+ async fetchMasterCredential() {
413
+ const response = await this.requestTransport(
414
+ {
415
+ method: "GET",
416
+ url: this.endpoint(AUTH_ENDPOINTS.masterToken),
417
+ headers: this.defaultHeaders,
418
+ timeoutMs: this.timeoutMs
419
+ },
420
+ false
421
+ );
422
+ if (response.status < 200 || response.status >= 300) {
423
+ throw new AuthError(
424
+ "MASTER_TOKEN_FAILED",
425
+ "Unable to acquire the master authentication token.",
426
+ {
427
+ httpStatus: response.status,
428
+ retryable: response.status >= 500
429
+ }
430
+ );
431
+ }
432
+ try {
433
+ return mapMasterCredential(response.body, this.now());
434
+ } catch (error) {
435
+ if (isAuthError(error) && error.code === "INVALID_RESPONSE") {
436
+ throw new AuthError(
437
+ "MASTER_TOKEN_FAILED",
438
+ "Backend returned a malformed master-token response."
439
+ );
440
+ }
441
+ throw error;
442
+ }
443
+ }
444
+ formHeaders(accessToken) {
445
+ return accessToken === void 0 ? { ...this.defaultHeaders, "Content-Type": FORM_CONTENT_TYPE } : {
446
+ ...this.defaultHeaders,
447
+ "Content-Type": FORM_CONTENT_TYPE,
448
+ Authorization: `Bearer ${accessToken}`
449
+ };
450
+ }
451
+ async requestWithMasterAuth(buildRequest, replayUnsafe) {
452
+ const firstCredential = await this.#masterTokens.getCredential();
453
+ const firstResponse = await this.requestTransport(
454
+ buildRequest(firstCredential.accessToken),
455
+ replayUnsafe
456
+ );
457
+ if (!isMasterRejection(firstResponse)) {
458
+ return firstResponse;
459
+ }
460
+ this.#masterTokens.invalidate(firstCredential.accessToken);
461
+ const replacement = await this.#masterTokens.getCredential();
462
+ const retryResponse = await this.requestTransport(
463
+ buildRequest(replacement.accessToken),
464
+ replayUnsafe
465
+ );
466
+ if (isMasterRejection(retryResponse)) {
467
+ this.#masterTokens.invalidate(replacement.accessToken);
468
+ throw new AuthError(
469
+ "MASTER_TOKEN_REJECTED",
470
+ "The server rejected master authentication.",
471
+ { httpStatus: retryResponse.status }
472
+ );
473
+ }
474
+ return retryResponse;
475
+ }
476
+ async requestTransport(request, replayUnsafe) {
477
+ try {
478
+ return await this.#transport.request(request);
479
+ } catch (error) {
480
+ const timedOut = isAuthError(error) && error.code === "TIMEOUT";
481
+ throw new AuthError(
482
+ timedOut ? "TIMEOUT" : "NETWORK_ERROR",
483
+ timedOut ? "Authentication request timed out." : "Authentication request could not reach the server.",
484
+ { retryable: !replayUnsafe }
485
+ );
486
+ }
487
+ }
488
+ };
489
+ function createMetadataHeaders(options) {
490
+ const headers = {};
491
+ if (options.appId !== void 0) {
492
+ headers["X-ERPGulf-App-Id"] = options.appId;
493
+ }
494
+ if (options.appVersion !== void 0) {
495
+ headers["X-ERPGulf-App-Version"] = options.appVersion;
496
+ }
497
+ return headers;
498
+ }
499
+ function isMasterRejection(response) {
500
+ return response.status === 401 && !businessErrorMarkerSchema.safeParse(response.body).success && masterAuthenticationErrorSchema.safeParse(response.body).success;
501
+ }
502
+ function ensureSuccess(response, operation, replayUnsafe) {
503
+ if (response.status >= 200 && response.status < 300) {
504
+ return;
505
+ }
506
+ throw new AuthError("SERVER_ERROR", `The ${operation} request failed.`, {
507
+ httpStatus: response.status,
508
+ retryable: !replayUnsafe && response.status >= 500
509
+ });
510
+ }
511
+ function mapSignInError(message, httpStatus) {
512
+ if (message === "Invalid or expired OTP") {
513
+ return new AuthError(
514
+ "INVALID_OR_EXPIRED_OTP",
515
+ "The OTP is invalid or expired.",
516
+ { httpStatus }
517
+ );
518
+ }
519
+ if (message === "Invalid password") {
520
+ return new AuthError("INVALID_PASSWORD", "The password is invalid.", {
521
+ httpStatus
522
+ });
523
+ }
524
+ return new AuthError(
525
+ "AUTHENTICATION_FAILED",
526
+ "Authentication was not successful.",
527
+ { httpStatus }
528
+ );
529
+ }
530
+
531
+ // src/http/FetchTransport.ts
532
+ var DEFAULT_TIMEOUT_MS = 1e4;
533
+ var FetchTransport = class {
534
+ async request(request) {
535
+ const timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;
536
+ const controller = new AbortController();
537
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
538
+ try {
539
+ const response = await fetch(request.url, {
540
+ method: request.method,
541
+ signal: controller.signal,
542
+ redirect: "error",
543
+ cache: "no-store",
544
+ credentials: "omit",
545
+ ...request.headers === void 0 ? {} : { headers: request.headers },
546
+ ...request.body === void 0 ? {} : { body: request.body }
547
+ });
548
+ return {
549
+ status: response.status,
550
+ body: await parseResponseBody(response)
551
+ };
552
+ } catch {
553
+ if (controller.signal.aborted) {
554
+ throw new AuthError("TIMEOUT", "Authentication request timed out.", {
555
+ retryable: true
556
+ });
557
+ }
558
+ throw new AuthError(
559
+ "NETWORK_ERROR",
560
+ "Authentication request could not reach the server.",
561
+ { retryable: true }
562
+ );
563
+ } finally {
564
+ clearTimeout(timeout);
565
+ }
566
+ }
567
+ };
568
+ async function parseResponseBody(response) {
569
+ const text = await response.text();
570
+ if (text.length === 0) {
571
+ return void 0;
572
+ }
573
+ try {
574
+ return JSON.parse(text);
575
+ } catch {
576
+ return text;
577
+ }
578
+ }
579
+
580
+ // src/flow/resolveAuthFlow.ts
581
+ function resolveAuthFlow(policy) {
582
+ if (!policy.employeeHasSignedUp && policy.employeeHasExistingPassword) {
583
+ throw unsupportedPolicy(
584
+ "An employee who has not signed up cannot have an existing password."
585
+ );
586
+ }
587
+ if (policy.passwordPolicy === "disabled" && policy.otpPolicy === "disabled") {
588
+ throw unsupportedPolicy(
589
+ "Authentication policy disables both password and OTP credentials."
590
+ );
591
+ }
592
+ const action = policy.employeeHasSignedUp ? "SIGN_IN" : "SIGN_UP";
593
+ const atLeastOneOf = resolveAtLeastOneOf(policy);
594
+ return {
595
+ action,
596
+ nextStep: resolveNextStep(policy),
597
+ credentials: {
598
+ password: policy.passwordPolicy,
599
+ otp: policy.otpPolicy,
600
+ atLeastOneOf
601
+ },
602
+ policy
603
+ };
604
+ }
605
+ function resolveAtLeastOneOf(policy) {
606
+ if (policy.passwordPolicy === "required" || policy.otpPolicy === "required") {
607
+ return [];
608
+ }
609
+ const credentials = [];
610
+ if (policy.passwordPolicy === "optional") {
611
+ credentials.push("password");
612
+ }
613
+ if (policy.otpPolicy === "optional") {
614
+ credentials.push("otp");
615
+ }
616
+ return credentials;
617
+ }
618
+ function resolveNextStep(policy) {
619
+ if (policy.passwordPolicy === "required" && policy.otpPolicy === "required") {
620
+ return "ENTER_PASSWORD_AND_OTP";
621
+ }
622
+ if (policy.passwordPolicy === "required") {
623
+ return policy.employeeHasExistingPassword ? "ENTER_PASSWORD" : "CREATE_PASSWORD";
624
+ }
625
+ if (policy.otpPolicy === "required") {
626
+ return "ENTER_OTP";
627
+ }
628
+ if (policy.passwordPolicy === "optional" && policy.otpPolicy === "optional") {
629
+ return "CHOOSE_PASSWORD_OR_OTP";
630
+ }
631
+ return policy.passwordPolicy === "optional" ? policy.employeeHasExistingPassword ? "ENTER_PASSWORD" : "CREATE_PASSWORD" : "ENTER_OTP";
632
+ }
633
+ function unsupportedPolicy(message) {
634
+ return new AuthError("UNSUPPORTED_POLICY", message);
635
+ }
636
+
637
+ // src/client/AuthClient.ts
638
+ var DefaultAuthClient = class {
639
+ #backend;
640
+ constructor(backend) {
641
+ this.#backend = backend;
642
+ }
643
+ async begin(input) {
644
+ const policy = await this.getLoginPolicy(input);
645
+ return resolveAuthFlow(policy);
646
+ }
647
+ async getLoginPolicy(input) {
648
+ return await this.#backend.getLoginPolicy({
649
+ mobileNumber: validateMobile(input.mobileNumber)
650
+ });
651
+ }
652
+ async sendOtp(input) {
653
+ return await this.#backend.sendOtp({
654
+ mobileNumber: validateMobile(input.mobileNumber)
655
+ });
656
+ }
657
+ async signUp(input) {
658
+ const otp = validateOptionalCredential(input.otp, "otp", "sign-up");
659
+ const password = validateOptionalCredential(
660
+ input.password,
661
+ "password",
662
+ "sign-up"
663
+ );
664
+ if (otp === void 0 && password === void 0) {
665
+ throw new AuthError(
666
+ "INVALID_SIGN_UP_INPUT",
667
+ "Sign-up requires an OTP, a password, or both according to policy."
668
+ );
669
+ }
670
+ return await this.#backend.signUp({
671
+ mobileNumber: validateMobile(input.mobileNumber),
672
+ ...otp === void 0 ? {} : { otp },
673
+ ...password === void 0 ? {} : { password }
674
+ });
675
+ }
676
+ async signIn(input) {
677
+ const otp = validateOptionalCredential(input.otp, "otp", "sign-in");
678
+ const password = validateOptionalCredential(
679
+ input.password,
680
+ "password",
681
+ "sign-in"
682
+ );
683
+ if (otp === void 0 && password === void 0) {
684
+ throw new AuthError(
685
+ "INVALID_SIGN_IN_INPUT",
686
+ "Sign-in requires an OTP, a password, or both."
687
+ );
688
+ }
689
+ return await this.#backend.signIn({
690
+ mobileNumber: validateMobile(input.mobileNumber),
691
+ ...otp === void 0 ? {} : { otp },
692
+ ...password === void 0 ? {} : { password }
693
+ });
694
+ }
695
+ };
696
+ function validateMobile(mobileNumber) {
697
+ const normalized = mobileNumber.trim();
698
+ if (normalized.length === 0) {
699
+ throw new AuthError("INVALID_MOBILE", "Mobile number must not be empty.");
700
+ }
701
+ return normalized;
702
+ }
703
+ function validateOptionalCredential(value, name, operation) {
704
+ if (value === void 0) {
705
+ return void 0;
706
+ }
707
+ if (value.length === 0) {
708
+ throw new AuthError(
709
+ operation === "sign-in" ? "INVALID_SIGN_IN_INPUT" : "INVALID_SIGN_UP_INPUT",
710
+ `${name === "otp" ? "OTP" : "Password"} must not be empty when provided.`
711
+ );
712
+ }
713
+ return value;
714
+ }
715
+
716
+ // src/client/createAuthClient.ts
717
+ var DEFAULT_TIMEOUT_MS2 = 1e4;
718
+ function createAuthClient(config) {
719
+ const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
720
+ validateTimeout(timeoutMs);
721
+ validateMetadata(config.metadata?.appId, "appId");
722
+ validateMetadata(config.metadata?.appVersion, "appVersion");
723
+ const backend = new FrappeAuthBackend({
724
+ baseUrl: normalizeBaseUrl(
725
+ config.baseUrl,
726
+ config.allowInsecureHttp === void 0 ? {} : { allowInsecureHttp: config.allowInsecureHttp }
727
+ ),
728
+ transport: config.transport ?? new FetchTransport(),
729
+ timeoutMs,
730
+ ...config.metadata?.appId === void 0 ? {} : { appId: config.metadata.appId },
731
+ ...config.metadata?.appVersion === void 0 ? {} : { appVersion: config.metadata.appVersion }
732
+ });
733
+ return new DefaultAuthClient(backend);
734
+ }
735
+ function validateTimeout(timeoutMs) {
736
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
737
+ throw new AuthError(
738
+ "INVALID_CLIENT_CONFIG",
739
+ "timeoutMs must be a positive finite number."
740
+ );
741
+ }
742
+ }
743
+ function validateMetadata(value, name) {
744
+ if (value !== void 0 && (value.length === 0 || /[\r\n]/u.test(value))) {
745
+ throw new AuthError(
746
+ "INVALID_CLIENT_CONFIG",
747
+ `${name} must be non-empty and must not contain line breaks.`
748
+ );
749
+ }
750
+ }
751
+ export {
752
+ AuthError,
753
+ createAuthClient,
754
+ isAuthError
755
+ };
756
+ //# sourceMappingURL=index.js.map