@passgage/sdk-react-native 1.0.4 → 1.0.6

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.
package/README.md CHANGED
@@ -10,6 +10,15 @@ npm install @passgage/sdk-react-native
10
10
  yarn add @passgage/sdk-react-native
11
11
  ```
12
12
 
13
+ ## Dependencies
14
+
15
+ The package has the following dependencies:
16
+
17
+ - **axios**: ^1.6.0 - HTTP client for API requests
18
+ - **react-native-geolocation-service**: ^5.3.1 - Geolocation service
19
+ - **react-native-permissions**: ^5.4.4 - Permissions management
20
+ - **zustand**: ^5.0.9 - State management
21
+
13
22
  ### Peer Dependencies
14
23
 
15
24
  You also need to install these peer dependencies:
@@ -19,9 +28,10 @@ npm install react-native-vision-camera react-native-nfc-manager @react-native-co
19
28
  ```
20
29
 
21
30
  Follow the installation instructions for each native library:
31
+
22
32
  - [react-native-vision-camera](https://react-native-vision-camera.com/docs/guides)
23
33
  - [react-native-nfc-manager](https://github.com/revtel/react-native-nfc-manager)
24
- - [@react-native-community/geolocation](https://github.com/react-native-geolocation/react-native-geolocation)
34
+ - [react-native-geolocation-service](https://github.com/Agontuk/react-native-geolocation-service)
25
35
  - [react-native-keychain](https://github.com/oblador/react-native-keychain)
26
36
 
27
37
  ## Usage
@@ -31,232 +41,484 @@ Follow the installation instructions for each native library:
31
41
  Wrap your app with `PassgageAccessProvider`:
32
42
 
33
43
  ```typescript
34
- import { PassgageAccessProvider } from '@passgage/sdk-react-native';
44
+ import {PassgageAccessProvider} from '@passgage/sdk-react-native';
35
45
 
36
46
  function App() {
37
47
  return (
38
48
  <PassgageAccessProvider
39
49
  baseURL="https://your-api.passgage.com"
40
- onUnauthorized={() => {
50
+ msalToken={'Your MSAL Token'}
51
+ apiVersion="v2" // Optional: API version (default: "v2")
52
+ timeout={30000} // Optional: Request timeout in ms (default: 30000)
53
+ rememberUser={true} // Optional: Persist authentication (default: true)
54
+ onUnauthorized={error => {
41
55
  // Handle session expiration
42
- console.log('Session expired');
56
+ console.log('Unauthorized error:', error);
43
57
  }}
44
- >
58
+ getLocationErrorCallback={error => {
59
+ // Handle location retrieval errors
60
+ console.log('Location error:', error);
61
+ }}
62
+ locationPermissionErrorCallback={error => {
63
+ // Handle location permission errors
64
+ console.log('Permission error:', error);
65
+ }}>
45
66
  <YourApp />
46
67
  </PassgageAccessProvider>
47
68
  );
48
69
  }
49
70
  ```
50
71
 
72
+ #### Provider Props
73
+
74
+ | Prop | Type | Required | Default | Description |
75
+ |------|------|----------|---------|-------------|
76
+ | `baseURL` | `string` | ✓ | - | Base URL of your Passgage API |
77
+ | `msalToken` | `string` | ✓ | - | Microsoft Azure AD token |
78
+ | `apiVersion` | `string` | ✗ | `"v2"` | API version to use |
79
+ | `timeout` | `number` | ✗ | `30000` | Request timeout in milliseconds |
80
+ | `rememberUser` | `boolean` | ✗ | `true` | Persist authentication state |
81
+ | `onUnauthorized` | `(error: Error) => void` | ✗ | - | Called when session expires |
82
+ | `getLocationErrorCallback` | `(error: any) => void` | ✗ | - | Called on location retrieval errors |
83
+ | `locationPermissionErrorCallback` | `(error: any) => void` | ✗ | - | Called on location permission errors |
84
+
51
85
  ### Authentication
52
86
 
53
87
  ```typescript
54
- import { usePassgageAuth } from '@passgage/sdk-react-native';
88
+ const LoginScreen = () => {
89
+ const {loading, error, loginWithAzure} = useAuthStore();
90
+
91
+ const handleLogin = async idToken => {
92
+ if (result?.idToken) {
93
+ loginWithAzure({id_token: result.idToken});
94
+ } else {
95
+ Alert.alert('Login Failed', 'No ID token received');
96
+ }
97
+ };
55
98
 
56
- function LoginScreen() {
57
- const { login, logout, user, isLoading, isAuthenticated } = usePassgageAuth({
58
- onLoginSuccess: (user) => {
59
- console.log('Logged in:', user?.fullName);
99
+ return (
100
+ <View style={styles.formContainer}>
101
+ {error && (
102
+ <View style={styles.errorContainer}>
103
+ <Text style={styles.errorText}>{error}</Text>
104
+ </View>
105
+ )}
106
+ <TouchableOpacity
107
+ style={[styles.button, loading && styles.buttonDisabled]}
108
+ onPress={() => handleLogin('Provide your msal token')}
109
+ disabled={loading}>
110
+ {loading ? (
111
+ <ActivityIndicator color="#fff" />
112
+ ) : (
113
+ <Text style={styles.buttonText}>Sign In</Text>
114
+ )}
115
+ </TouchableOpacity>
116
+ </View>
117
+ );
118
+ };
119
+ ```
120
+
121
+ ### QR Scanner
122
+
123
+ ```typescript
124
+ const QRScannerScreen = () => {
125
+ const [qrCode, setQrCode] = useState('c70fc3a2-fbfb-4ca6-adcb-71ac0b796836');
126
+
127
+ const {scan, isLoading} = usePassgageQRScanner({
128
+ options: {skipLocationCheck: false, skipRepetitiveCheck: false},
129
+ onSuccess: entrance => {
130
+ const message = `Access granted!\nEntrance ID: ${entrance?.id || 'N/A'}`;
131
+ Alert.alert('Success', message);
60
132
  },
61
- onLoginError: (error) => {
62
- console.error('Login failed:', error);
133
+ onError: error => {
134
+ const errorMessage = error.message || 'QR validation failed';
135
+ Alert.alert('Failed', errorMessage);
63
136
  },
64
137
  });
65
138
 
66
- const handleLogin = async () => {
67
- await login({
68
- login: 'user@example.com',
69
- password: 'password123',
70
- });
139
+ const handleScan = async () => {
140
+ if (!qrCode.trim()) {
141
+ Alert.alert('Error', 'Please enter a QR code');
142
+ return;
143
+ }
144
+
145
+ try {
146
+ await scan(qrCode.trim());
147
+ } catch (error: any) {
148
+ // Error already handled by onError callback
149
+ console.error('QR scan error:', error);
150
+ }
151
+ };
152
+
153
+ const handleClear = () => {
154
+ setQrCode('');
71
155
  };
72
156
 
73
157
  return (
74
- <View>
75
- {isAuthenticated ? (
76
- <Text>Welcome, {user?.fullName}!</Text>
77
- ) : (
78
- <Button title="Login" onPress={handleLogin} disabled={isLoading} />
79
- )}
158
+ <View style={styles.container}>
159
+ <View style={styles.content}>
160
+ <Text style={styles.icon}>📱</Text>
161
+ <Text style={styles.title}>QR Code Scanner</Text>
162
+ <Text style={styles.description}>
163
+ Enter a QR code to validate access
164
+ </Text>
165
+
166
+ <View style={styles.inputContainer}>
167
+ <TextInput
168
+ style={styles.input}
169
+ placeholder="Enter QR code"
170
+ value={qrCode}
171
+ onChangeText={setQrCode}
172
+ autoCapitalize="none"
173
+ editable={!isLoading}
174
+ returnKeyType="done"
175
+ onSubmitEditing={handleScan}
176
+ />
177
+ </View>
178
+
179
+ <View style={styles.buttonContainer}>
180
+ <TouchableOpacity
181
+ style={[
182
+ styles.button,
183
+ styles.scanButton,
184
+ isLoading && styles.buttonDisabled,
185
+ ]}
186
+ onPress={handleScan}
187
+ disabled={isLoading || !qrCode.trim()}>
188
+ {isLoading ? (
189
+ <ActivityIndicator color="#fff" />
190
+ ) : (
191
+ <Text style={styles.buttonText}>Validate QR Code</Text>
192
+ )}
193
+ </TouchableOpacity>
194
+ {qrCode.trim() && !isLoading && (
195
+ <TouchableOpacity
196
+ style={[styles.button, styles.clearButton]}
197
+ onPress={handleClear}>
198
+ <Text style={styles.clearButtonText}>Clear</Text>
199
+ </TouchableOpacity>
200
+ )}
201
+ </View>
202
+ <View style={styles.info}>
203
+ <Text style={styles.infoText}>
204
+ 💡 In a real app, you would use react-native-vision-camera to scan
205
+ QR codes with the camera
206
+ </Text>
207
+ <Text style={styles.infoText}>
208
+ {'\n'}For testing, you can enter any QR code value manually
209
+ </Text>
210
+ </View>
211
+ </View>
80
212
  </View>
81
213
  );
82
- }
214
+ };
83
215
  ```
84
216
 
85
- ### QR Scanner
217
+ ### NFC Scanner
86
218
 
87
219
  ```typescript
88
- import { usePassgageQRScanner } from '@passgage/sdk-react-native';
220
+ export default function NFCScannerScreen() {
221
+ NfcManager.start();
222
+
223
+ const {nfcData, supportNFC, startScanning, stopScanning, isScanning, error} =
224
+ usePassgageNFCScanner({
225
+ options: {skipLocationCheck: false, skipRepetitiveCheck: false},
226
+ onSuccess: entrance => {
227
+ const message = `Access granted!\nEntrance ID: ${
228
+ entrance?.id || 'N/A'
229
+ }`;
230
+ Alert.alert('Success', message);
231
+ },
232
+ onError: error => {
233
+ const errorMessage = error.message || 'NFC validation failed';
234
+ Alert.alert('Failed', errorMessage);
235
+ },
236
+ });
89
237
 
90
- function QRScanScreen() {
91
- const { scan, isLoading, error } = usePassgageQRScanner({
92
- onSuccess: (entrance) => {
93
- console.log('QR scan successful:', entrance);
94
- },
95
- onError: (error) => {
96
- console.error('QR scan failed:', error);
97
- },
98
- });
238
+ const handleStartScan = async () => {
239
+ startScanning();
240
+ };
99
241
 
242
+ const handleStopScan = async () => {
243
+ try {
244
+ await stopScanning();
245
+ } catch (error: any) {
246
+ console.error('Stop NFC scan error:', error);
247
+ }
248
+ };
100
249
  return (
101
- <View>
102
- <Camera
103
- onCodeScanned={(code) => scan(code)}
104
- isActive={!isLoading}
105
- />
250
+ <View style={styles.container}>
251
+ <View style={styles.content}>
252
+ <Text style={styles.icon}>💳</Text>
253
+ <Text style={styles.title}>NFC Card Scanner</Text>
254
+ <Text style={styles.description}>
255
+ {isScanning
256
+ ? 'Hold your device near an NFC card'
257
+ : 'Tap the button below to start scanning'}
258
+ </Text>
259
+
260
+ <View style={styles.scanStatus}>
261
+ {isScanning ? (
262
+ <>
263
+ <ActivityIndicator size="large" color="#FF9500" />
264
+ <Text style={styles.statusText}>Scanning for NFC cards...</Text>
265
+ </>
266
+ ) : (
267
+ <View style={styles.readyIndicator}>
268
+ <Text style={styles.readyText}>
269
+ {supportNFC
270
+ ? 'Ready to scan'
271
+ : supportNFC === false
272
+ ? 'Device not support NFC'
273
+ : 'LOADING...'}
274
+ </Text>
275
+ </View>
276
+ )}
277
+ </View>
278
+
279
+ <View style={styles.buttonContainer}>
280
+ {!isScanning ? (
281
+ <TouchableOpacity
282
+ style={[styles.button, styles.scanButton]}
283
+ onPress={async () => {
284
+ handleStartScan();
285
+ }}>
286
+ <Text style={styles.buttonText}>Start NFC Scan</Text>
287
+ </TouchableOpacity>
288
+ ) : (
289
+ <TouchableOpacity
290
+ style={[styles.button, styles.stopButton]}
291
+ onPress={handleStopScan}>
292
+ <Text style={styles.buttonText}>Stop Scanning</Text>
293
+ </TouchableOpacity>
294
+ )}
295
+ </View>
296
+
297
+ {error && (
298
+ <View style={styles.errorContainer}>
299
+ <Text style={styles.errorText}>{error.message}</Text>
300
+ </View>
301
+ )}
302
+
303
+ <View style={styles.info}>
304
+ <Text style={styles.infoTitle}>How it works:</Text>
305
+ <Text style={styles.infoText}>
306
+ 1. Tap "Start NFC Scan" button{'\n'}
307
+ 2. Hold your device near an NFC card{'\n'}
308
+ 3. Wait for validation response{'\n'}
309
+ 4. Access will be granted if authorized
310
+ </Text>
311
+ <Text style={styles.infoNote}>
312
+ {'\n'}💡 Make sure NFC is enabled in your device settings
313
+ </Text>
314
+ </View>
315
+ </View>
106
316
  </View>
107
317
  );
108
318
  }
109
319
  ```
110
320
 
111
- ### NFC Scanner
321
+ ## API
322
+
323
+ ### Hooks
324
+
325
+ All hooks use the `usePassgage*` prefix to avoid naming conflicts:
326
+
327
+ #### `usePassgageQRScanner(options)`
328
+
329
+ QR code scanning with validation and location checking.
112
330
 
113
331
  ```typescript
114
- import { usePassgageNFCScanner } from '@passgage/sdk-react-native';
332
+ const {scan, isLoading} = usePassgageQRScanner({
333
+ options: {
334
+ skipLocationCheck: false, // Skip location verification
335
+ skipRepetitiveCheck: false, // Skip duplicate scan detection
336
+ },
337
+ onSuccess: (entrance) => {
338
+ console.log('Access granted:', entrance);
339
+ },
340
+ onError: (error) => {
341
+ console.error('Access denied:', error);
342
+ },
343
+ });
344
+
345
+ // Scan a QR code
346
+ await scan(qrCode);
347
+ ```
348
+
349
+ #### `usePassgageNFCScanner(options)`
115
350
 
116
- function NFCScanScreen() {
117
- const { startScanning, stopScanning, isScanning } = usePassgageNFCScanner({
351
+ NFC card scanning with validation and location checking.
352
+
353
+ ```typescript
354
+ const {nfcData, supportNFC, startScanning, stopScanning, isScanning, error} =
355
+ usePassgageNFCScanner({
356
+ options: {
357
+ skipLocationCheck: false,
358
+ skipRepetitiveCheck: false,
359
+ },
118
360
  onSuccess: (entrance) => {
119
- console.log('NFC scan successful:', entrance);
361
+ console.log('Access granted:', entrance);
120
362
  },
121
363
  onError: (error) => {
122
- console.error('NFC scan failed:', error);
364
+ console.error('Access denied:', error);
123
365
  },
124
366
  });
125
367
 
126
- useEffect(() => {
127
- startScanning();
128
- return () => stopScanning();
129
- }, []);
368
+ // Start NFC scanning
369
+ await startScanning();
130
370
 
131
- return (
132
- <View>
133
- <Text>{isScanning ? 'Scanning...' : 'Tap to scan'}</Text>
134
- </View>
135
- );
136
- }
371
+ // Stop NFC scanning
372
+ await stopScanning();
137
373
  ```
138
374
 
139
- ### Check-In
375
+ **Returns:**
376
+ - `nfcData?: string` - Last scanned NFC card data
377
+ - `supportNFC?: boolean` - Whether device supports NFC
378
+ - `startScanning: () => Promise<void>` - Start NFC scanning
379
+ - `stopScanning: () => Promise<void>` - Stop NFC scanning
380
+ - `isScanning: boolean` - Whether currently scanning
381
+ - `error: Error | null` - Last error if any
382
+
383
+ #### `useLocation()`
384
+
385
+ Access current user location and location utilities.
140
386
 
141
387
  ```typescript
142
- import { usePassgageCheckIn, usePassgageAuth } from '@passgage/sdk-react-native';
143
-
144
- function CheckInScreen() {
145
- const { user } = usePassgageAuth();
146
- const { getNearbyBranches, checkInEntry, checkInExit, isLoading } = usePassgageCheckIn();
147
- const [branches, setBranches] = useState([]);
148
-
149
- useEffect(() => {
150
- const loadBranches = async () => {
151
- const result = await getNearbyBranches({ radius: 5000 });
152
- if (result.success) {
153
- setBranches(result.data);
154
- }
155
- };
156
- loadBranches();
157
- }, []);
158
-
159
- const handleCheckIn = async (branchId: string) => {
160
- const result = await checkInEntry({
161
- branchId,
162
- userId: user.id,
163
- });
388
+ const {location, error, refreshLocation} = useLocation();
164
389
 
165
- if (result.success) {
166
- console.log('Checked in successfully');
167
- }
168
- };
390
+ // Get current location
391
+ console.log(location?.latitude, location?.longitude);
169
392
 
170
- return (
171
- <View>
172
- {branches.map((branch) => (
173
- <TouchableOpacity key={branch.id} onPress={() => handleCheckIn(branch.id)}>
174
- <Text>{branch.title}</Text>
175
- </TouchableOpacity>
176
- ))}
177
- </View>
178
- );
179
- }
393
+ // Manually refresh location
394
+ await refreshLocation();
180
395
  ```
181
396
 
182
- ### Remote Work
397
+ **Returns:**
398
+ - `location?: Location` - Current location coordinates
399
+ - `error?: string` - Location error if any
400
+ - `refreshLocation: () => Promise<void>` - Manually refresh location
401
+
402
+ ### State Management
403
+
404
+ #### `useAuthStore()`
405
+
406
+ Zustand store for authentication state management.
183
407
 
184
408
  ```typescript
185
- import { usePassgageRemoteWork, usePassgageAuth } from '@passgage/sdk-react-native';
409
+ import {useAuthStore} from '@passgage/sdk-react-native';
410
+
411
+ const {
412
+ user,
413
+ loading,
414
+ error,
415
+ authStatus,
416
+ login,
417
+ loginWithAzure,
418
+ logout,
419
+ restoreAuth,
420
+ refreshToken,
421
+ clearError,
422
+ } = useAuthStore();
423
+
424
+ // Login with Azure AD
425
+ await loginWithAzure({id_token: 'your-azure-token'});
426
+
427
+ // Check auth status
428
+ if (authStatus === 'authenticated') {
429
+ console.log('User:', user);
430
+ }
186
431
 
187
- function RemoteWorkScreen() {
188
- const { user } = usePassgageAuth();
189
- const { logEntry, logExit, isLoading } = usePassgageRemoteWork();
432
+ // Logout
433
+ await logout();
190
434
 
191
- const handleStartWork = async () => {
192
- const result = await logEntry({
193
- userId: user.id,
194
- description: 'Working from home',
195
- });
435
+ // Restore previous session
436
+ await restoreAuth();
196
437
 
197
- if (result.success) {
198
- console.log('Work entry logged');
199
- }
200
- };
438
+ // Manually refresh token
439
+ await refreshToken();
201
440
 
202
- const handleEndWork = async () => {
203
- const result = await logExit({
204
- userId: user.id,
205
- description: 'Finished work',
206
- });
441
+ // Clear error state
442
+ clearError();
443
+ ```
207
444
 
208
- if (result.success) {
209
- console.log('Work exit logged');
210
- }
211
- };
445
+ **State:**
446
+ - `user?: User` - Current authenticated user
447
+ - `loading: boolean` - Loading state
448
+ - `error: string | null` - Error message if any
449
+ - `authStatus: 'idle' | 'loading' | 'authenticated' | 'unauthenticated'` - Authentication status
212
450
 
213
- return (
214
- <View>
215
- <Button title="Start Work" onPress={handleStartWork} disabled={isLoading} />
216
- <Button title="End Work" onPress={handleEndWork} disabled={isLoading} />
217
- </View>
218
- );
219
- }
451
+ **Methods:**
452
+ - `login(credentials)` - Login with email/password
453
+ - `loginWithAzure(params)` - Login with Azure AD token
454
+ - `logout()` - Logout and clear session
455
+ - `restoreAuth()` - Restore session from secure storage
456
+ - `refreshToken()` - Manually refresh access token
457
+ - `clearError()` - Clear error state
458
+
459
+ ### Services
460
+
461
+ Low-level services for advanced use cases. Most developers should use hooks instead.
462
+
463
+ #### `AuthService`
464
+
465
+ ```typescript
466
+ import {AuthService} from '@passgage/sdk-react-native';
467
+
468
+ const authService = new AuthService(apiClient);
469
+ await authService.loginWithAzure({id_token: 'token'});
470
+ await authService.logout();
220
471
  ```
221
472
 
222
- ## API
473
+ #### `QRAccessService`
223
474
 
224
- ### Hooks
475
+ ```typescript
476
+ import {QRAccessService} from '@passgage/sdk-react-native';
225
477
 
226
- All hooks use the `usePassgage*` prefix to avoid naming conflicts:
478
+ const qrService = new QRAccessService(apiClient);
479
+ const entrance = await qrService.validateQRCode(qrCode, options);
480
+ ```
227
481
 
228
- - `usePassgageAuth()` - Authentication and user management
229
- - `usePassgageQRScanner()` - QR code scanning with validation
230
- - `usePassgageNFCScanner()` - NFC card scanning with validation
231
- - `usePassgageCheckIn()` - GPS-based check-in with nearby branches
232
- - `usePassgageRemoteWork()` - Remote work entry/exit logging
233
- - `useLocation()` - Location tracking and permissions
234
- - `usePassgageAccess()` - Access SDK context and services
482
+ #### `NFCAccessService`
235
483
 
236
- ### Components
484
+ ```typescript
485
+ import {NFCAccessService} from '@passgage/sdk-react-native';
237
486
 
238
- - `<PassgageAccessProvider>` - SDK configuration provider
487
+ const nfcService = new NFCAccessService(apiClient);
488
+ const entrance = await nfcService.validateNFCCard(cardId, options);
489
+ ```
239
490
 
240
- ### Services
491
+ #### `DeviceAccessService`
241
492
 
242
- Low-level services are also available through `usePassgageAccess()`:
493
+ ```typescript
494
+ import {DeviceAccessService} from '@passgage/sdk-react-native';
243
495
 
244
- - `authService` - Authentication operations
245
- - `qrAccessService` - QR code validation
246
- - `nfcAccessService` - NFC card validation
247
- - `checkInService` - Check-in operations
248
- - `remoteWorkService` - Remote work logging
249
- - `locationService` - Location management
250
- - `deviceAccessService` - Device operations
496
+ const deviceService = new DeviceAccessService(apiClient);
497
+ const device = await deviceService.getDeviceInfo(deviceId);
498
+ ```
499
+
500
+ #### `LocationService`
501
+
502
+ ```typescript
503
+ import {LocationService} from '@passgage/sdk-react-native';
504
+
505
+ const locationService = new LocationService();
506
+ const location = await locationService.getCurrentLocation();
507
+ const isInRange = locationService.isWithinRange(location, targetLocation, range);
508
+ ```
509
+
510
+ ### Components
511
+
512
+ - `<PassgageAccessProvider>` - SDK configuration provider
251
513
 
252
514
  ## Security Features
253
515
 
254
516
  - **JWT Authentication**: Secure token-based authentication
255
517
  - **Automatic Token Refresh**: Tokens are automatically refreshed when expired
256
- - **Secure Storage**: Tokens stored securely using platform-specific secure storage
518
+ - **Secure Storage**: Tokens stored securely using platform-specific secure storage (Keychain/Keystore)
257
519
  - **TLS 1.2+**: All network communications encrypted
258
520
  - **Location Verification**: Automatic location validation with range checking
259
- - **Anti-Passback**: Support for anti-passback rules
521
+ - **Backend Logging**: Access logs sent to backend for audit trail
260
522
 
261
523
  ## Token Flow
262
524
 
@@ -267,7 +529,7 @@ The SDK automatically handles authentication tokens:
267
529
  3. Tokens are automatically refreshed when they expire
268
530
  4. On logout, all tokens are cleared
269
531
 
270
- **You don't need to manually manage tokens!** Just use the hooks and the SDK handles everything.
532
+ **You don't need to manually manage tokens!** Just provide msal tokens to provider and the SDK handles everything.
271
533
 
272
534
  ## Example Application
273
535