@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
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "hawcx-react-native-sdk-example",
3
+ "version": "1.0.1",
4
+ "private": true,
5
+ "scripts": {
6
+ "start": "react-native start",
7
+ "ios": "react-native run-ios",
8
+ "android": "react-native run-android",
9
+ "lint": "eslint . --ext .js,.ts,.tsx"
10
+ },
11
+ "dependencies": {
12
+ "@hawcx/react-native-sdk": "file:..",
13
+ "react": "18.2.0",
14
+ "react-native": "0.73.9",
15
+ "react-native-safe-area-context": "4.10.5",
16
+ "react-native-svg": "15.1.0"
17
+ },
18
+ "devDependencies": {
19
+ "@react-native/eslint-config": "^0.74.0",
20
+ "@react-native/metro-config": "^0.82.1",
21
+ "@types/react": "^18.3.3",
22
+ "@types/react-native": "^0.73.0",
23
+ "babel-jest": "^29.7.0",
24
+ "eslint": "^8.57.0",
25
+ "jest": "^29.7.0",
26
+ "metro-react-native-babel-preset": "^0.76.8",
27
+ "react-test-renderer": "18.2.0",
28
+ "typescript": "^5.4.3"
29
+ }
30
+ }
@@ -0,0 +1,552 @@
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
+ handlePushNotification as forwardPushPayload,
23
+ approvePushRequest,
24
+ declinePushRequest,
25
+ type HawcxInitializeConfig,
26
+ type PushEvent,
27
+ type AuthEvent,
28
+ type SessionEvent,
29
+ } from '@hawcx/react-native-sdk';
30
+ import { DEFAULT_HAWCX_CONFIG } from './hawcx.config';
31
+
32
+ const COLORS = {
33
+ bg: '#0f172a',
34
+ card: '#1e293b',
35
+ accent: '#38bdf8',
36
+ text: '#f8fafc',
37
+ muted: '#94a3b8',
38
+ error: '#f87171',
39
+ success: '#4ade80',
40
+ };
41
+
42
+ 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.',
49
+ );
50
+ const [email, setEmail] = useState('user@example.com');
51
+ const [otp, setOtp] = useState('');
52
+ const [pin, setPin] = useState('');
53
+ const [token, setToken] = useState('');
54
+ const [pushTokenInput, setPushTokenInput] = useState('');
55
+ const [pushPayloadInput, setPushPayloadInput] = useState('{"request_id":"","ip_address":"","deviceInfo":"","timestamp":""}');
56
+ const [pushRequestId, setPushRequestId] = useState('');
57
+ const [pushEvents, setPushEvents] = useState<PushEvent[]>([]);
58
+ const [pushStatus, setPushStatus] = useState<string | null>(null);
59
+ const [pushError, setPushError] = useState<string | null>(null);
60
+ const [logs, setLogs] = useState<string[]>([]);
61
+ const [loggingEnabled, setLoggingEnabled] = useState(false);
62
+
63
+ const { state: authState, authenticate, submitOtp } = useHawcxAuth();
64
+ const web = useHawcxWebLogin();
65
+
66
+ const appendLog = useCallback(
67
+ (message: string) => {
68
+ if (!loggingEnabled) {
69
+ return;
70
+ }
71
+ const timestamp = new Date().toLocaleTimeString();
72
+ setLogs((prev) => [`[${timestamp}] ${message}`, ...prev].slice(0, 100));
73
+ },
74
+ [loggingEnabled],
75
+ );
76
+
77
+ useEffect(() => {
78
+ if (!activeConfig) {
79
+ return;
80
+ }
81
+ setInitStatus('initializing');
82
+ setInitError(null);
83
+ initialize(activeConfig)
84
+ .then(() => {
85
+ setInitStatus('ready');
86
+ appendLog('SDK initialized successfully');
87
+ })
88
+ .catch((err) => {
89
+ console.warn('Init failed', err);
90
+ setInitStatus('error');
91
+ setInitError(err?.message ?? 'Failed to initialize the Hawcx SDK');
92
+ appendLog(`SDK initialization failed: ${err?.message ?? 'unknown error'}`);
93
+ });
94
+ }, [activeConfig, appendLog]);
95
+
96
+ useEffect(() => {
97
+ const authSubscription = addAuthListener((event: AuthEvent) => {
98
+ appendLog(`auth event: ${event.type}`);
99
+ if (event.type === 'auth_error') {
100
+ appendLog(`auth error payload: ${event.payload.code} ${event.payload.message}`);
101
+ }
102
+ });
103
+ const sessionSubscription = addSessionListener((event: SessionEvent) => {
104
+ appendLog(`session event: ${event.type}`);
105
+ if (event.type === 'session_error') {
106
+ appendLog(`session error payload: ${event.payload.code} ${event.payload.message}`);
107
+ }
108
+ });
109
+ const pushSubscription = addPushListener((event) => {
110
+ setPushEvents((prev) => [event, ...prev].slice(0, 4));
111
+ setPushStatus(`Received push event: ${event.type}`);
112
+ appendLog(`push event: ${event.type}`);
113
+ });
114
+ return () => {
115
+ authSubscription.remove();
116
+ sessionSubscription.remove();
117
+ pushSubscription.remove();
118
+ };
119
+ }, [appendLog]);
120
+
121
+ const isReady = initStatus === 'ready';
122
+ const maskedKey = useMemo(() => {
123
+ if (!activeConfig?.projectApiKey) {
124
+ return '';
125
+ }
126
+ const suffix = activeConfig.projectApiKey.slice(-4);
127
+ return `Active key ••••${suffix}`;
128
+ }, [activeConfig?.projectApiKey]);
129
+ const oauthStatus = activeConfig?.oauthConfig ? 'Configured' : 'Not provided';
130
+
131
+ const requireReady = () => {
132
+ if (!isReady) {
133
+ setInitError('Initialize the SDK with your credentials in hawcx.config.ts before calling this action.');
134
+ return false;
135
+ }
136
+ return true;
137
+ };
138
+
139
+ const startAuth = () => {
140
+ if (!requireReady()) {
141
+ return;
142
+ }
143
+ appendLog(`trigger authenticate for ${email}`);
144
+ authenticate(email);
145
+ };
146
+
147
+ const submitOtpCode = () => {
148
+ if (!requireReady()) {
149
+ return;
150
+ }
151
+ appendLog('submit OTP');
152
+ submitOtp(otp);
153
+ setOtp('');
154
+ };
155
+
156
+ const registerPushToken = async () => {
157
+ if (!requireReady()) {
158
+ return;
159
+ }
160
+ const trimmed = pushTokenInput.trim();
161
+ if (!trimmed) {
162
+ setPushError('Enter a token first (FCM token for Android or byte list for iOS).');
163
+ return;
164
+ }
165
+ setPushError(null);
166
+ try {
167
+ if (Platform.OS === 'ios') {
168
+ const bytes = trimmed
169
+ .split(',')
170
+ .map((segment) => parseInt(segment.trim(), 10))
171
+ .filter((value) => !Number.isNaN(value));
172
+ if (!bytes.length) {
173
+ throw new Error('Provide a comma-separated list of APNs byte values for iOS.');
174
+ }
175
+ await setPushDeviceToken(bytes);
176
+ } else {
177
+ await setPushDeviceToken(trimmed);
178
+ }
179
+ appendLog('push token registered with native SDK');
180
+ setPushStatus('Push token submitted to the Hawcx SDK.');
181
+ } catch (error: unknown) {
182
+ setPushError((error as Error)?.message ?? 'Failed to register token');
183
+ }
184
+ };
185
+
186
+ const forwardPush = async () => {
187
+ if (!requireReady()) {
188
+ return;
189
+ }
190
+ setPushError(null);
191
+ try {
192
+ const parsed = JSON.parse(pushPayloadInput);
193
+ await forwardPushPayload(parsed);
194
+ appendLog('forwarded push payload to native SDK');
195
+ setPushStatus('Forwarded payload to the Hawcx SDK.');
196
+ } catch (error: unknown) {
197
+ setPushError((error as Error)?.message ?? 'Invalid JSON payload');
198
+ }
199
+ };
200
+
201
+ const onApprovePush = async () => {
202
+ try {
203
+ await approvePushRequest(pushRequestId.trim());
204
+ appendLog(`approved push request ${pushRequestId.trim()}`);
205
+ setPushStatus('Approved push request');
206
+ } catch (error: unknown) {
207
+ setPushError((error as Error)?.message ?? 'Failed to approve push request');
208
+ }
209
+ };
210
+
211
+ const onDeclinePush = async () => {
212
+ try {
213
+ await declinePushRequest(pushRequestId.trim());
214
+ appendLog(`declined push request ${pushRequestId.trim()}`);
215
+ setPushStatus('Declined push request');
216
+ } catch (error: unknown) {
217
+ setPushError((error as Error)?.message ?? 'Failed to decline push request');
218
+ }
219
+ };
220
+
221
+ const markUserAuthenticated = async () => {
222
+ try {
223
+ await notifyUserAuthenticated();
224
+ appendLog('notified native SDK that user authenticated');
225
+ setPushStatus('Notified native SDK to register push token.');
226
+ } catch (error: unknown) {
227
+ setPushError((error as Error)?.message ?? 'Failed to notify Hawcx SDK');
228
+ }
229
+ };
230
+
231
+ const ScrollContainer = RNScrollView ?? View;
232
+
233
+ return (
234
+ <SafeAreaView style={styles.container}>
235
+ <ScrollContainer
236
+ contentContainerStyle={ScrollContainer === View ? undefined : styles.scrollContent}
237
+ style={ScrollContainer === View ? styles.viewFallback : undefined}>
238
+ <Text style={styles.title}>Hawcx React Native SDK</Text>
239
+
240
+ <View style={styles.card}>
241
+ <Text style={styles.cardTitle}>SDK Status</Text>
242
+ <Text style={styles.status}>State: {initStatus === 'ready' ? 'Ready' : initStatus}</Text>
243
+ {!!maskedKey && <Text style={styles.status}>{maskedKey}</Text>}
244
+ <Text style={styles.status}>OAuth: {oauthStatus}</Text>
245
+ {!!initError && <Text style={[styles.status, styles.errorText]}>{initError}</Text>}
246
+ </View>
247
+
248
+ <View style={styles.card}>
249
+ <Text style={styles.cardTitle}>V5 Authentication</Text>
250
+ <TextInput
251
+ style={styles.input}
252
+ placeholder="Email"
253
+ placeholderTextColor={COLORS.muted}
254
+ value={email}
255
+ onChangeText={setEmail}
256
+ autoCapitalize="none"
257
+ />
258
+ <TouchableOpacity
259
+ style={[styles.button, !isReady && styles.buttonDisabled]}
260
+ onPress={startAuth}
261
+ disabled={!isReady}>
262
+ <Text style={styles.buttonText}>Authenticate</Text>
263
+ </TouchableOpacity>
264
+ {authState.status === 'otp' && (
265
+ <View style={styles.otpRow}>
266
+ <TextInput
267
+ style={[styles.input, styles.otpInput]}
268
+ placeholder="OTP"
269
+ placeholderTextColor={COLORS.muted}
270
+ value={otp}
271
+ onChangeText={setOtp}
272
+ keyboardType="number-pad"
273
+ />
274
+ <TouchableOpacity
275
+ style={[styles.button, styles.otpButton, !isReady && styles.buttonDisabled]}
276
+ onPress={submitOtpCode}
277
+ disabled={!isReady}>
278
+ <Text style={styles.buttonText}>Submit</Text>
279
+ </TouchableOpacity>
280
+ </View>
281
+ )}
282
+ <Text style={styles.status}>State: {authState.status}</Text>
283
+ {authState.status === 'error' && (
284
+ <Text style={[styles.status, styles.errorText]}>
285
+ {authState.error.code}: {authState.error.message}
286
+ </Text>
287
+ )}
288
+ {authState.status === 'success' && (
289
+ <Text style={[styles.status, styles.successText]}>Tokens received from Hawcx</Text>
290
+ )}
291
+ </View>
292
+
293
+ <View style={styles.card}>
294
+ <Text style={styles.cardTitle}>Web Login</Text>
295
+ <TextInput
296
+ style={styles.input}
297
+ placeholder="Web PIN"
298
+ placeholderTextColor={COLORS.muted}
299
+ value={pin}
300
+ onChangeText={setPin}
301
+ />
302
+ <TouchableOpacity
303
+ style={[styles.button, !isReady && styles.buttonDisabled]}
304
+ disabled={!isReady}
305
+ onPress={() => {
306
+ if (requireReady()) {
307
+ web.webLogin(pin);
308
+ }
309
+ }}>
310
+ <Text style={styles.buttonText}>Validate PIN</Text>
311
+ </TouchableOpacity>
312
+ <TextInput
313
+ style={styles.input}
314
+ placeholder="Web Token"
315
+ placeholderTextColor={COLORS.muted}
316
+ value={token}
317
+ onChangeText={setToken}
318
+ />
319
+ <TouchableOpacity
320
+ style={[styles.button, !isReady && styles.buttonDisabled]}
321
+ disabled={!isReady}
322
+ onPress={() => {
323
+ if (requireReady()) {
324
+ web.webApprove(token);
325
+ }
326
+ }}>
327
+ <Text style={styles.buttonText}>Approve Token</Text>
328
+ </TouchableOpacity>
329
+ <Text style={styles.status}>Web State: {web.state.status}</Text>
330
+ {web.state.status === 'error' && (
331
+ <Text style={[styles.status, styles.errorText]}>
332
+ {web.state.error.code}: {web.state.error.message}
333
+ </Text>
334
+ )}
335
+ </View>
336
+
337
+ <View style={styles.card}>
338
+ <Text style={styles.cardTitle}>Push Approvals (Manual Harness)</Text>
339
+ <Text style={styles.status}>
340
+ Token format: Android expects the FCM string. iOS expects a comma-separated list of APNs byte values.
341
+ </Text>
342
+ <TextInput
343
+ style={[styles.input, styles.payloadInput]}
344
+ placeholder={Platform.OS === 'ios' ? 'e.g. 42, 13, 255' : 'FCM token'}
345
+ placeholderTextColor={COLORS.muted}
346
+ value={pushTokenInput}
347
+ onChangeText={setPushTokenInput}
348
+ multiline
349
+ />
350
+ <View style={styles.row}>
351
+ <TouchableOpacity style={[styles.button, styles.rowButton]} onPress={registerPushToken} disabled={!isReady}>
352
+ <Text style={styles.buttonText}>Register Token</Text>
353
+ </TouchableOpacity>
354
+ <TouchableOpacity style={[styles.button, styles.rowButton]} onPress={markUserAuthenticated} disabled={!isReady}>
355
+ <Text style={styles.buttonText}>Notify Authenticated</Text>
356
+ </TouchableOpacity>
357
+ </View>
358
+
359
+ <TextInput
360
+ style={[styles.input, styles.payloadInput]}
361
+ placeholder='Raw push payload JSON (e.g. {"request_id": "..."} )'
362
+ placeholderTextColor={COLORS.muted}
363
+ value={pushPayloadInput}
364
+ onChangeText={setPushPayloadInput}
365
+ multiline
366
+ />
367
+ <TouchableOpacity style={[styles.button, !isReady && styles.buttonDisabled]} onPress={forwardPush} disabled={!isReady}>
368
+ <Text style={styles.buttonText}>Send Payload to SDK</Text>
369
+ </TouchableOpacity>
370
+
371
+ <TextInput
372
+ style={styles.input}
373
+ placeholder="Request ID for approve/decline"
374
+ placeholderTextColor={COLORS.muted}
375
+ value={pushRequestId}
376
+ onChangeText={setPushRequestId}
377
+ />
378
+ <View style={styles.row}>
379
+ <TouchableOpacity
380
+ style={[styles.button, styles.successButton, (!isReady || !pushRequestId.trim()) && styles.buttonDisabled]}
381
+ onPress={onApprovePush}
382
+ disabled={!isReady || !pushRequestId.trim()}>
383
+ <Text style={styles.buttonText}>Approve</Text>
384
+ </TouchableOpacity>
385
+ <TouchableOpacity
386
+ style={[styles.button, styles.errorButton, (!isReady || !pushRequestId.trim()) && styles.buttonDisabled]}
387
+ onPress={onDeclinePush}
388
+ disabled={!isReady || !pushRequestId.trim()}>
389
+ <Text style={styles.buttonText}>Decline</Text>
390
+ </TouchableOpacity>
391
+ </View>
392
+
393
+ {!!pushStatus && <Text style={[styles.status, styles.successText]}>{pushStatus}</Text>}
394
+ {!!pushError && <Text style={[styles.status, styles.errorText]}>{pushError}</Text>}
395
+
396
+ <View>
397
+ <Text style={styles.status}>Recent Push Events</Text>
398
+ {pushEvents.length === 0 && <Text style={styles.status}>Waiting for events…</Text>}
399
+ {pushEvents.map((event, index) => (
400
+ <View key={`${event.type}-${index}`} style={styles.pushEvent}>
401
+ <Text style={styles.monoText}>{event.type}</Text>
402
+ {'payload' in event && event.payload && (
403
+ <Text style={[styles.monoText, styles.payloadText]}>{JSON.stringify(event.payload, null, 2)}</Text>
404
+ )}
405
+ </View>
406
+ ))}
407
+ </View>
408
+ </View>
409
+
410
+ <View style={styles.card}>
411
+ <View style={styles.loggerHeader}>
412
+ <Text style={styles.cardTitle}>Logging</Text>
413
+ <View style={styles.loggerToggle}>
414
+ <Text style={styles.status}>{loggingEnabled ? 'On' : 'Off'}</Text>
415
+ <Switch value={loggingEnabled} onValueChange={setLoggingEnabled} />
416
+ </View>
417
+ </View>
418
+ <Text style={styles.status}>
419
+ Enable logging to see SDK events, errors, and push actions below. Logs are kept in-memory and
420
+ capped to the most recent 10 entries.
421
+ </Text>
422
+ {logs.length === 0 ? (
423
+ <Text style={styles.status}>No logs yet.</Text>
424
+ ) : (
425
+ logs.slice(0, 10).map((log) => (
426
+ <Text key={`${log}`} style={[styles.status, styles.logLine]}>
427
+ {log}
428
+ </Text>
429
+ ))
430
+ )}
431
+ </View>
432
+ </ScrollContainer>
433
+ </SafeAreaView>
434
+ );
435
+ };
436
+
437
+ const styles = StyleSheet.create({
438
+ container: {
439
+ flex: 1,
440
+ backgroundColor: COLORS.bg,
441
+ },
442
+ scrollContent: {
443
+ padding: 16,
444
+ gap: 16,
445
+ paddingBottom: 32,
446
+ },
447
+ viewFallback: {
448
+ padding: 16,
449
+ gap: 16,
450
+ },
451
+ title: {
452
+ color: COLORS.text,
453
+ fontSize: 22,
454
+ fontWeight: '600',
455
+ },
456
+ card: {
457
+ backgroundColor: COLORS.card,
458
+ padding: 16,
459
+ borderRadius: 12,
460
+ gap: 12,
461
+ },
462
+ cardTitle: {
463
+ color: COLORS.text,
464
+ fontSize: 18,
465
+ fontWeight: '500',
466
+ },
467
+ input: {
468
+ backgroundColor: '#0b1220',
469
+ borderRadius: 8,
470
+ paddingHorizontal: 12,
471
+ paddingVertical: 10,
472
+ color: COLORS.text,
473
+ },
474
+ button: {
475
+ backgroundColor: COLORS.accent,
476
+ paddingVertical: 12,
477
+ borderRadius: 8,
478
+ alignItems: 'center',
479
+ },
480
+ buttonDisabled: {
481
+ opacity: 0.5,
482
+ },
483
+ buttonText: {
484
+ color: '#0f172a',
485
+ fontWeight: '600',
486
+ },
487
+ row: {
488
+ flexDirection: 'row',
489
+ gap: 12,
490
+ },
491
+ rowButton: {
492
+ flex: 1,
493
+ },
494
+ payloadInput: {
495
+ minHeight: 70,
496
+ textAlignVertical: 'top',
497
+ },
498
+ status: {
499
+ color: COLORS.muted,
500
+ },
501
+ errorText: {
502
+ color: COLORS.error,
503
+ },
504
+ successText: {
505
+ color: COLORS.success,
506
+ },
507
+ successButton: {
508
+ backgroundColor: COLORS.success,
509
+ },
510
+ errorButton: {
511
+ backgroundColor: COLORS.error,
512
+ },
513
+ otpRow: {
514
+ flexDirection: 'row',
515
+ gap: 8,
516
+ },
517
+ otpInput: {
518
+ flex: 1,
519
+ },
520
+ otpButton: {
521
+ flex: 0.6,
522
+ },
523
+ pushEvent: {
524
+ backgroundColor: '#0b1527',
525
+ padding: 10,
526
+ borderRadius: 8,
527
+ marginTop: 8,
528
+ },
529
+ monoText: {
530
+ fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
531
+ fontSize: 12,
532
+ color: COLORS.text,
533
+ },
534
+ payloadText: {
535
+ marginTop: 4,
536
+ },
537
+ loggerHeader: {
538
+ flexDirection: 'row',
539
+ justifyContent: 'space-between',
540
+ alignItems: 'center',
541
+ },
542
+ loggerToggle: {
543
+ flexDirection: 'row',
544
+ alignItems: 'center',
545
+ gap: 8,
546
+ },
547
+ logLine: {
548
+ fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
549
+ },
550
+ });
551
+
552
+ export default App;
@@ -0,0 +1,41 @@
1
+ import type { HawcxInitializeConfig } from '@hawcx/react-native-sdk';
2
+
3
+ /**
4
+ * Set these constants if you prefer to hard-code dev credentials instead of using
5
+ * the in-app configuration inputs.
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-----`;
16
+
17
+ const buildDefaultConfig = (): HawcxInitializeConfig | null => {
18
+ const trimmedKey = HAWCX_PROJECT_API_KEY.trim();
19
+ if (!trimmedKey) {
20
+ return null;
21
+ }
22
+ const baseConfig: HawcxInitializeConfig = {
23
+ projectApiKey: trimmedKey,
24
+ };
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
+ };
40
+
41
+ export const DEFAULT_HAWCX_CONFIG = buildDefaultConfig();
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "jsx": "react-native",
5
+ "outDir": "build"
6
+ },
7
+ "include": ["src", "index.js"]
8
+ }
File without changes
@@ -0,0 +1,44 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>AvailableLibraries</key>
6
+ <array>
7
+ <dict>
8
+ <key>BinaryPath</key>
9
+ <string>HawcxFramework.framework/HawcxFramework</string>
10
+ <key>LibraryIdentifier</key>
11
+ <string>ios-arm64_x86_64-simulator</string>
12
+ <key>LibraryPath</key>
13
+ <string>HawcxFramework.framework</string>
14
+ <key>SupportedArchitectures</key>
15
+ <array>
16
+ <string>arm64</string>
17
+ <string>x86_64</string>
18
+ </array>
19
+ <key>SupportedPlatform</key>
20
+ <string>ios</string>
21
+ <key>SupportedPlatformVariant</key>
22
+ <string>simulator</string>
23
+ </dict>
24
+ <dict>
25
+ <key>BinaryPath</key>
26
+ <string>HawcxFramework.framework/HawcxFramework</string>
27
+ <key>LibraryIdentifier</key>
28
+ <string>ios-arm64</string>
29
+ <key>LibraryPath</key>
30
+ <string>HawcxFramework.framework</string>
31
+ <key>SupportedArchitectures</key>
32
+ <array>
33
+ <string>arm64</string>
34
+ </array>
35
+ <key>SupportedPlatform</key>
36
+ <string>ios</string>
37
+ </dict>
38
+ </array>
39
+ <key>CFBundlePackageType</key>
40
+ <string>XFWK</string>
41
+ <key>XCFrameworkFormatVersion</key>
42
+ <string>1.0</string>
43
+ </dict>
44
+ </plist>
@@ -0,0 +1,16 @@
1
+ //
2
+ // HawcxFramework.h
3
+ // HawcxFramework
4
+ //
5
+ // Created by dev on 10/15/24.
6
+ //
7
+
8
+ #import <Foundation/Foundation.h>
9
+
10
+ //! Project version number for HawcxFramework.
11
+ FOUNDATION_EXPORT double HawcxFrameworkVersionNumber;
12
+
13
+ //! Project version string for HawcxFramework.
14
+ FOUNDATION_EXPORT const unsigned char HawcxFrameworkVersionString[];
15
+
16
+ // In this header, you should import all the public headers of your framework using statements like #import <HawcxFramework/PublicHeader.h>