@incodetech/core 0.0.0-dev-20260126-4504c5b

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.
@@ -0,0 +1,552 @@
1
+ import { d as addEvent, n as eventModuleNames, r as eventScreenNames } from "./events-B8ZkhAZo.esm.js";
2
+ import { C as createManager, d as BrowserTimerProvider } from "./src-DYtpbFY5.esm.js";
3
+ import { a as fromPromise, i as fromCallback, n as setup, o as createActor, r as assign, t as endpoints } from "./endpoints-BUsSVoJV.esm.js";
4
+ import { t as api } from "./api-DfRLAneb.esm.js";
5
+
6
+ //#region src/modules/phone/phoneServices.ts
7
+ async function fetchPhone(signal) {
8
+ const res = await api.get(endpoints.getPhone, { signal });
9
+ if (!res.ok) throw new Error(`GET ${endpoints.getPhone} failed: ${res.status} ${res.statusText}`);
10
+ return res.data;
11
+ }
12
+ async function fetchStartInfo(signal) {
13
+ const res = await api.get(endpoints.startInfo, { signal });
14
+ if (!res.ok) throw new Error(`GET ${endpoints.startInfo} failed: ${res.status} ${res.statusText}`);
15
+ return res.data;
16
+ }
17
+ async function addPhone(params, signal) {
18
+ const res = await api.post(endpoints.phone, {
19
+ phone: params.phone,
20
+ optInGranted: params.optInGranted
21
+ }, { signal });
22
+ if (!res.ok) throw new Error(`POST ${endpoints.phone} failed: ${res.status} ${res.statusText}`);
23
+ return res.data;
24
+ }
25
+ async function addPhoneInstantVerify(params, signal) {
26
+ const res = await api.post(endpoints.phoneInstant, {
27
+ phone: params.phone,
28
+ optInGranted: params.optInGranted
29
+ }, { signal });
30
+ if (!res.ok) throw new Error(`POST ${endpoints.phoneInstant} failed: ${res.status} ${res.statusText}`);
31
+ return res.data;
32
+ }
33
+ async function sendOtp(signal) {
34
+ const res = await api.get(`${endpoints.sendSmsOtp}?communicationchannel=SMS`, { signal });
35
+ if (!res.ok) throw new Error(`GET ${endpoints.sendSmsOtp} failed: ${res.status} ${res.statusText}`);
36
+ }
37
+ async function verifyOtp(code, signal) {
38
+ const res = await api.get(`${endpoints.compareOtp}?code=${code}&channel=SMS`, { signal });
39
+ if (!res.ok) throw new Error(`GET ${endpoints.compareOtp} failed: ${res.status} ${res.statusText}`);
40
+ return res.data;
41
+ }
42
+
43
+ //#endregion
44
+ //#region src/modules/phone/phoneStateMachine.ts
45
+ const RESEND_TIMER_SECONDS = 30;
46
+ const phoneMachine = setup({
47
+ types: {
48
+ context: {},
49
+ events: {},
50
+ input: {}
51
+ },
52
+ actors: {
53
+ fetchPhone: fromPromise(async ({ signal }) => {
54
+ return fetchPhone(signal);
55
+ }),
56
+ fetchStartInfo: fromPromise(async ({ signal }) => {
57
+ return fetchStartInfo(signal);
58
+ }),
59
+ submitPhone: fromPromise(async ({ input, signal }) => {
60
+ if (input.isInstantVerify) return addPhoneInstantVerify({
61
+ phone: input.phone,
62
+ optInGranted: input.optInGranted
63
+ }, signal);
64
+ return addPhone({
65
+ phone: input.phone,
66
+ optInGranted: input.optInGranted
67
+ }, signal);
68
+ }),
69
+ sendOtp: fromPromise(async ({ signal }) => {
70
+ return sendOtp(signal);
71
+ }),
72
+ verifyOtp: fromPromise(async ({ input, signal }) => {
73
+ return verifyOtp(input.code, signal);
74
+ }),
75
+ resendTimer: fromCallback(({ sendBack }) => {
76
+ const timer = BrowserTimerProvider.getInstance();
77
+ let seconds = RESEND_TIMER_SECONDS;
78
+ const interval = timer.setInterval(() => {
79
+ seconds -= 1;
80
+ sendBack({ type: "TICK" });
81
+ if (seconds <= 0) timer.clearInterval(interval);
82
+ }, 1e3);
83
+ return () => timer.clearInterval(interval);
84
+ })
85
+ },
86
+ actions: {
87
+ setStartInfo: assign(({ event }) => {
88
+ const info = event.output;
89
+ return {
90
+ startInfo: info,
91
+ countryCode: info.ipIsoCode,
92
+ phonePrefix: info.phonePrefix
93
+ };
94
+ }),
95
+ setPrefilledPhone: assign(({ event }) => {
96
+ const phone = event.output.phone;
97
+ return {
98
+ prefilledPhone: phone,
99
+ phone
100
+ };
101
+ }),
102
+ setPhone: assign(({ event }) => {
103
+ const e = event;
104
+ return {
105
+ phone: e.phone,
106
+ isValid: e.isValid,
107
+ phoneError: e.isValid ? void 0 : "Invalid phone number"
108
+ };
109
+ }),
110
+ setPhoneError: assign(({ event }) => ({ phoneError: String(event.error) })),
111
+ setOptInGranted: assign(({ event }) => ({ optInGranted: event.granted })),
112
+ setError: assign(({ event }) => ({ error: String(event.error) })),
113
+ clearError: assign({ error: () => void 0 }),
114
+ clearPhoneError: assign({ phoneError: () => void 0 }),
115
+ setOtpCode: assign(({ event }) => ({
116
+ otpCode: event.code,
117
+ otpError: void 0
118
+ })),
119
+ setOtpError: assign(({ context, event }) => ({
120
+ otpError: String(event.error),
121
+ attemptsRemaining: context.attemptsRemaining - 1
122
+ })),
123
+ clearOtpError: assign({
124
+ otpError: () => void 0,
125
+ otpCode: () => ""
126
+ }),
127
+ startResendTimer: assign({
128
+ resendTimer: () => RESEND_TIMER_SECONDS,
129
+ resendTimerActive: () => true
130
+ }),
131
+ tickResendTimer: assign(({ context }) => {
132
+ const newTimer = Math.max(0, context.resendTimer - 1);
133
+ return {
134
+ resendTimer: newTimer,
135
+ resendTimerActive: newTimer > 0
136
+ };
137
+ }),
138
+ stopResendTimer: assign({ resendTimerActive: () => false }),
139
+ resetContext: assign(({ context }) => ({
140
+ config: context.config,
141
+ phone: "",
142
+ isValid: false,
143
+ phoneError: void 0,
144
+ countryCode: "",
145
+ phonePrefix: "",
146
+ prefilledPhone: void 0,
147
+ optInGranted: false,
148
+ startInfo: void 0,
149
+ error: void 0,
150
+ otpCode: "",
151
+ otpError: void 0,
152
+ attemptsRemaining: context.config.maxOtpAttempts ?? 3,
153
+ resendTimer: 0,
154
+ resendTimerActive: false
155
+ })),
156
+ sendPhoneSubmitEvent: () => {
157
+ addEvent({
158
+ code: "continue",
159
+ module: eventModuleNames.phone,
160
+ screen: eventScreenNames.phoneInput
161
+ });
162
+ }
163
+ },
164
+ guards: {
165
+ hasPrefill: ({ context }) => context.config.prefill,
166
+ hasOtpVerification: ({ context }) => context.config.otpVerification,
167
+ isValidPhone: ({ context }) => context.isValid,
168
+ hasAttemptsRemaining: ({ context }) => context.attemptsRemaining > 0,
169
+ canResend: ({ context }) => !context.resendTimerActive
170
+ }
171
+ }).createMachine({
172
+ id: "phone",
173
+ initial: "idle",
174
+ context: ({ input }) => ({
175
+ config: input.config,
176
+ phone: "",
177
+ isValid: false,
178
+ phoneError: void 0,
179
+ countryCode: "",
180
+ phonePrefix: "",
181
+ prefilledPhone: void 0,
182
+ optInGranted: false,
183
+ startInfo: void 0,
184
+ error: void 0,
185
+ otpCode: "",
186
+ otpError: void 0,
187
+ attemptsRemaining: input.config.maxOtpAttempts ?? 3,
188
+ resendTimer: 0,
189
+ resendTimerActive: false
190
+ }),
191
+ states: {
192
+ idle: { on: { LOAD: [{
193
+ target: "loadingPrefill",
194
+ guard: "hasPrefill"
195
+ }, { target: "loadingStartInfo" }] } },
196
+ loadingPrefill: { invoke: {
197
+ id: "fetchPhone",
198
+ src: "fetchPhone",
199
+ onDone: {
200
+ target: "loadingStartInfo",
201
+ actions: "setPrefilledPhone"
202
+ },
203
+ onError: { target: "loadingStartInfo" }
204
+ } },
205
+ loadingStartInfo: { invoke: {
206
+ id: "fetchStartInfo",
207
+ src: "fetchStartInfo",
208
+ onDone: {
209
+ target: "inputting",
210
+ actions: "setStartInfo"
211
+ },
212
+ onError: { target: "inputting" }
213
+ } },
214
+ inputting: {
215
+ entry: "clearPhoneError",
216
+ on: {
217
+ PHONE_CHANGED: { actions: "setPhone" },
218
+ OPT_IN_CHANGED: { actions: "setOptInGranted" },
219
+ SUBMIT: {
220
+ target: "submitting",
221
+ guard: "isValidPhone"
222
+ }
223
+ }
224
+ },
225
+ submitting: { invoke: {
226
+ id: "submitPhone",
227
+ src: "submitPhone",
228
+ input: ({ context }) => ({
229
+ phone: context.phone,
230
+ optInGranted: context.config.optinEnabled ? context.optInGranted : void 0,
231
+ isInstantVerify: context.config.isInstantVerify ?? false
232
+ }),
233
+ onDone: [{
234
+ target: "sendingOtp",
235
+ guard: "hasOtpVerification",
236
+ actions: "sendPhoneSubmitEvent"
237
+ }, {
238
+ target: "finished",
239
+ actions: "sendPhoneSubmitEvent"
240
+ }],
241
+ onError: {
242
+ target: "inputting",
243
+ actions: "setPhoneError"
244
+ }
245
+ } },
246
+ sendingOtp: { invoke: {
247
+ id: "sendOtp",
248
+ src: "sendOtp",
249
+ onDone: { target: "awaitingOtp" },
250
+ onError: {
251
+ target: "awaitingOtp",
252
+ actions: "setError"
253
+ }
254
+ } },
255
+ awaitingOtp: {
256
+ entry: "startResendTimer",
257
+ invoke: {
258
+ id: "resendTimer",
259
+ src: "resendTimer"
260
+ },
261
+ on: {
262
+ TICK: { actions: "tickResendTimer" },
263
+ OTP_CHANGED: { actions: "setOtpCode" },
264
+ VERIFY_OTP: { target: "verifyingOtp" },
265
+ RESEND_OTP: {
266
+ target: "sendingOtp",
267
+ guard: "canResend"
268
+ },
269
+ BACK: { target: "inputting" }
270
+ }
271
+ },
272
+ verifyingOtp: { invoke: {
273
+ id: "verifyOtp",
274
+ src: "verifyOtp",
275
+ input: ({ context }) => ({ code: context.otpCode }),
276
+ onDone: [
277
+ {
278
+ target: "finished",
279
+ guard: ({ event }) => event.output.success === true
280
+ },
281
+ {
282
+ target: "otpError",
283
+ guard: "hasAttemptsRemaining",
284
+ actions: assign(({ context }) => ({
285
+ otpError: "Invalid OTP code",
286
+ attemptsRemaining: context.attemptsRemaining - 1
287
+ }))
288
+ },
289
+ {
290
+ target: "error",
291
+ actions: assign({ error: () => "Maximum OTP attempts exceeded" })
292
+ }
293
+ ],
294
+ onError: [{
295
+ target: "otpError",
296
+ guard: "hasAttemptsRemaining",
297
+ actions: "setOtpError"
298
+ }, {
299
+ target: "error",
300
+ actions: "setError"
301
+ }]
302
+ } },
303
+ otpError: { on: {
304
+ OTP_CHANGED: {
305
+ target: "awaitingOtp",
306
+ actions: "setOtpCode"
307
+ },
308
+ RESEND_OTP: {
309
+ target: "sendingOtp",
310
+ guard: "canResend"
311
+ },
312
+ BACK: { target: "inputting" }
313
+ } },
314
+ finished: { type: "final" },
315
+ error: { on: { RESET: {
316
+ target: "idle",
317
+ actions: "resetContext"
318
+ } } }
319
+ }
320
+ });
321
+
322
+ //#endregion
323
+ //#region src/modules/phone/phoneActor.ts
324
+ function createPhoneActor(options) {
325
+ return createActor(phoneMachine, { input: { config: options.config } }).start();
326
+ }
327
+
328
+ //#endregion
329
+ //#region src/modules/phone/phoneManager.ts
330
+ /**
331
+ * @module @incodetech/core/phone
332
+ *
333
+ * Phone verification module for the Incode Web SDK.
334
+ * Supports both headless (programmatic) and UI-driven usage patterns.
335
+ *
336
+ * ## Headless Usage
337
+ *
338
+ * The phone manager can be used entirely without UI for backend integrations,
339
+ * custom UI implementations, or automated workflows.
340
+ *
341
+ * @example Basic headless phone verification with OTP
342
+ * ```typescript
343
+ * import { createPhoneManager } from '@incodetech/core/phone';
344
+ * import { setup } from '@incodetech/core';
345
+ *
346
+ * // 1. Configure the SDK (required before using any module)
347
+ * setup({ apiURL: 'https://api.example.com', token: 'your-token' });
348
+ *
349
+ * // 2. Create the phone manager
350
+ * const phoneManager = createPhoneManager({
351
+ * config: {
352
+ * otpVerification: true,
353
+ * otpExpirationInMinutes: 5,
354
+ * prefill: false,
355
+ * maxOtpAttempts: 3,
356
+ * },
357
+ * });
358
+ *
359
+ * // 3. Subscribe to state changes (optional but recommended)
360
+ * phoneManager.subscribe((state) => {
361
+ * console.log('Phone state:', state.status);
362
+ * if (state.status === 'finished') {
363
+ * console.log('Phone verified successfully!');
364
+ * }
365
+ * if (state.status === 'error') {
366
+ * console.error('Error:', state.error);
367
+ * }
368
+ * });
369
+ *
370
+ * // 4. Start the flow
371
+ * phoneManager.load();
372
+ *
373
+ * // 5. When state is 'inputting', set the phone number
374
+ * phoneManager.setPhoneNumber('+14155551234', true);
375
+ *
376
+ * // 6. Submit the phone number
377
+ * phoneManager.submit();
378
+ *
379
+ * // 7. When state is 'awaitingOtp', submit the OTP code
380
+ * phoneManager.submitOtp('ABC123');
381
+ *
382
+ * // 8. Clean up when done
383
+ * phoneManager.stop();
384
+ * ```
385
+ *
386
+ * @example Polling-based headless usage
387
+ * ```typescript
388
+ * const phoneManager = createPhoneManager({ config });
389
+ *
390
+ * phoneManager.load();
391
+ *
392
+ * // Poll for state changes
393
+ * const interval = setInterval(() => {
394
+ * const state = phoneManager.getState();
395
+ *
396
+ * switch (state.status) {
397
+ * case 'inputting':
398
+ * phoneManager.setPhoneNumber('+14155551234', true);
399
+ * phoneManager.submit();
400
+ * break;
401
+ * case 'awaitingOtp':
402
+ * // Get OTP from user or external source
403
+ * phoneManager.submitOtp(otpCode);
404
+ * break;
405
+ * case 'finished':
406
+ * clearInterval(interval);
407
+ * phoneManager.stop();
408
+ * break;
409
+ * case 'error':
410
+ * clearInterval(interval);
411
+ * console.error(state.error);
412
+ * phoneManager.stop();
413
+ * break;
414
+ * }
415
+ * }, 100);
416
+ * ```
417
+ */
418
+ function mapState(snapshot) {
419
+ const typedSnapshot = snapshot;
420
+ const { context } = typedSnapshot;
421
+ if (typedSnapshot.matches("idle")) return { status: "idle" };
422
+ if (typedSnapshot.matches("loadingPrefill")) return { status: "loadingPrefill" };
423
+ if (typedSnapshot.matches("loadingStartInfo")) return {
424
+ status: "inputting",
425
+ countryCode: context.countryCode,
426
+ phonePrefix: context.phonePrefix,
427
+ prefilledPhone: context.prefilledPhone
428
+ };
429
+ if (typedSnapshot.matches("inputting")) return {
430
+ status: "inputting",
431
+ countryCode: context.countryCode,
432
+ phonePrefix: context.phonePrefix,
433
+ prefilledPhone: context.prefilledPhone,
434
+ phoneError: context.phoneError
435
+ };
436
+ if (typedSnapshot.matches("submitting")) return { status: "submitting" };
437
+ if (typedSnapshot.matches("sendingOtp")) return { status: "sendingOtp" };
438
+ if (typedSnapshot.matches("awaitingOtp")) return {
439
+ status: "awaitingOtp",
440
+ resendTimer: context.resendTimer,
441
+ canResend: !context.resendTimerActive,
442
+ attemptsRemaining: context.attemptsRemaining
443
+ };
444
+ if (typedSnapshot.matches("verifyingOtp")) return { status: "verifyingOtp" };
445
+ if (typedSnapshot.matches("otpError")) return {
446
+ status: "otpError",
447
+ error: context.otpError ?? "Invalid OTP code",
448
+ attemptsRemaining: context.attemptsRemaining
449
+ };
450
+ if (typedSnapshot.matches("finished")) return { status: "finished" };
451
+ if (typedSnapshot.matches("error")) return {
452
+ status: "error",
453
+ error: context.error ?? "An error occurred"
454
+ };
455
+ return { status: "idle" };
456
+ }
457
+ function createApi({ actor }) {
458
+ return {
459
+ load() {
460
+ actor.send({ type: "LOAD" });
461
+ },
462
+ setPhoneNumber(phone, isValid) {
463
+ actor.send({
464
+ type: "PHONE_CHANGED",
465
+ phone,
466
+ isValid
467
+ });
468
+ },
469
+ setOptInGranted(granted) {
470
+ actor.send({
471
+ type: "OPT_IN_CHANGED",
472
+ granted
473
+ });
474
+ },
475
+ submit() {
476
+ actor.send({ type: "SUBMIT" });
477
+ },
478
+ setOtpCode(code) {
479
+ actor.send({
480
+ type: "OTP_CHANGED",
481
+ code
482
+ });
483
+ },
484
+ submitOtp(code) {
485
+ actor.send({
486
+ type: "OTP_CHANGED",
487
+ code
488
+ });
489
+ actor.send({ type: "VERIFY_OTP" });
490
+ },
491
+ resendOtp() {
492
+ actor.send({ type: "RESEND_OTP" });
493
+ },
494
+ back() {
495
+ actor.send({ type: "BACK" });
496
+ },
497
+ reset() {
498
+ actor.send({ type: "RESET" });
499
+ }
500
+ };
501
+ }
502
+ /**
503
+ * Creates a phone verification manager for headless or UI-driven usage.
504
+ *
505
+ * The manager provides a state machine-based API for phone number verification
506
+ * with optional OTP (one-time password) verification.
507
+ *
508
+ * @param options - Configuration options
509
+ * @param options.config - Phone verification configuration
510
+ * @param options.config.otpVerification - Whether to require OTP verification
511
+ * @param options.config.otpExpirationInMinutes - How long the OTP is valid
512
+ * @param options.config.prefill - Whether to fetch a pre-filled phone number
513
+ * @param options.config.isInstantVerify - Use instant verification API
514
+ * @param options.config.optinEnabled - Show marketing opt-in checkbox
515
+ * @param options.config.maxOtpAttempts - Maximum OTP verification attempts (default: 3)
516
+ *
517
+ * @returns Phone manager with state, API methods, and subscription
518
+ *
519
+ * @example Headless usage
520
+ * ```typescript
521
+ * const manager = createPhoneManager({
522
+ * config: { otpVerification: true, otpExpirationInMinutes: 5, prefill: false },
523
+ * });
524
+ *
525
+ * manager.subscribe((state) => console.log(state.status));
526
+ * manager.load();
527
+ * manager.setPhoneNumber('+14155551234', true);
528
+ * manager.submit();
529
+ * // ... wait for 'awaitingOtp' state ...
530
+ * manager.submitOtp('ABC123');
531
+ * manager.stop();
532
+ * ```
533
+ *
534
+ * @example With React/Preact UI hook
535
+ * ```tsx
536
+ * const [state, manager] = useManager(() => createPhoneManager({ config }));
537
+ *
538
+ * if (state.status === 'inputting') {
539
+ * return <input onChange={(e) => manager.setPhoneNumber(e.target.value, true)} />;
540
+ * }
541
+ * ```
542
+ */
543
+ function createPhoneManager(options) {
544
+ return createManager({
545
+ actor: createPhoneActor(options),
546
+ mapState,
547
+ createApi
548
+ });
549
+ }
550
+
551
+ //#endregion
552
+ export { createPhoneManager, phoneMachine };