@hawcx/react-native-sdk 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/HawcxReactNative.podspec +25 -0
  3. package/LICENSE +21 -0
  4. package/README.md +109 -0
  5. package/docs/RELEASE.md +119 -0
  6. package/example/README.md +59 -0
  7. package/example/android/app/build.gradle +126 -0
  8. package/example/android/app/debug.keystore +0 -0
  9. package/example/android/app/proguard-rules.pro +10 -0
  10. package/example/android/app/src/debug/AndroidManifest.xml +9 -0
  11. package/example/android/app/src/main/AndroidManifest.xml +27 -0
  12. package/example/android/app/src/main/java/com/hawcx/example/MainActivity.kt +22 -0
  13. package/example/android/app/src/main/java/com/hawcx/example/MainApplication.kt +45 -0
  14. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +36 -0
  15. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  16. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  17. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  18. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  19. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  20. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  21. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  22. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  23. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  24. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  25. package/example/android/app/src/main/res/values/strings.xml +3 -0
  26. package/example/android/app/src/main/res/values/styles.xml +9 -0
  27. package/example/android/build.gradle +23 -0
  28. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  29. package/example/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  30. package/example/android/gradle.properties +41 -0
  31. package/example/android/gradlew +249 -0
  32. package/example/android/gradlew.bat +92 -0
  33. package/example/android/local.properties +2 -0
  34. package/example/android/settings.gradle +4 -0
  35. package/example/app.json +4 -0
  36. package/example/babel.config.js +3 -0
  37. package/example/e2e/README.md +17 -0
  38. package/example/e2e/hawcx-login.yaml +14 -0
  39. package/example/index.js +5 -0
  40. package/example/ios/.xcode.env +11 -0
  41. package/example/ios/HawcxExampleApp/AppDelegate.h +6 -0
  42. package/example/ios/HawcxExampleApp/AppDelegate.mm +31 -0
  43. package/example/ios/HawcxExampleApp/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
  44. package/example/ios/HawcxExampleApp/Images.xcassets/Contents.json +6 -0
  45. package/example/ios/HawcxExampleApp/Info.plist +55 -0
  46. package/example/ios/HawcxExampleApp/LaunchScreen.storyboard +47 -0
  47. package/example/ios/HawcxExampleApp/PrivacyInfo.xcprivacy +37 -0
  48. package/example/ios/HawcxExampleApp/main.m +10 -0
  49. package/example/ios/HawcxExampleApp.xcodeproj/project.pbxproj +704 -0
  50. package/example/ios/HawcxExampleApp.xcodeproj/project.xcworkspace/xcuserdata/agambhullar.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  51. package/example/ios/HawcxExampleApp.xcodeproj/xcshareddata/xcschemes/HawcxExampleApp.xcscheme +90 -0
  52. package/example/ios/HawcxExampleApp.xcodeproj/xcuserdata/agambhullar.xcuserdatad/xcschemes/xcschememanagement.plist +16 -0
  53. package/example/ios/HawcxExampleApp.xcworkspace/contents.xcworkspacedata +10 -0
  54. package/example/ios/HawcxExampleAppTests/HawcxExampleAppTests.m +66 -0
  55. package/example/ios/HawcxExampleAppTests/Info.plist +24 -0
  56. package/example/ios/Podfile +55 -0
  57. package/example/ios/Podfile.lock +1290 -0
  58. package/example/metro.config.js +16 -0
  59. package/example/package-lock.json +13220 -0
  60. package/example/package.json +30 -0
  61. package/example/src/App.tsx +552 -0
  62. package/example/src/hawcx.config.ts +41 -0
  63. package/example/tsconfig.json +8 -0
  64. package/ios/Frameworks/.keep +0 -0
  65. package/ios/Frameworks/HawcxFramework.xcframework/Info.plist +44 -0
  66. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/HawcxFramework +0 -0
  67. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Headers/HawcxFramework.h +16 -0
  68. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Info.plist +0 -0
  69. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.abi.json +9794 -0
  70. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.private.swiftinterface +302 -0
  71. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  72. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.swiftinterface +302 -0
  73. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/module.modulemap +6 -0
  74. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/HawcxFramework +0 -0
  75. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Headers/HawcxFramework.h +16 -0
  76. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Info.plist +0 -0
  77. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.abi.json +9794 -0
  78. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +302 -0
  79. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  80. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.swiftinterface +302 -0
  81. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.abi.json +9794 -0
  82. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +302 -0
  83. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  84. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +302 -0
  85. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/module.modulemap +6 -0
  86. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/_CodeSignature/CodeResources +234 -0
  87. package/ios/HawcxReactNative.m +51 -0
  88. package/ios/HawcxReactNative.swift +311 -0
  89. package/lib/commonjs/index.js +404 -0
  90. package/lib/commonjs/index.js.map +1 -0
  91. package/lib/module/index.js +375 -0
  92. package/lib/module/index.js.map +1 -0
  93. package/lib/typescript/__tests__/index.test.d.ts +2 -0
  94. package/lib/typescript/__tests__/index.test.d.ts.map +1 -0
  95. package/lib/typescript/index.d.ts +151 -0
  96. package/lib/typescript/index.d.ts.map +1 -0
  97. package/package.json +72 -0
  98. package/react_mobile_sdk_plan.md +240 -0
  99. package/src/__tests__/index.test.ts +163 -0
  100. package/src/index.ts +518 -0
