@hawcx/react-native-sdk 1.0.7 → 1.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.
Files changed (132) hide show
  1. package/CHANGELOG.md +3 -6
  2. package/HawcxReactNative.podspec +2 -2
  3. package/README.md +327 -109
  4. package/android/build.gradle +2 -2
  5. package/android/src/main/java/com/hawcx/reactnative/HawcxEventDispatcher.kt +4 -0
  6. package/android/src/main/java/com/hawcx/reactnative/HawcxReactNativeModule.kt +324 -1
  7. package/android/src/main/java/com/hawcx/reactnative/v6/HawcxV6Bridge.kt +402 -0
  8. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/HawcxFramework +0 -0
  9. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Info.plist +0 -0
  10. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.abi.json +22143 -0
  11. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.private.swiftinterface +649 -21
  12. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  13. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.swiftinterface +649 -21
  14. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/HawcxFramework +0 -0
  15. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Info.plist +0 -0
  16. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.abi.json +22143 -0
  17. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +649 -21
  18. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  19. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.swiftinterface +649 -21
  20. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.abi.json +22143 -0
  21. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +649 -21
  22. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  23. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +649 -21
  24. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/_CodeSignature/CodeResources +21 -21
  25. package/ios/HawcxReactNative.m +56 -0
  26. package/ios/HawcxReactNative.swift +380 -1
  27. package/ios/HawcxV6BridgeSupport.swift +468 -0
  28. package/lib/commonjs/index.js +326 -3
  29. package/lib/commonjs/index.js.map +1 -1
  30. package/lib/commonjs/v6Normalization.js +325 -0
  31. package/lib/commonjs/v6Normalization.js.map +1 -0
  32. package/lib/commonjs/v6State.js +186 -0
  33. package/lib/commonjs/v6State.js.map +1 -0
  34. package/lib/commonjs/v6Types.js +2 -0
  35. package/lib/commonjs/v6Types.js.map +1 -0
  36. package/lib/commonjs/v6WebLogin.js +101 -0
  37. package/lib/commonjs/v6WebLogin.js.map +1 -0
  38. package/lib/module/index.js +287 -1
  39. package/lib/module/index.js.map +1 -1
  40. package/lib/module/v6Normalization.js +318 -0
  41. package/lib/module/v6Normalization.js.map +1 -0
  42. package/lib/module/v6State.js +173 -0
  43. package/lib/module/v6State.js.map +1 -0
  44. package/lib/module/v6Types.js +2 -0
  45. package/lib/module/v6Types.js.map +1 -0
  46. package/lib/module/v6WebLogin.js +92 -0
  47. package/lib/module/v6WebLogin.js.map +1 -0
  48. package/lib/typescript/index.d.ts +83 -0
  49. package/lib/typescript/index.d.ts.map +1 -1
  50. package/lib/typescript/v6Normalization.d.ts +3 -0
  51. package/lib/typescript/v6Normalization.d.ts.map +1 -0
  52. package/lib/typescript/v6State.d.ts +13 -0
  53. package/lib/typescript/v6State.d.ts.map +1 -0
  54. package/lib/typescript/v6Types.d.ts +157 -0
  55. package/lib/typescript/v6Types.d.ts.map +1 -0
  56. package/lib/typescript/v6WebLogin.d.ts +32 -0
  57. package/lib/typescript/v6WebLogin.d.ts.map +1 -0
  58. package/package.json +21 -9
  59. package/src/index.ts +477 -0
  60. package/src/v6Normalization.ts +356 -0
  61. package/src/v6State.ts +238 -0
  62. package/src/v6Types.ts +194 -0
  63. package/src/v6WebLogin.ts +154 -0
  64. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  65. package/android/gradle/wrapper/gradle-wrapper.properties +0 -6
  66. package/android/gradlew +0 -185
  67. package/android/gradlew.bat +0 -89
  68. package/android/libs/hawcx-5.1.2.aar +0 -0
  69. package/docs/RELEASE.md +0 -129
  70. package/example/README.md +0 -59
  71. package/example/android/app/build.gradle +0 -126
  72. package/example/android/app/debug.keystore +0 -0
  73. package/example/android/app/proguard-rules.pro +0 -10
  74. package/example/android/app/src/debug/AndroidManifest.xml +0 -9
  75. package/example/android/app/src/main/AndroidManifest.xml +0 -27
  76. package/example/android/app/src/main/java/com/hawcx/example/MainActivity.kt +0 -22
  77. package/example/android/app/src/main/java/com/hawcx/example/MainApplication.kt +0 -45
  78. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -36
  79. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  80. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  81. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  82. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  83. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  84. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  85. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  86. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  87. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  88. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  89. package/example/android/app/src/main/res/values/strings.xml +0 -3
  90. package/example/android/app/src/main/res/values/styles.xml +0 -9
  91. package/example/android/build.gradle +0 -35
  92. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  93. package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -7
  94. package/example/android/gradle.properties +0 -41
  95. package/example/android/gradlew +0 -249
  96. package/example/android/gradlew.bat +0 -92
  97. package/example/android/local.properties +0 -2
  98. package/example/android/settings.gradle +0 -38
  99. package/example/app.json +0 -4
  100. package/example/babel.config.js +0 -3
  101. package/example/e2e/README.md +0 -17
  102. package/example/e2e/hawcx-login.yaml +0 -14
  103. package/example/index.js +0 -5
  104. package/example/ios/.xcode.env +0 -11
  105. package/example/ios/HawcxExampleApp/AppDelegate.h +0 -6
  106. package/example/ios/HawcxExampleApp/AppDelegate.mm +0 -31
  107. package/example/ios/HawcxExampleApp/Images.xcassets/AppIcon.appiconset/Contents.json +0 -53
  108. package/example/ios/HawcxExampleApp/Images.xcassets/Contents.json +0 -6
  109. package/example/ios/HawcxExampleApp/Info.plist +0 -55
  110. package/example/ios/HawcxExampleApp/LaunchScreen.storyboard +0 -47
  111. package/example/ios/HawcxExampleApp/PrivacyInfo.xcprivacy +0 -37
  112. package/example/ios/HawcxExampleApp/main.m +0 -10
  113. package/example/ios/HawcxExampleApp.xcodeproj/project.pbxproj +0 -704
  114. package/example/ios/HawcxExampleApp.xcodeproj/project.xcworkspace/xcuserdata/agambhullar.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  115. package/example/ios/HawcxExampleApp.xcodeproj/xcshareddata/xcschemes/HawcxExampleApp.xcscheme +0 -90
  116. package/example/ios/HawcxExampleApp.xcodeproj/xcuserdata/agambhullar.xcuserdatad/xcschemes/xcschememanagement.plist +0 -16
  117. package/example/ios/HawcxExampleApp.xcworkspace/contents.xcworkspacedata +0 -10
  118. package/example/ios/HawcxExampleAppTests/HawcxExampleAppTests.m +0 -66
  119. package/example/ios/HawcxExampleAppTests/Info.plist +0 -24
  120. package/example/ios/Podfile +0 -79
  121. package/example/ios/Podfile.lock +0 -1290
  122. package/example/metro.config.js +0 -16
  123. package/example/package-lock.json +0 -13220
  124. package/example/package.json +0 -30
  125. package/example/src/App.tsx +0 -755
  126. package/example/src/hawcx.config.ts +0 -25
  127. package/example/tsconfig.json +0 -8
  128. package/ios/Frameworks/.keep +0 -0
  129. package/lib/typescript/__tests__/index.test.d.ts +0 -2
  130. package/lib/typescript/__tests__/index.test.d.ts.map +0 -1
  131. package/react_mobile_sdk_plan.md +0 -242
  132. package/src/__tests__/index.test.ts +0 -206
