@passgage/sdk-react-native 1.0.3 → 1.0.5

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
@@ -1,27 +1,38 @@
1
- # @passgage-sdk/react-native
1
+ # @passgage/sdk-react-native
2
2
 
3
- React Native wrapper for Passgage Access SDK with ready-to-use components and hooks.
3
+ Complete Passgage Access SDK for React Native with authentication, QR/NFC scanning, check-in, and remote work features.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @passgage-sdk/react-native @passgage-sdk/core
8
+ npm install @passgage/sdk-react-native
9
9
  # or
10
- yarn add @passgage-sdk/react-native @passgage-sdk/core
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:
16
25
 
17
26
  ```bash
18
- npm install react-native-vision-camera react-native-nfc-manager @react-native-community/geolocation
27
+ npm install react-native-vision-camera react-native-nfc-manager @react-native-community/geolocation react-native-keychain
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)
35
+ - [react-native-keychain](https://github.com/oblador/react-native-keychain)
25
36
 
26
37
  ## Usage
27
38
 
@@ -30,127 +41,510 @@ Follow the installation instructions for each native library:
30
41
  Wrap your app with `PassgageAccessProvider`:
31
42
 
32
43
  ```typescript
33
- import { PassgageAccessProvider } from '@passgage-sdk/react-native';
44
+ import {PassgageAccessProvider} from '@passgage/sdk-react-native';
34
45
 
35
46
  function App() {
36
47
  return (
37
48
  <PassgageAccessProvider
38
49
  baseURL="https://your-api.passgage.com"
39
- token="your-jwt-token"
40
- >
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 => {
55
+ // Handle session expiration
56
+ console.log('Unauthorized error:', error);
57
+ }}
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
+ }}>
41
66
  <YourApp />
42
67
  </PassgageAccessProvider>
43
68
  );
44
69
  }
45
70
  ```
46
71
 
47
- ### QR Scanner Hook
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
+
85
+ ### Authentication
48
86
 
49
87
  ```typescript
50
- import { useQRScanner } 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
+ };
51
98
 
52
- function QRScanScreen() {
53
- const { scan, isLoading, error } = useQRScanner({
54
- onSuccess: (entrance) => {
55
- console.log('QR scan successful:', entrance);
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);
56
132
  },
57
- onError: (error) => {
58
- console.error('QR scan failed:', error);
133
+ onError: error => {
134
+ const errorMessage = error.message || 'QR validation failed';
135
+ Alert.alert('Failed', errorMessage);
59
136
  },
60
137
  });
61
138
 
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('');
155
+ };
156
+
62
157
  return (
63
- <View>
64
- {/* Your camera view */}
65
- <Camera onCodeScanned={(code) => scan(code)} />
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>
212
+ </View>
213
+ );
214
+ };
215
+ ```
216
+
217
+ ### NFC Scanner
218
+
219
+ ```typescript
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
+ });
237
+
238
+ const handleStartScan = async () => {
239
+ startScanning();
240
+ };
241
+
242
+ const handleStopScan = async () => {
243
+ try {
244
+ await stopScanning();
245
+ } catch (error: any) {
246
+ console.error('Stop NFC scan error:', error);
247
+ }
248
+ };
249
+ return (
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>
66
316
  </View>
67
317
  );
68
318
  }
69
319
  ```
70
320
 
71
- ### NFC Scanner Hook
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.
72
330
 
73
331
  ```typescript
74
- import { useNFCScanner } 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)`
350
+
351
+ NFC card scanning with validation and location checking.
75
352
 
76
- function NFCScanScreen() {
77
- const { startScanning, stopScanning, isScanning } = useNFCScanner({
353
+ ```typescript
354
+ const {nfcData, supportNFC, startScanning, stopScanning, isScanning, error} =
355
+ usePassgageNFCScanner({
356
+ options: {
357
+ skipLocationCheck: false,
358
+ skipRepetitiveCheck: false,
359
+ },
78
360
  onSuccess: (entrance) => {
79
- console.log('NFC scan successful:', entrance);
361
+ console.log('Access granted:', entrance);
80
362
  },
81
363
  onError: (error) => {
82
- console.error('NFC scan failed:', error);
364
+ console.error('Access denied:', error);
83
365
  },
84
366
  });
85
367
 
86
- useEffect(() => {
87
- startScanning();
88
- return () => stopScanning();
89
- }, []);
368
+ // Start NFC scanning
369
+ await startScanning();
90
370
 
91
- return <View>{/* Your NFC scan UI */}</View>;
92
- }
371
+ // Stop NFC scanning
372
+ await stopScanning();
93
373
  ```
94
374
 
95
- ### Check-In Hook
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.
96
386
 
97
387
  ```typescript
98
- import { useCheckIn } from '@passgage-sdk/react-native';
388
+ const {location, error, refreshLocation} = useLocation();
99
389
 