package/src/index.ts ADDED
@@ -0,0 +1,518 @@
1
+ import {
2
+ NativeModules,
3
+ NativeEventEmitter,
4
+ Platform,
5
+ EmitterSubscription,
6
+ NativeModule,
7
+ } from 'react-native';
8
+ import { useCallback, useEffect, useRef, useState } from 'react';
9
+
10
+ const LINKING_ERROR =
11
+ "The package '@hawcx/react-native-sdk' doesn't seem to be linked.\n" +
12
+ "Please ensure you have run 'pod install' in the ios directory and rebuilt the app.";
13
+
14
+ const AUTH_EVENT = 'hawcx.auth.event';
15
+ const SESSION_EVENT = 'hawcx.session.event';
16
+ const PUSH_EVENT = 'hawcx.push.event';
17
+
18
+ export type HawcxInitializeConfig = {
19
+ projectApiKey: string;
20
+ oauthConfig?: {
21
+ clientId: string;
22
+ publicKeyPem: string;
23
+ tokenEndpoint: string;
24
+ };
25
+ endpoints?: {
26
+ authBaseUrl?: string;
27
+ };
28
+ };
29
+
30
+ export type AuthSuccessPayload = {
31
+ accessToken?: string;
32
+ refreshToken?: string;
33
+ isLoginFlow: boolean;
34
+ };
35
+
36
+ export type AuthErrorPayload = {
37
+ code: string;
38
+ message: string;
39
+ };
40
+
41
+ export type AuthEvent =
42
+ | { type: 'otp_required' }
43
+ | { type: 'auth_success'; payload: AuthSuccessPayload }
44
+ | { type: 'auth_error'; payload: AuthErrorPayload };
45
+
46
+ export type SessionEvent =
47
+ | { type: 'session_success' }
48
+ | { type: 'session_error'; payload: AuthErrorPayload };
49
+
50
+ export type PushLoginPayload = {
51
+ requestId: string;
52
+ ipAddress: string;
53
+ deviceInfo: string;
54
+ location?: string;
55
+ timestamp: string;
56
+ };
57
+
58
+ export type PushEvent =
59
+ | { type: 'push_login_request'; payload: PushLoginPayload }
60
+ | { type: 'push_error'; payload: AuthErrorPayload };
61
+
62
+ type NativeBridge = {
63
+ initialize(config: HawcxInitializeConfig): Promise<void>;
64
+ authenticate(userId: string): Promise<void>;
65
+ submitOtp(otp: string): Promise<void>;
66
+ getDeviceDetails(): Promise<void>;
67
+ webLogin(pin: string): Promise<void>;
68
+ webApprove(token: string): Promise<void>;
69
+ setApnsDeviceToken(tokenBase64: string): Promise<void>;
70
+ setFcmToken(token: string): Promise<void>;
71
+ userDidAuthenticate(): Promise<void>;
72
+ handlePushNotification(payload: Record<string, unknown>): Promise<boolean>;
73
+ approvePushRequest(requestId: string): Promise<void>;
74
+ declinePushRequest(requestId: string): Promise<void>;
75
+ };
76
+
77
+ type HawcxNativeModule = NativeBridge & NativeModule;
78
+
79
+ const HawcxReactNativeModule = NativeModules.HawcxReactNative as HawcxNativeModule | undefined;
80
+
81
+ if (!HawcxReactNativeModule) {
82
+ throw new Error(LINKING_ERROR);
83
+ }
84
+
85
+ const HawcxReactNative = HawcxReactNativeModule;
86
+
87
+ const authEventEmitter = new NativeEventEmitter(HawcxReactNativeModule);
88
+ const sessionEventEmitter = new NativeEventEmitter(HawcxReactNativeModule);
89
+ const pushEventEmitter = new NativeEventEmitter(HawcxReactNativeModule);
90
+
91
+ const isIOS = () => Platform.OS === 'ios';
92
+ const isAndroid = () => Platform.OS === 'android';
93
+
94
+ const ensureNonEmpty = (value: string, field: string): string => {
95
+ const trimmed = value?.trim();
96
+ if (!trimmed) {
97
+ throw new Error(`${field} is required`);
98
+ }
99
+ return trimmed;
100
+ };
101
+
102
+ export function initialize(config: HawcxInitializeConfig): Promise<void> {
103
+ try {
104
+ const apiKey = ensureNonEmpty(config.projectApiKey, 'projectApiKey');
105
+ return HawcxReactNative.initialize({
106
+ ...config,
107
+ projectApiKey: apiKey,
108
+ });
109
+ } catch (error) {
110
+ return Promise.reject(error);
111
+ }
112
+ }
113
+
114
+ export function authenticate(userId: string): Promise<void> {
115
+ try {
116
+ return HawcxReactNative.authenticate(ensureNonEmpty(userId, 'userId'));
117
+ } catch (error) {
118
+ return Promise.reject(error);
119
+ }
120
+ }
121
+
122
+ export function submitOtp(otp: string): Promise<void> {
123
+ try {
124
+ return HawcxReactNative.submitOtp(ensureNonEmpty(otp, 'otp'));
125
+ } catch (error) {
126
+ return Promise.reject(error);
127
+ }
128
+ }
129
+
130
+ export function getDeviceDetails(): Promise<void> {
131
+ return HawcxReactNative.getDeviceDetails();
132
+ }
133
+
134
+ export function webLogin(pin: string): Promise<void> {
135
+ try {
136
+ return HawcxReactNative.webLogin(ensureNonEmpty(pin, 'pin'));
137
+ } catch (error) {
138
+ return Promise.reject(error);
139
+ }
140
+ }
141
+
142
+ export function webApprove(token: string): Promise<void> {
143
+ try {
144
+ return HawcxReactNative.webApprove(ensureNonEmpty(token, 'token'));
145
+ } catch (error) {
146
+ return Promise.reject(error);
147
+ }
148
+ }
149
+
150
+ export function setApnsDeviceToken(tokenData: Uint8Array | number[]): Promise<void> {
151
+ if (!isIOS()) {
152
+ return Promise.resolve();
153
+ }
154
+ const buffer = Buffer.from(tokenData);
155
+ return HawcxReactNative.setApnsDeviceToken(buffer.toString('base64'));
156
+ }
157
+
158
+ export function setFcmToken(token: string): Promise<void> {
159
+ if (!isAndroid()) {
160
+ return Promise.resolve();
161
+ }
162
+ try {
163
+ return HawcxReactNative.setFcmToken(ensureNonEmpty(token, 'token'));
164
+ } catch (error) {
165
+ return Promise.reject(error);
166
+ }
167
+ }
168
+
169
+ export function setPushDeviceToken(token: Uint8Array | number[] | string): Promise<void> {
170
+ if (isIOS()) {
171
+ if (typeof token === 'string') {
172
+ return Promise.reject(
173
+ new Error('APNs tokens must be provided as byte arrays or Uint8Arrays'),
174
+ );
175
+ }
176
+ return setApnsDeviceToken(token);
177
+ }
178
+ if (isAndroid()) {
179
+ if (typeof token !== 'string') {
180
+ return Promise.reject(new Error('FCM token must be a string on Android'));
181
+ }
182
+ return setFcmToken(token);
183
+ }
184
+ return Promise.reject(
185
+ new Error(`Unsupported platform for push token registration: ${Platform.OS}`),
186
+ );
187
+ }
188
+
189
+ export function notifyUserAuthenticated(): Promise<void> {
190
+ return HawcxReactNative.userDidAuthenticate();
191
+ }
192
+
193
+ export function handlePushNotification(payload: Record<string, unknown>): Promise<boolean> {
194
+ return HawcxReactNative.handlePushNotification(payload);
195
+ }
196
+
197
+ export function approvePushRequest(requestId: string): Promise<void> {
198
+ try {
199
+ return HawcxReactNative.approvePushRequest(ensureNonEmpty(requestId, 'requestId'));
200
+ } catch (error) {
201
+ return Promise.reject(error);
202
+ }
203
+ }
204
+
205
+ export function declinePushRequest(requestId: string): Promise<void> {
206
+ try {
207
+ return HawcxReactNative.declinePushRequest(ensureNonEmpty(requestId, 'requestId'));
208
+ } catch (error) {
209
+ return Promise.reject(error);
210
+ }
211
+ }
212
+
213
+ export function addAuthListener(handler: (event: AuthEvent) => void): EmitterSubscription {
214
+ return authEventEmitter.addListener(AUTH_EVENT, handler);
215
+ }
216
+
217
+ export function removeAllListeners(): void {
218
+ authEventEmitter.removeAllListeners(AUTH_EVENT);
219
+ sessionEventEmitter.removeAllListeners(SESSION_EVENT);
220
+ pushEventEmitter.removeAllListeners(PUSH_EVENT);
221
+ }
222
+
223
+ export function addSessionListener(handler: (event: SessionEvent) => void): EmitterSubscription {
224
+ return sessionEventEmitter.addListener(SESSION_EVENT, handler);
225
+ }
226
+
227
+ export function addPushListener(handler: (event: PushEvent) => void): EmitterSubscription {
228
+ return pushEventEmitter.addListener(PUSH_EVENT, handler);
229
+ }
230
+
231
+ export const platform = Platform.OS;
232
+
233
+ export class HawcxAuthError extends Error {
234
+ public readonly code: string;
235
+ public readonly payload: AuthErrorPayload;
236
+
237
+ constructor(payload: AuthErrorPayload) {
238
+ super(payload.message);
239
+ this.name = 'HawcxAuthError';
240
+ this.code = payload.code;
241
+ this.payload = payload;
242
+ }
243
+ }
244
+
245
+ export type HawcxAuthOptions = {
246
+ onOtpRequired?: () => void;
247
+ onEvent?: (event: AuthEvent) => void;
248
+ };
249
+
250
+ export type AuthInvocation = {
251
+ promise: Promise<AuthSuccessPayload>;
252
+ cancel: () => void;
253
+ };
254
+
255
+ const AUTH_CANCELLED: AuthErrorPayload = {
256
+ code: 'auth_cancelled',
257
+ message: 'Authentication cancelled by caller',
258
+ };
259
+
260
+ export class HawcxClient {
261
+ initialize(config: HawcxInitializeConfig): Promise<void> {
262
+ return initialize(config);
263
+ }
264
+
265
+ authenticate(userId: string, options?: HawcxAuthOptions): AuthInvocation {
266
+ const authPromise = authenticate(userId);
267
+ let rejectPromise: ((reason?: unknown) => void) | null = null;
268
+ let subscription: EmitterSubscription | null = null;
269
+
270
+ const cleanup = () => {
271
+ subscription?.remove();
272
+ subscription = null;
273
+ };
274
+
275
+ const eventPromise = new Promise<AuthSuccessPayload>((resolve, reject) => {
276
+ rejectPromise = reject;
277
+ subscription = addAuthListener((event) => {
278
+ options?.onEvent?.(event);
279
+ switch (event.type) {
280
+ case 'otp_required':
281
+ options?.onOtpRequired?.();
282
+ break;
283
+ case 'auth_success':
284
+ cleanup();
285
+ resolve(event.payload);
286
+ break;
287
+ case 'auth_error':
288
+ cleanup();
289
+ reject(new HawcxAuthError(event.payload));
290
+ break;
291
+ }
292
+ });
293
+ });
294
+
295
+ authPromise.catch((error) => {
296
+ cleanup();
297
+ rejectPromise?.(error);
298
+ });
299
+
300
+ return {
301
+ promise: eventPromise,
302
+ cancel: () => {
303
+ cleanup();
304
+ rejectPromise?.(new HawcxAuthError(AUTH_CANCELLED));
305
+ },
306
+ };
307
+ }
308
+
309
+ submitOtp(otp: string): Promise<void> {
310
+ return submitOtp(otp);
311
+ }
312
+
313
+ fetchDeviceDetails(): Promise<void> {
314
+ return HawcxReactNative.getDeviceDetails();
315
+ }
316
+
317
+ webLogin(pin: string, options?: { onEvent?: (event: SessionEvent) => void }): Promise<void> {
318
+ if (options?.onEvent) {
319
+ let subscription: EmitterSubscription | null = null;
320
+ subscription = addSessionListener((event) => {
321
+ options.onEvent?.(event);
322
+ subscription?.remove();
323
+ subscription = null;
324
+ });
325
+ return HawcxReactNative.webLogin(pin).catch((error) => {
326
+ subscription?.remove();
327
+ subscription = null;
328
+ throw error;
329
+ });
330
+ }
331
+ return HawcxReactNative.webLogin(pin);
332
+ }
333
+
334
+ webApprove(token: string, options?: { onEvent?: (event: SessionEvent) => void }): Promise<void> {
335
+ if (options?.onEvent) {
336
+ let subscription: EmitterSubscription | null = null;
337
+ subscription = addSessionListener((event) => {
338
+ options.onEvent?.(event);
339
+ subscription?.remove();
340
+ subscription = null;
341
+ });
342
+ return HawcxReactNative.webApprove(token).catch((error) => {
343
+ subscription?.remove();
344
+ subscription = null;
345
+ throw error;
346
+ });
347
+ }
348
+ return HawcxReactNative.webApprove(token);
349
+ }
350
+
351
+ addListener(handler: (event: AuthEvent) => void): EmitterSubscription {
352
+ return addAuthListener(handler);
353
+ }
354
+
355
+ clearListeners(): void {
356
+ removeAllListeners();
357
+ }
358
+
359
+ setApnsDeviceToken(tokenData: Uint8Array | number[]): Promise<void> {
360
+ return setApnsDeviceToken(tokenData);
361
+ }
362
+
363
+ setFcmToken(token: string): Promise<void> {
364
+ return setFcmToken(token);
365
+ }
366
+
367
+ setPushDeviceToken(token: Uint8Array | number[] | string): Promise<void> {
368
+ return setPushDeviceToken(token);
369
+ }
370
+
371
+ notifyUserAuthenticated(): Promise<void> {
372
+ return notifyUserAuthenticated();
373
+ }
374
+
375
+ handlePushNotification(payload: Record<string, unknown>): Promise<boolean> {
376
+ return handlePushNotification(payload);
377
+ }
378
+
379
+ approvePushRequest(requestId: string): Promise<void> {
380
+ return approvePushRequest(requestId);
381
+ }
382
+
383
+ declinePushRequest(requestId: string): Promise<void> {
384
+ return declinePushRequest(requestId);
385
+ }
386
+
387
+ addPushListener(handler: (event: PushEvent) => void): EmitterSubscription {
388
+ return addPushListener(handler);
389
+ }
390
+ }
391
+
392
+ export const hawcxClient = new HawcxClient();
393
+
394
+ export type HawcxAuthHookState =
395
+ | { status: 'idle' }
396
+ | { status: 'pending' }
397
+ | { status: 'otp'; attempts: number }
398
+ | { status: 'success'; result: AuthSuccessPayload }
399
+ | { status: 'error'; error: AuthErrorPayload };
400
+
401
+ export type HawcxWebSessionState =
402
+ | { status: 'idle' }
403
+ | { status: 'loading' }
404
+ | { status: 'success' }
405
+ | { status: 'error'; error: AuthErrorPayload };
406
+
407
+ export function useHawcxAuth(client: HawcxClient = hawcxClient) {
408
+ const [state, setState] = useState<HawcxAuthHookState>({ status: 'idle' });
409
+ const otpAttempts = useRef(0);
410
+
411
+ useEffect(() => {
412
+ const subscription = client.addListener((event) => {
413
+ switch (event.type) {
414
+ case 'otp_required':
415
+ otpAttempts.current += 1;
416
+ setState({ status: 'otp', attempts: otpAttempts.current });
417
+ break;
418
+ case 'auth_success':
419
+ otpAttempts.current = 0;
420
+ setState({ status: 'success', result: event.payload });
421
+ break;
422
+ case 'auth_error':
423
+ otpAttempts.current = 0;
424
+ setState({ status: 'error', error: event.payload });
425
+ break;
426
+ }
427
+ });
428
+
429
+ return () => {
430
+ subscription.remove();
431
+ };
432
+ }, [client]);
433
+
434
+ const authenticateUser = useCallback(
435
+ (userId: string, options?: HawcxAuthOptions) => {
436
+ setState({ status: 'pending' });
437
+ return client.authenticate(userId, options);
438
+ },
439
+ [client],
440
+ );
441
+
442
+ const submitOtpCode = useCallback(
443
+ (otp: string) => {
444
+ return client.submitOtp(otp);
445
+ },
446
+ [client],
447
+ );
448
+
449
+ const reset = useCallback(() => {
450
+ otpAttempts.current = 0;
451
+ setState({ status: 'idle' });
452
+ }, []);
453
+
454
+ return {
455
+ state,
456
+ authenticate: authenticateUser,
457
+ submitOtp: submitOtpCode,
458
+ reset,
459
+ };
460
+ }
461
+
462
+ /** @internal Testing hook to emit synthetic events */
463
+ export const __INTERNAL_EVENTS__ = {
464
+ authEmitter: authEventEmitter,
465
+ sessionEmitter: sessionEventEmitter,
466
+ pushEmitter: pushEventEmitter,
467
+ authEventName: AUTH_EVENT,
468
+ sessionEventName: SESSION_EVENT,
469
+ pushEventName: PUSH_EVENT,
470
+ };
471
+
472
+ export function useHawcxWebLogin(client: HawcxClient = hawcxClient) {
473
+ const [state, setState] = useState<HawcxWebSessionState>({ status: 'idle' });
474
+
475
+ useEffect(() => {
476
+ const subscription = addSessionListener((event) => {
477
+ if (event.type === 'session_success') {
478
+ setState({ status: 'success' });
479
+ } else {
480
+ setState({ status: 'error', error: event.payload });
481
+ }
482
+ });
483
+ return () => subscription.remove();
484
+ }, []);
485
+
486
+ const start = useCallback(
487
+ (pin: string) => {
488
+ setState({ status: 'loading' });
489
+ return client.webLogin(pin);
490
+ },
491
+ [client],
492
+ );
493
+
494
+ const approve = useCallback(
495
+ (token: string) => {
496
+ setState({ status: 'loading' });
497
+ return client.webApprove(token);
498
+ },
499
+ [client],
500
+ );
501
+
502
+ const fetchDeviceDetails = useCallback(() => {
503
+ setState({ status: 'loading' });
504
+ return client.fetchDeviceDetails();
505
+ }, [client]);
506
+
507
+ const reset = useCallback(() => {
508
+ setState({ status: 'idle' });
509
+ }, []);
510
+
511
+ return {
512
+ state,
513
+ webLogin: start,
514
+ webApprove: approve,
515
+ getDeviceDetails: fetchDeviceDetails,
516
+ reset,
517
+ };
518
+ }