@hawcx/react-native-sdk 1.0.1 → 1.0.2

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 (39) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/HawcxReactNative.podspec +2 -2
  3. package/LICENSE +48 -15
  4. package/README.md +150 -88
  5. package/docs/RELEASE.md +2 -2
  6. package/example/ios/HawcxExampleApp.xcodeproj/project.pbxproj +57 -57
  7. package/example/ios/HawcxExampleApp.xcodeproj/project.xcworkspace/xcuserdata/agambhullar.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  8. package/example/ios/Podfile +24 -0
  9. package/example/ios/Podfile.lock +3 -3
  10. package/example/package-lock.json +3 -3
  11. package/example/package.json +1 -1
  12. package/example/src/App.tsx +211 -8
  13. package/example/src/hawcx.config.ts +4 -26
  14. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/HawcxFramework +0 -0
  15. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.abi.json +208 -77
  16. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.private.swiftinterface +10 -0
  17. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  18. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.swiftinterface +10 -0
  19. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/HawcxFramework +0 -0
  20. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.abi.json +208 -77
  21. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +10 -0
  22. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  23. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.swiftinterface +10 -0
  24. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.abi.json +208 -77
  25. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +10 -0
  26. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  27. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +10 -0
  28. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/_CodeSignature/CodeResources +20 -20
  29. package/ios/HawcxReactNative.swift +64 -0
  30. package/lib/commonjs/index.js +34 -0
  31. package/lib/commonjs/index.js.map +1 -1
  32. package/lib/module/index.js +33 -0
  33. package/lib/module/index.js.map +1 -1
  34. package/lib/typescript/index.d.ts +28 -0
  35. package/lib/typescript/index.d.ts.map +1 -1
  36. package/package.json +1 -1
  37. package/react_mobile_sdk_plan.md +2 -0
  38. package/src/__tests__/index.test.ts +40 -1
  39. package/src/index.ts +66 -0
@@ -19,6 +19,7 @@ import {
19
19
  addPushListener,
20
20
  setPushDeviceToken,
21
21
  notifyUserAuthenticated,
22
+ storeBackendOAuthTokens,
22
23
  handlePushNotification as forwardPushPayload,
23
24
  approvePushRequest,
24
25
  declinePushRequest,
@@ -26,6 +27,8 @@ import {
26
27
  type PushEvent,
27
28
  type AuthEvent,
28
29
  type SessionEvent,
30
+ type AuthorizationCodePayload,
31
+ type AdditionalVerificationPayload,
29
32
  } from '@hawcx/react-native-sdk';
30
33
  import { DEFAULT_HAWCX_CONFIG } from './hawcx.config';
31
34
 
@@ -39,14 +42,23 @@ const COLORS = {
39
42
  success: '#4ade80',
40
43
  };
41
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
+
42
56
  const App = () => {
43
- const activeConfig: HawcxInitializeConfig | null = DEFAULT_HAWCX_CONFIG;
44
- const [initStatus, setInitStatus] = useState<'idle' | 'initializing' | 'ready' | 'error'>(
45
- DEFAULT_HAWCX_CONFIG ? 'initializing' : 'error',
46
- );
47
- const [initError, setInitError] = useState<string | null>(
48
- DEFAULT_HAWCX_CONFIG ? null : 'Set credentials in example/src/hawcx.config.ts to continue.',
57
+ const [activeConfig, setActiveConfig] = useState<HawcxInitializeConfig | null>(
58
+ DEFAULT_HAWCX_CONFIG,
49
59
  );
60
+ const [initStatus, setInitStatus] = useState<'idle' | 'initializing' | 'ready' | 'error'>('idle');
61
+ const [initError, setInitError] = useState<string | null>(null);
50
62
  const [email, setEmail] = useState('user@example.com');
51
63
  const [otp, setOtp] = useState('');
52
64
  const [pin, setPin] = useState('');
@@ -59,6 +71,12 @@ const App = () => {
59
71
  const [pushError, setPushError] = useState<string | null>(null);
60
72
  const [logs, setLogs] = useState<string[]>([]);
61
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);
62
80
 
63
81
  const { state: authState, authenticate, submitOtp } = useHawcxAuth();
64
82
  const web = useHawcxWebLogin();
@@ -74,8 +92,87 @@ const App = () => {
74
92
  [loggingEnabled],
75
93
  );
76
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
+
77
170
  useEffect(() => {
78
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
+ );
79
176
  return;
80
177
  }
81
178
  setInitStatus('initializing');
@@ -118,6 +215,50 @@ const App = () => {
118
215
  };
119
216
  }, [appendLog]);
120
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
+ }, []);
121
262
  const isReady = initStatus === 'ready';