100
- function CheckInScreen() {
101
- const { nearbyBranches, checkIn, isLoading } = useCheckIn();
390
+ // Get current location
391
+ console.log(location?.latitude, location?.longitude);
102
392
 
103
- const handleCheckIn = async (branchId: string) => {
104
- await checkIn({
105
- branchId,
106
- entranceType: 0, // 0 = ENTRY, 1 = EXIT
107
- });
108
- };
393
+ // Manually refresh location
394
+ await refreshLocation();
395
+ ```
109
396
 
110
- return (
111
- <View>
112
- {nearbyBranches.map((branch) => (
113
- <TouchableOpacity key={branch.id} onPress={() => handleCheckIn(branch.id)}>
114
- <Text>{branch.title}</Text>
115
- </TouchableOpacity>
116
- ))}
117
- </View>
118
- );
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.
407
+
408
+ ```typescript
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);
119
430
  }
431
+
432
+ // Logout
433
+ await logout();
434
+
435
+ // Restore previous session
436
+ await restoreAuth();
437
+
438
+ // Manually refresh token
439
+ await refreshToken();
440
+
441
+ // Clear error state
442
+ clearError();
120
443
  ```
121
444
 
122
- ### Remote Work Hook
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
450
+
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`
123
464
 
124
465
  ```typescript
125
- import { useRemoteWork } from '@passgage-sdk/react-native';
466
+ import {AuthService} from '@passgage/sdk-react-native';
126
467
 
127
- function RemoteWorkScreen() {
128
- const { logEntry, logExit, isLoading } = useRemoteWork();
468
+ const authService = new AuthService(apiClient);
469
+ await authService.loginWithAzure({id_token: 'token'});
470
+ await authService.logout();
471
+ ```
129
472
 
130
- return (
131
- <View>
132
- <Button title="Log Entry" onPress={() => logEntry()} />
133
- <Button title="Log Exit" onPress={() => logExit()} />
134
- </View>
135
- );
136
- }
473
+ #### `QRAccessService`
474
+
475
+ ```typescript
476
+ import {QRAccessService} from '@passgage/sdk-react-native';
477
+
478
+ const qrService = new QRAccessService(apiClient);
479
+ const entrance = await qrService.validateQRCode(qrCode, options);
137
480
  ```
138
481
 
139
- ## API
482
+ #### `NFCAccessService`
140
483
 
141
- ### Hooks
484
+ ```typescript
485
+ import {NFCAccessService} from '@passgage/sdk-react-native';
486
+
487
+ const nfcService = new NFCAccessService(apiClient);
488
+ const entrance = await nfcService.validateNFCCard(cardId, options);
489
+ ```
490
+
491
+ #### `DeviceAccessService`
142
492
 
143
- - `useQRScanner()` - QR code scanning with validation
144
- - `useNFCScanner()` - NFC card scanning with validation
145
- - `useCheckIn()` - GPS-based check-in
146
- - `useRemoteWork()` - Remote work logging
147
- - `useLocation()` - Location tracking
148
- - `usePassgageAccess()` - Access SDK context
493
+ ```typescript
494
+ import {DeviceAccessService} from '@passgage/sdk-react-native';
495
+
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
+ ```
149
509
 
150
510
  ### Components
151
511
 
152
512
  - `<PassgageAccessProvider>` - SDK configuration provider
153
513
 
514
+ ## Security Features
515
+
516
+ - **JWT Authentication**: Secure token-based authentication
517
+ - **Automatic Token Refresh**: Tokens are automatically refreshed when expired
518
+ - **Secure Storage**: Tokens stored securely using platform-specific secure storage (Keychain/Keystore)
519
+ - **TLS 1.2+**: All network communications encrypted
520
+ - **Location Verification**: Automatic location validation with range checking
521
+ - **Backend Logging**: Access logs sent to backend for audit trail
522
+
523
+ ## Token Flow
524
+
525
+ The SDK automatically handles authentication tokens:
526
+
527
+ 1. User logs in → JWT tokens stored securely
528
+ 2. API client automatically adds `Authorization: Bearer {token}` to all requests
529
+ 3. Tokens are automatically refreshed when they expire
530
+ 4. On logout, all tokens are cleared
531
+
532
+ **You don't need to manually manage tokens!** Just provide msal tokens to provider and the SDK handles everything.
533
+
534
+ ## Example Application
535
+
536
+ A complete example React Native app demonstrating all SDK features:
537
+
538
+ 👉 [Passgage SDK Example App](https://github.com/passgage/passgage-sdk-example-react-native)
539
+
540
+ ## TypeScript Support
541
+
542
+ The SDK is written in TypeScript and includes full type definitions for all APIs, hooks, and models.
543
+
154
544
  ## License
155
545
 
156
546
  Proprietary - Passgage © 2025
547
+
548
+ ## Support
549
+
550
+ For support, contact devops@passgage.com