@@ -1,755 +0,0 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import {
3
- Platform,
4
- SafeAreaView,
5
- ScrollView as RNScrollView,
6
- StyleSheet,
7
- Switch,
8
- Text,
9
- TextInput,
10
- TouchableOpacity,
11
- View,
12
- } from 'react-native';
13
- import {
14
- initialize,
15
- useHawcxAuth,
16
- useHawcxWebLogin,
17
- addAuthListener,
18
- addSessionListener,
19
- addPushListener,
20
- setPushDeviceToken,
21
- notifyUserAuthenticated,
22
- storeBackendOAuthTokens,
23
- handlePushNotification as forwardPushPayload,
24
- approvePushRequest,
25
- declinePushRequest,
26
- type HawcxInitializeConfig,
27
- type PushEvent,
28
- type AuthEvent,
29
- type SessionEvent,
30
- type AuthorizationCodePayload,
31
- type AdditionalVerificationPayload,
32
- } from '@hawcx/react-native-sdk';
33
- import { DEFAULT_HAWCX_CONFIG } from './hawcx.config';
34
-
35
- const COLORS = {
36
- bg: '#0f172a',
37
- card: '#1e293b',
38
- accent: '#38bdf8',
39
- text: '#f8fafc',
40
- muted: '#94a3b8',
41
- error: '#f87171',
42
- success: '#4ade80',
43
- };
44
-
45
- const DEFAULT_BACKEND_URL = 'http://localhost:3000/api/login';
46
- const BACKEND_FLOW_ENABLED = false;
47
-
48
- type BackendExchangeResponse = {
49
- success: boolean;
50
- message?: string;
51
- error?: string;
52
- access_token?: string;
53
- refresh_token?: string;
54
- };
55
-
56
- const App = () => {
57
- const [activeConfig, setActiveConfig] = useState<HawcxInitializeConfig | null>(
58
- DEFAULT_HAWCX_CONFIG,
59
- );
60
- const [initStatus, setInitStatus] = useState<'idle' | 'initializing' | 'ready' | 'error'>('idle');
61
- const [initError, setInitError] = useState<string | null>(null);
62
- const [email, setEmail] = useState('user@example.com');
63
- const [otp, setOtp] = useState('');
64
- const [pin, setPin] = useState('');
65
- const [token, setToken] = useState('');
66
- const [pushTokenInput, setPushTokenInput] = useState('');
67
- const [pushPayloadInput, setPushPayloadInput] = useState('{"request_id":"","ip_address":"","deviceInfo":"","timestamp":""}');
68
- const [pushRequestId, setPushRequestId] = useState('');
69
- const [pushEvents, setPushEvents] = useState<PushEvent[]>([]);
70
- const [pushStatus, setPushStatus] = useState<string | null>(null);
71
- const [pushError, setPushError] = useState<string | null>(null);
72
- const [logs, setLogs] = useState<string[]>([]);
73
- const [loggingEnabled, setLoggingEnabled] = useState(false);
74
- const [backendUrl, setBackendUrl] = useState(DEFAULT_BACKEND_URL);
75
- const [lastAuthCode, setLastAuthCode] = useState<AuthorizationCodePayload | null>(null);
76
- const [additionalVerification, setAdditionalVerification] =
77
- useState<AdditionalVerificationPayload | null>(null);
78
- const [backendStatus, setBackendStatus] = useState<string | null>(null);
79
- const [backendError, setBackendError] = useState<string | null>(null);
80
-
81
- const { state: authState, authenticate, submitOtp } = useHawcxAuth();
82
- const web = useHawcxWebLogin();
83
-
84
- const appendLog = useCallback(
85
- (message: string) => {
86
- if (!loggingEnabled) {
87
- return;
88
- }
89
- const timestamp = new Date().toLocaleTimeString();
90
- setLogs((prev) => [`[${timestamp}] ${message}`, ...prev].slice(0, 100));
91
- },
92
- [loggingEnabled],
93
- );
94
-
95
- const exchangeWithBackend = useCallback(
96
- async (payload: AuthorizationCodePayload) => {
97
- setLastAuthCode(payload);
98
- setAdditionalVerification(null);
99
- setBackendError(null);
100
- if (!BACKEND_FLOW_ENABLED) {
101
- setBackendStatus(
102
- `Demo mode: treated authorization code ${payload.code.slice(0, 6)}… as a successful login.`,
103
- );
104
- appendLog(`demo login completed with authorization code ${payload.code}`);
105
- return;
106
- }
107
-
108
- const trimmedUrl = backendUrl.trim();
109
- if (!trimmedUrl) {
110
- setBackendError('Enter a backend URL to send the authorization code.');
111
- return;
112
- }
113
- const trimmedEmail = email.trim();
114
- if (!trimmedEmail) {
115
- setBackendError('Enter an email before exchanging the authorization code.');
116
- return;
117
- }
118
-
119
- setBackendStatus('Forwarding authorization code to backend…');
120
- appendLog('forwarding authorization code to backend');
121
-
122
- try {
123
- const response = await fetch(trimmedUrl, {
124
- method: 'POST',
125
- headers: { 'Content-Type': 'application/json' },
126
- body: JSON.stringify({
127
- code: payload.code,
128
- email: trimmedEmail,
129
- expires_in: payload.expiresIn,
130
- }),
131
- });
132
- const text = await response.text();
133
- let parsed: BackendExchangeResponse = { success: response.ok };
134
- if (text) {
135
- try {
136
- parsed = JSON.parse(text);
137
- } catch {
138
- // Non-JSON response – fallback to HTTP status.
139
- }
140
- }
141
- if (!response.ok || parsed.success === false) {
142
- throw new Error(parsed.error ?? parsed.message ?? `Backend responded with ${response.status}`);
143
- }
144
-
145
- if (parsed.access_token) {
146
- await storeBackendOAuthTokens(
147
- trimmedEmail,
148
- parsed.access_token,
149
- parsed.refresh_token ?? undefined,
150
- );
151
- await notifyUserAuthenticated();
152
- setBackendStatus('Backend tokens stored with Hawcx SDK. Login complete.');
153
- appendLog('stored backend-issued tokens via Hawcx SDK');
154
- } else {
155
- setBackendStatus(
156
- parsed.message ?? 'Backend accepted the code but did not return access tokens.',
157
- );
158
- }
159
- } catch (error) {
160
- const message =
161
- (error as Error)?.message ?? 'Failed to reach backend. Check ngrok/local server.';
162
- setBackendError(message);
163
- setBackendStatus(null);
164
- appendLog(`backend exchange failed: ${message}`);
165
- }
166
- },
167
- [appendLog, backendUrl, email],
168
- );
169
-
170
- useEffect(() => {
171
- if (!activeConfig) {
172
- setInitStatus('error');
173
- setInitError(
174
- 'Add your project API key in example/src/hawcx.config.ts or via the in-app form to initialize the SDK.',
175
- );
176
- return;
177
- }
178
- setInitStatus('initializing');
179
- setInitError(null);
180
- initialize(activeConfig)
181
- .then(() => {
182
- setInitStatus('ready');
183
- appendLog('SDK initialized successfully');
184
- })
185
- .catch((err) => {
186
- console.warn('Init failed', err);
187
- setInitStatus('error');
188
- setInitError(err?.message ?? 'Failed to initialize the Hawcx SDK');
189
- appendLog(`SDK initialization failed: ${err?.message ?? 'unknown error'}`);
190
- });
191
- }, [activeConfig, appendLog]);
192
-
193
- useEffect(() => {
194
- const authSubscription = addAuthListener((event: AuthEvent) => {
195
- appendLog(`auth event: ${event.type}`);
196
- if (event.type === 'auth_error') {
197
- appendLog(`auth error payload: ${event.payload.code} ${event.payload.message}`);
198
- }
199
- });
200
- const sessionSubscription = addSessionListener((event: SessionEvent) => {
201
- appendLog(`session event: ${event.type}`);
202
- if (event.type === 'session_error') {
203
- appendLog(`session error payload: ${event.payload.code} ${event.payload.message}`);
204
- }
205
- });
206
- const pushSubscription = addPushListener((event) => {
207
- setPushEvents((prev) => [event, ...prev].slice(0, 4));
208
- setPushStatus(`Received push event: ${event.type}`);
209
- appendLog(`push event: ${event.type}`);
210
- });
211
- return () => {
212
- authSubscription.remove();
213
- sessionSubscription.remove();
214
- pushSubscription.remove();
215
- };
216
- }, [appendLog]);
217
-
218
- useEffect(() => {
219
- switch (authState.status) {
220
- case 'authorization_code':
221
- appendLog(
222
- `authorization_code event received (backend=${BACKEND_FLOW_ENABLED ? 'on' : 'off'})`,
223
- );
224
- void exchangeWithBackend(authState.payload);
225
- break;
226
- case 'additional_verification_required':
227
- appendLog(
228
- `additional_verification_required: ${authState.payload.sessionId} ${
229
- authState.payload.detail ?? ''
230
- }`.trim(),
231
- );
232
- setAdditionalVerification(authState.payload);
233
- break;
234
- case 'success':
235
- appendLog('auth_success event received from native SDK');
236
- setBackendStatus('Hawcx SDK stored tokens automatically.');
237
- setBackendError(null);
238
- break;
239
- case 'error':
240
- appendLog(`auth_error: ${authState.error.code} ${authState.error.message}`);
241
- if (!backendError) {
242
- setBackendError(authState.error.message);
243
- }
244
- break;
245
- case 'pending':
246
- setBackendError(null);
247
- setBackendStatus(null);
248
- setAdditionalVerification(null);
249
- setLastAuthCode(null);
250
- break;
251
- default:
252
- break;
253
- }
254
- }, [appendLog, authState, backendError, exchangeWithBackend]);
255
-
256
- const summarizeCode = useCallback((code: string) => {
257
- if (code.length <= 10) {
258
- return code;
259
- }
260
- return `${code.slice(0, 6)}…${code.slice(-4)}`;
261
- }, []);
262
- const isReady = initStatus === 'ready';
263
- const maskedKey = useMemo(() => {
264
- if (!activeConfig?.projectApiKey) {
265
- return '';
266
- }
267
- const suffix = activeConfig.projectApiKey.slice(-4);
268
- return `Active key ••••${suffix}`;
269
- }, [activeConfig?.projectApiKey]);
270
-
271
- const requireReady = () => {
272
- if (!isReady) {
273
- setInitError('Initialize the SDK with your credentials in hawcx.config.ts before calling this action.');
274
- return false;
275
- }
276
- return true;
277
- };
278
-
279
- const startAuth = () => {
280
- if (!requireReady()) {
281
- return;
282
- }
283
- appendLog(`trigger authenticate for ${email}`);
284
- authenticate(email);
285
- };
286
-
287
- const submitOtpCode = () => {
288
- if (!requireReady()) {
289
- return;
290
- }
291
- appendLog('submit OTP');
292
- submitOtp(otp);
293
- setOtp('');
294
- };
295
-
296
- const registerPushToken = async () => {
297
- if (!requireReady()) {
298
- return;
299
- }
300
- const trimmed = pushTokenInput.trim();
301
- if (!trimmed) {
302
- setPushError('Enter a token first (FCM token for Android or byte list for iOS).');
303
- return;
304
- }
305
- setPushError(null);
306
- try {
307
- if (Platform.OS === 'ios') {
308
- const bytes = trimmed
309
- .split(',')
310
- .map((segment) => parseInt(segment.trim(), 10))
311
- .filter((value) => !Number.isNaN(value));
312
- if (!bytes.length) {
313
- throw new Error('Provide a comma-separated list of APNs byte values for iOS.');
314
- }
315
- await setPushDeviceToken(bytes);
316
- } else {
317
- await setPushDeviceToken(trimmed);
318
- }
319
- appendLog('push token registered with native SDK');
320
- setPushStatus('Push token submitted to the Hawcx SDK.');
321
- } catch (error: unknown) {
322
- setPushError((error as Error)?.message ?? 'Failed to register token');
323
- }
324
- };
325
-
326
- const forwardPush = async () => {
327
- if (!requireReady()) {
328
- return;
329
- }
330
- setPushError(null);
331
- try {
332
- const parsed = JSON.parse(pushPayloadInput);
333
- await forwardPushPayload(parsed);
334
- appendLog('forwarded push payload to native SDK');
335
- setPushStatus('Forwarded payload to the Hawcx SDK.');
336
- } catch (error: unknown) {
337
- setPushError((error as Error)?.message ?? 'Invalid JSON payload');
338
- }
339
- };
340
-
341
- const onApprovePush = async () => {
342
- try {
343
- await approvePushRequest(pushRequestId.trim());
344
- appendLog(`approved push request ${pushRequestId.trim()}`);
345
- setPushStatus('Approved push request');
346
- } catch (error: unknown) {
347
- setPushError((error as Error)?.message ?? 'Failed to approve push request');
348
- }
349
- };
350
-
351
- const onDeclinePush = async () => {
352
- try {
353
- await declinePushRequest(pushRequestId.trim());
354
- appendLog(`declined push request ${pushRequestId.trim()}`);
355
- setPushStatus('Declined push request');
356
- } catch (error: unknown) {
357
- setPushError((error as Error)?.message ?? 'Failed to decline push request');
358
- }
359
- };
360
-
361
- const markUserAuthenticated = async () => {
362
- try {
363
- await notifyUserAuthenticated();
364
- appendLog('notified native SDK that user authenticated');
365
- setPushStatus('Notified native SDK to register push token.');
366
- } catch (error: unknown) {
367
- setPushError((error as Error)?.message ?? 'Failed to notify Hawcx SDK');
368
- }
369
- };
370
-
371
- const ScrollContainer = RNScrollView ?? View;
372
-
373
- return (
374
- <SafeAreaView style={styles.container}>
375
- <ScrollContainer
376
- contentContainerStyle={ScrollContainer === View ? undefined : styles.scrollContent}
377
- style={ScrollContainer === View ? styles.viewFallback : undefined}>
378
- <Text style={styles.title}>Hawcx React Native SDK</Text>
379
-
380
- <View style={styles.card}>
381
- <Text style={styles.cardTitle}>SDK Status</Text>
382
- <Text style={styles.status}>State: {initStatus === 'ready' ? 'Ready' : initStatus}</Text>
383
- {!!maskedKey && <Text style={styles.status}>{maskedKey}</Text>}
384
- {!!initError && <Text style={[styles.status, styles.errorText]}>{initError}</Text>}
385
- </View>
386
-
387
- <View style={styles.card}>
388
- <Text style={styles.cardTitle}>V5 Authentication</Text>
389
- <TextInput
390
- style={styles.input}
391
- placeholder="Email"
392
- placeholderTextColor={COLORS.muted}
393
- value={email}
394
- onChangeText={setEmail}
395
- autoCapitalize="none"
396
- />
397
- <TouchableOpacity
398
- style={[styles.button, !isReady && styles.buttonDisabled]}
399
- onPress={startAuth}
400
- disabled={!isReady}>
401
- <Text style={styles.buttonText}>Authenticate</Text>
402
- </TouchableOpacity>
403
- {authState.status === 'otp' && (
404
- <View style={styles.otpRow}>
405
- <TextInput
406
- style={[styles.input, styles.otpInput]}
407
- placeholder="OTP"
408
- placeholderTextColor={COLORS.muted}
409
- value={otp}
410
- onChangeText={setOtp}
411
- keyboardType="number-pad"
412
- />
413
- <TouchableOpacity
414
- style={[styles.button, styles.otpButton, !isReady && styles.buttonDisabled]}
415
- onPress={submitOtpCode}
416
- disabled={!isReady}>
417
- <Text style={styles.buttonText}>Submit</Text>
418
- </TouchableOpacity>
419
- </View>
420
- )}
421
- <Text style={styles.status}>State: {authState.status}</Text>
422
- {authState.status === 'error' && (
423
- <Text style={[styles.status, styles.errorText]}>
424
- {authState.error.code}: {authState.error.message}
425
- </Text>
426
- )}
427
- {authState.status === 'success' && (
428
- <Text style={[styles.status, styles.successText]}>Tokens received from Hawcx</Text>
429
- )}
430
- </View>
431
-
432
- <View style={styles.card}>
433
- <Text style={styles.cardTitle}>Authorization Code & Backend Exchange</Text>
434
- {!BACKEND_FLOW_ENABLED ? (
435
- <Text style={styles.status}>
436
- Demo mode: codes are treated as success locally. Set `BACKEND_FLOW_ENABLED = true` in
437
- `App.tsx` to forward codes to your backend.
438
- </Text>
439
- ) : (
440
- <>
441
- <TextInput
442
- style={styles.input}
443
- placeholder="https://example-ngrok-url.ngrok-free.dev/api/login"
444
- placeholderTextColor={COLORS.muted}
445
- autoCapitalize="none"
446
- value={backendUrl}
447
- onChangeText={setBackendUrl}
448
- />
449
- <TouchableOpacity
450
- style={[styles.button, styles.secondaryButton]}
451
- onPress={() => setBackendUrl(DEFAULT_BACKEND_URL)}>
452
- <Text style={styles.buttonText}>Use localhost:3000</Text>
453
- </TouchableOpacity>
454
- </>
455
- )}
456
- {lastAuthCode && (
457
- <View style={styles.codeBox}>
458
- <Text style={styles.status}>Latest code: </Text>
459
- <Text style={styles.monoText}>{summarizeCode(lastAuthCode.code)}</Text>
460
- <Text style={styles.status}>
461
- Expires in approximately {lastAuthCode.expiresIn ?? 60} seconds
462
- </Text>
463
- </View>
464
- )}
465
- {BACKEND_FLOW_ENABLED && lastAuthCode && (
466
- <TouchableOpacity
467
- style={[styles.button, styles.rowButton]}
468
- onPress={() => exchangeWithBackend(lastAuthCode)}>
469
- <Text style={styles.buttonText}>Retry Backend Exchange</Text>
470
- </TouchableOpacity>
471
- )}
472
- {backendStatus && <Text style={[styles.status, styles.successText]}>{backendStatus}</Text>}
473
- {backendError && <Text style={[styles.status, styles.errorText]}>{backendError}</Text>}
474
- {additionalVerification && (
475
- <Text style={[styles.status, styles.errorText]}>
476
- Additional verification required ({additionalVerification.sessionId}).{' '}
477
- {additionalVerification.detail ?? 'Complete verification inside Hawcx Admin.'}
478
- </Text>
479
- )}
480
- </View>
481
-
482
- <View style={styles.card}>
483
- <Text style={styles.cardTitle}>Web Login</Text>
484
- <TextInput
485
- style={styles.input}
486
- placeholder="Web PIN"
487
- placeholderTextColor={COLORS.muted}
488
- value={pin}
489
- onChangeText={setPin}
490
- />
491
- <TouchableOpacity
492
- style={[styles.button, !isReady && styles.buttonDisabled]}
493
- disabled={!isReady}
494
- onPress={() => {
495
- if (requireReady()) {
496
- web.webLogin(pin);
497
- }
498
- }}>
499
- <Text style={styles.buttonText}>Validate PIN</Text>
500
- </TouchableOpacity>
501
- <TextInput
502
- style={styles.input}
503
- placeholder="Web Token"
504
- placeholderTextColor={COLORS.muted}
505
- value={token}
506
- onChangeText={setToken}
507
- />
508
- <TouchableOpacity
509
- style={[styles.button, !isReady && styles.buttonDisabled]}
510
- disabled={!isReady}
511
- onPress={() => {
512
- if (requireReady()) {
513
- web.webApprove(token);
514
- }
515
- }}>
516
- <Text style={styles.buttonText}>Approve Token</Text>
517
- </TouchableOpacity>
518
- <Text style={styles.status}>Web State: {web.state.status}</Text>
519
- {web.state.status === 'error' && (
520
- <Text style={[styles.status, styles.errorText]}>
521
- {web.state.error.code}: {web.state.error.message}
522
- </Text>
523
- )}
524
- </View>
525
-
526
- <View style={styles.card}>
527
- <Text style={styles.cardTitle}>Push Approvals (Manual Harness)</Text>
528
- <Text style={styles.status}>
529
- Token format: Android expects the FCM string. iOS expects a comma-separated list of APNs byte values.
530
- </Text>
531
- <TextInput
532
- style={[styles.input, styles.payloadInput]}
533
- placeholder={Platform.OS === 'ios' ? 'e.g. 42, 13, 255' : 'FCM token'}
534
- placeholderTextColor={COLORS.muted}
535
- value={pushTokenInput}
536
- onChangeText={setPushTokenInput}
537
- multiline
538
- />
539
- <View style={styles.row}>
540
- <TouchableOpacity style={[styles.button, styles.rowButton]} onPress={registerPushToken} disabled={!isReady}>
541
- <Text style={styles.buttonText}>Register Token</Text>
542
- </TouchableOpacity>
543
- <TouchableOpacity style={[styles.button, styles.rowButton]} onPress={markUserAuthenticated} disabled={!isReady}>
544
- <Text style={styles.buttonText}>Notify Authenticated</Text>
545
- </TouchableOpacity>
546
- </View>
547
-
548
- <TextInput
549
- style={[styles.input, styles.payloadInput]}
550
- placeholder='Raw push payload JSON (e.g. {"request_id": "..."} )'
551
- placeholderTextColor={COLORS.muted}
552
- value={pushPayloadInput}
553
- onChangeText={setPushPayloadInput}
554
- multiline
555
- />
556
- <TouchableOpacity style={[styles.button, !isReady && styles.buttonDisabled]} onPress={forwardPush} disabled={!isReady}>
557
- <Text style={styles.buttonText}>Send Payload to SDK</Text>
558
- </TouchableOpacity>
559
-
560
- <TextInput
561
- style={styles.input}
562
- placeholder="Request ID for approve/decline"
563
- placeholderTextColor={COLORS.muted}
564
- value={pushRequestId}
565
- onChangeText={setPushRequestId}
566
- />
567
- <View style={styles.row}>
568
- <TouchableOpacity
569
- style={[styles.button, styles.successButton, (!isReady || !pushRequestId.trim()) && styles.buttonDisabled]}
570
- onPress={onApprovePush}
571
- disabled={!isReady || !pushRequestId.trim()}>
572
- <Text style={styles.buttonText}>Approve</Text>
573
- </TouchableOpacity>
574
- <TouchableOpacity
575
- style={[styles.button, styles.errorButton, (!isReady || !pushRequestId.trim()) && styles.buttonDisabled]}
576
- onPress={onDeclinePush}
577
- disabled={!isReady || !pushRequestId.trim()}>
578
- <Text style={styles.buttonText}>Decline</Text>
579
- </TouchableOpacity>
580
- </View>
581
-
582
- {!!pushStatus && <Text style={[styles.status, styles.successText]}>{pushStatus}</Text>}
583
- {!!pushError && <Text style={[styles.status, styles.errorText]}>{pushError}</Text>}
584
-
585
- <View>
586
- <Text style={styles.status}>Recent Push Events</Text>
587
- {pushEvents.length === 0 && <Text style={styles.status}>Waiting for events…</Text>}
588
- {pushEvents.map((event, index) => (
589
- <View key={`${event.type}-${index}`} style={styles.pushEvent}>
590
- <Text style={styles.monoText}>{event.type}</Text>
591
- {'payload' in event && event.payload && (
592
- <Text style={[styles.monoText, styles.payloadText]}>{JSON.stringify(event.payload, null, 2)}</Text>
593
- )}
594
- </View>
595
- ))}
596
- </View>
597
- </View>
598
-
599
- <View style={styles.card}>
600
- <View style={styles.loggerHeader}>
601
- <Text style={styles.cardTitle}>Logging</Text>
602
- <View style={styles.loggerToggle}>
603
- <Text style={styles.status}>{loggingEnabled ? 'On' : 'Off'}</Text>
604
- <Switch value={loggingEnabled} onValueChange={setLoggingEnabled} />
605
- </View>
606
- </View>
607
- <Text style={styles.status}>
608
- Enable logging to see SDK events, errors, and push actions below. Logs are kept in-memory and
609
- capped to the most recent 10 entries.
610
- </Text>
611
- {logs.length === 0 ? (
612
- <Text style={styles.status}>No logs yet.</Text>
613
- ) : (
614
- logs.slice(0, 10).map((log) => (
615
- <Text key={`${log}`} style={[styles.status, styles.logLine]}>
616
- {log}
617
- </Text>
618
- ))
619
- )}
620
- </View>
621
- </ScrollContainer>
622
- </SafeAreaView>
623
- );
624
- };
625
-
626
- const styles = StyleSheet.create({
627
- container: {
628
- flex: 1,
629
- backgroundColor: COLORS.bg,
630
- },
631
- scrollContent: {
632
- padding: 16,
633
- gap: 16,
634
- paddingBottom: 32,
635
- },
636
- viewFallback: {
637
- padding: 16,
638
- gap: 16,
639
- },
640
- title: {
641
- color: COLORS.text,
642
- fontSize: 22,
643
- fontWeight: '600',
644
- },
645
- card: {
646
- backgroundColor: COLORS.card,
647
- padding: 16,
648
- borderRadius: 12,
649
- gap: 12,
650
- },
651
- cardTitle: {
652
- color: COLORS.text,
653
- fontSize: 18,
654
- fontWeight: '500',
655
- },
656
- input: {
657
- backgroundColor: '#0b1220',
658
- borderRadius: 8,
659
- paddingHorizontal: 12,
660
- paddingVertical: 10,
661
- color: COLORS.text,
662
- },
663
- button: {
664
- backgroundColor: COLORS.accent,
665
- paddingVertical: 12,
666
- borderRadius: 8,
667
- alignItems: 'center',
668
- },
669
- buttonDisabled: {
670
- opacity: 0.5,
671
- },
672
- buttonText: {
673
- color: '#0f172a',
674
- fontWeight: '600',
675
- },
676
- row: {
677
- flexDirection: 'row',
678
- gap: 12,
679
- },
680
- switchRow: {
681
- flexDirection: 'row',
682
- alignItems: 'center',
683
- justifyContent: 'space-between',
684
- },
685
- rowButton: {
686
- flex: 1,
687
- },
688
- payloadInput: {
689
- minHeight: 70,
690
- textAlignVertical: 'top',
691
- },
692
- status: {
693
- color: COLORS.muted,
694
- },
695
- errorText: {
696
- color: COLORS.error,
697
- },
698
- successText: {
699
- color: COLORS.success,
700
- },
701
- successButton: {
702
- backgroundColor: COLORS.success,
703
- },
704
- errorButton: {
705
- backgroundColor: COLORS.error,
706
- },
707
- secondaryButton: {
708
- backgroundColor: '#475569',
709
- },
710
- otpRow: {
711
- flexDirection: 'row',
712
- gap: 8,
713
- },
714
- otpInput: {
715
- flex: 1,
716
- },
717
- otpButton: {
718
- flex: 0.6,
719
- },
720
- pushEvent: {
721
- backgroundColor: '#0b1527',
722
- padding: 10,
723
- borderRadius: 8,
724
- marginTop: 8,
725
- },
726
- monoText: {
727
- fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
728
- fontSize: 12,
729
- color: COLORS.text,
730
- },
731
- payloadText: {
732
- marginTop: 4,
733
- },
734
- loggerHeader: {
735
- flexDirection: 'row',
736
- justifyContent: 'space-between',
737
- alignItems: 'center',
738
- },
739
- loggerToggle: {
740
- flexDirection: 'row',
741
- alignItems: 'center',
742
- gap: 8,
743
- },
744
- logLine: {
745
- fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
746
- },
747
- codeBox: {
748
- backgroundColor: '#0b1527',
749
- borderRadius: 8,
750
- padding: 12,
751
- marginTop: 8,
752
- },
753
- });
754
-
755
- export default App;