122
263
  const maskedKey = useMemo(() => {
123
264
  if (!activeConfig?.projectApiKey) {
@@ -126,7 +267,6 @@ const App = () => {
126
267
  const suffix = activeConfig.projectApiKey.slice(-4);
127
268
  return `Active key ••••${suffix}`;
128
269
  }, [activeConfig?.projectApiKey]);
129
- const oauthStatus = activeConfig?.oauthConfig ? 'Configured' : 'Not provided';
130
270
 
131
271
  const requireReady = () => {
132
272
  if (!isReady) {
@@ -241,7 +381,6 @@ const App = () => {
241
381
  <Text style={styles.cardTitle}>SDK Status</Text>
242
382
  <Text style={styles.status}>State: {initStatus === 'ready' ? 'Ready' : initStatus}</Text>
243
383
  {!!maskedKey && <Text style={styles.status}>{maskedKey}</Text>}
244
- <Text style={styles.status}>OAuth: {oauthStatus}</Text>
245
384
  {!!initError && <Text style={[styles.status, styles.errorText]}>{initError}</Text>}
246
385
  </View>
247
386
 
@@ -290,6 +429,56 @@ const App = () => {
290
429
  )}
291
430
  </View>
292
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
+
293
482
  <View style={styles.card}>
294
483
  <Text style={styles.cardTitle}>Web Login</Text>
295
484
  <TextInput
@@ -488,6 +677,11 @@ const styles = StyleSheet.create({
488
677
  flexDirection: 'row',
489
678
  gap: 12,
490
679
  },
680
+ switchRow: {
681
+ flexDirection: 'row',
682
+ alignItems: 'center',
683
+ justifyContent: 'space-between',
684
+ },
491
685
  rowButton: {
492
686
  flex: 1,
493
687
  },
@@ -510,6 +704,9 @@ const styles = StyleSheet.create({
510
704
  errorButton: {
511
705
  backgroundColor: COLORS.error,
512
706
  },
707
+ secondaryButton: {
708
+ backgroundColor: '#475569',
709
+ },
513
710
  otpRow: {
514
711
  flexDirection: 'row',
515
712
  gap: 8,
@@ -547,6 +744,12 @@ const styles = StyleSheet.create({
547
744
  logLine: {
548
745
  fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
549
746
  },
747
+ codeBox: {
748
+ backgroundColor: '#0b1527',
749
+ borderRadius: 8,
750
+ padding: 12,
751
+ marginTop: 8,
752
+ },
550
753
  });
551
754
 
552
755
  export default App;
@@ -1,41 +1,19 @@
1
1
  import type { HawcxInitializeConfig } from '@hawcx/react-native-sdk';
2
2
 
3
3
  /**
4
- * Set these constants if you prefer to hard-code dev credentials instead of using
5
- * the in-app configuration inputs.
4
+ * Populate the API key locally for testing or use the in-app form.
5
+ * Leaving it blank ensures we never ship real credentials in git history.
6
6
  */
7
- export const HAWCX_PROJECT_API_KEY = 'YE3a8gqIPPiiRw3dotlMo3mksiamUy1V';
8
- export const HAWCX_OAUTH_CLIENT_ID = 'parenthive-demo.hawcx.com';
9
- export const HAWCX_OAUTH_TOKEN_ENDPOINT = 'https://parenthive-api.hawcx.com/oauth2/token';
10
- export const HAWCX_OAUTH_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
11
- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBFPGnOqFYdmJr6nYpJyYuArjf6LKg
12
- pr0XRbu6X9515vaX3oMW0MZCikmZ9Rph+GgvELzf2EgHX6PcdHPEnuYB5n8BrdUf
13
- Yu2TDW4QfK9r2QgNbi34h87SLzPaHXPomwEAmxk2SQrH0gSYiJRna7pPeDj7aStX
14
- v/xVOP7RclIR29l+SwU=
15
- -----END PUBLIC KEY-----`;
7
+ export const HAWCX_PROJECT_API_KEY = 'YOUR_API_KEY';
16
8
 
17
9
  const buildDefaultConfig = (): HawcxInitializeConfig | null => {
18
10
  const trimmedKey = HAWCX_PROJECT_API_KEY.trim();
19
11
  if (!trimmedKey) {
20
12
  return null;
21
13
  }
22
- const baseConfig: HawcxInitializeConfig = {
14
+ return {
23
15
  projectApiKey: trimmedKey,
24
16
  };
25
-
26
- const trimmedClientId = HAWCX_OAUTH_CLIENT_ID.trim();
27
- const trimmedEndpoint = HAWCX_OAUTH_TOKEN_ENDPOINT.trim();
28
- const trimmedPem = HAWCX_OAUTH_PUBLIC_KEY_PEM.trim();
29
-
30
- if (trimmedClientId && trimmedEndpoint && trimmedPem) {
31
- baseConfig.oauthConfig = {
32
- clientId: trimmedClientId,
33
- tokenEndpoint: trimmedEndpoint,
34
- publicKeyPem: trimmedPem,
35
- };
36
- }
37
-
38
- return baseConfig;
39
17
  };
40
18
 
41
19
  export const DEFAULT_HAWCX_CONFIG = buildDefaultConfig();