@passgage/sdk-react-native 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.
package/dist/index.mjs ADDED
@@ -0,0 +1,685 @@
1
+ // src/providers/PassgageAccessProvider.tsx
2
+ import React, { createContext, useContext, useMemo } from "react";
3
+ import {
4
+ createApiClient,
5
+ AuthService,
6
+ QRAccessService,
7
+ NFCAccessService,
8
+ CheckInService,
9
+ RemoteWorkService,
10
+ DeviceAccessService,
11
+ LocationService
12
+ } from "@passgage/sdk-core";
13
+
14
+ // src/utils/secureStorage.ts
15
+ import * as Keychain from "react-native-keychain";
16
+ var STORAGE_KEYS = {
17
+ TOKENS: "passgage_auth_tokens",
18
+ USER: "passgage_user_info"
19
+ };
20
+ var SecureStorage = class {
21
+ /**
22
+ * Save tokens to secure storage
23
+ */
24
+ async saveTokens(tokens) {
25
+ try {
26
+ await Keychain.setGenericPassword(
27
+ STORAGE_KEYS.TOKENS,
28
+ JSON.stringify(tokens),
29
+ {
30
+ service: STORAGE_KEYS.TOKENS,
31
+ accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED
32
+ }
33
+ );
34
+ } catch (error) {
35
+ console.error("Failed to save tokens to secure storage:", error);
36
+ throw new Error("Failed to save tokens");
37
+ }
38
+ }
39
+ /**
40
+ * Get tokens from secure storage
41
+ */
42
+ async getTokens() {
43
+ try {
44
+ const credentials = await Keychain.getGenericPassword({
45
+ service: STORAGE_KEYS.TOKENS
46
+ });
47
+ if (!credentials) {
48
+ return null;
49
+ }
50
+ const tokens = JSON.parse(credentials.password);
51
+ return tokens;
52
+ } catch (error) {
53
+ console.error("Failed to get tokens from secure storage:", error);
54
+ return null;
55
+ }
56
+ }
57
+ /**
58
+ * Clear tokens from secure storage
59
+ */
60
+ async clearTokens() {
61
+ try {
62
+ await Keychain.resetGenericPassword({
63
+ service: STORAGE_KEYS.TOKENS
64
+ });
65
+ } catch (error) {
66
+ console.error("Failed to clear tokens from secure storage:", error);
67
+ }
68
+ }
69
+ /**
70
+ * Save user information
71
+ */
72
+ async saveUser(user) {
73
+ try {
74
+ await Keychain.setGenericPassword(
75
+ STORAGE_KEYS.USER,
76
+ JSON.stringify(user),
77
+ {
78
+ service: STORAGE_KEYS.USER,
79
+ accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED
80
+ }
81
+ );
82
+ } catch (error) {
83
+ console.error("Failed to save user to secure storage:", error);
84
+ throw new Error("Failed to save user");
85
+ }
86
+ }
87
+ /**
88
+ * Get user information
89
+ */
90
+ async getUser() {
91
+ try {
92
+ const credentials = await Keychain.getGenericPassword({
93
+ service: STORAGE_KEYS.USER
94
+ });
95
+ if (!credentials) {
96
+ return null;
97
+ }
98
+ const user = JSON.parse(credentials.password);
99
+ return user;
100
+ } catch (error) {
101
+ console.error("Failed to get user from secure storage:", error);
102
+ return null;
103
+ }
104
+ }
105
+ /**
106
+ * Clear user information
107
+ */
108
+ async clearUser() {
109
+ try {
110
+ await Keychain.resetGenericPassword({
111
+ service: STORAGE_KEYS.USER
112
+ });
113
+ } catch (error) {
114
+ console.error("Failed to clear user from secure storage:", error);
115
+ }
116
+ }
117
+ /**
118
+ * Clear all data (tokens + user)
119
+ */
120
+ async clearAll() {
121
+ await this.clearTokens();
122
+ await this.clearUser();
123
+ }
124
+ };
125
+ function createSecureStorage() {
126
+ return new SecureStorage();
127
+ }
128
+
129
+ // src/providers/PassgageAccessProvider.tsx
130
+ var PassgageAccessContext = createContext(void 0);
131
+ function PassgageAccessProvider({
132
+ children,
133
+ baseURL,
134
+ token,
135
+ apiVersion = "v2",
136
+ timeout = 3e4,
137
+ onUnauthorized,
138
+ onError
139
+ }) {
140
+ const config = {
141
+ baseURL,
142
+ token,
143
+ apiVersion,
144
+ timeout,
145
+ onUnauthorized,
146
+ onError
147
+ };
148
+ const { apiClient, services } = useMemo(() => {
149
+ const secureStorage = createSecureStorage();
150
+ let authService;
151
+ const client = createApiClient({
152
+ baseURL: config.baseURL,
153
+ token: config.token,
154
+ apiVersion: config.apiVersion,
155
+ timeout: config.timeout,
156
+ onUnauthorized: config.onUnauthorized,
157
+ onError: config.onError,
158
+ onTokenRefreshNeeded: async () => {
159
+ const storedTokens = await authService.getStoredTokens();
160
+ if (!storedTokens) {
161
+ return null;
162
+ }
163
+ const result = await authService.refreshToken(storedTokens.refresh.token);
164
+ if (result.success) {
165
+ return result.tokens.access.token;
166
+ }
167
+ return null;
168
+ }
169
+ });
170
+ authService = new AuthService(client);
171
+ authService.setTokenStorage(secureStorage);
172
+ const allServices = {
173
+ authService,
174
+ qrAccessService: new QRAccessService(client),
175
+ nfcAccessService: new NFCAccessService(client),
176
+ checkInService: new CheckInService(client),
177
+ remoteWorkService: new RemoteWorkService(client),
178
+ deviceAccessService: new DeviceAccessService(client),
179
+ locationService: new LocationService(client)
180
+ };
181
+ return {
182
+ apiClient: client,
183
+ services: allServices
184
+ };
185
+ }, [config.baseURL, config.token, config.apiVersion, config.timeout, config.onUnauthorized, config.onError]);
186
+ const setToken = (newToken) => {
187
+ apiClient.setToken(newToken);
188
+ };
189
+ const clearToken = () => {
190
+ apiClient.clearToken();
191
+ };
192
+ const contextValue = {
193
+ apiClient,
194
+ ...services,
195
+ config,
196
+ setToken,
197
+ clearToken
198
+ };
199
+ return /* @__PURE__ */ React.createElement(PassgageAccessContext.Provider, { value: contextValue }, children);
200
+ }
201
+ function usePassgageAccess() {
202
+ const context = useContext(PassgageAccessContext);
203
+ if (!context) {
204
+ throw new Error(
205
+ "usePassgageAccess must be used within a PassgageAccessProvider"
206
+ );
207
+ }
208
+ return context;
209
+ }
210
+
211
+ // src/hooks/usePassgageAuth.ts
212
+ import { useState, useEffect, useCallback } from "react";
213
+ function usePassgageAuth(options = {}) {
214
+ const {
215
+ onLoginSuccess,
216
+ onLoginError,
217
+ onLogoutSuccess,
218
+ autoRestore = true
219
+ } = options;
220
+ const { authService } = usePassgageAccess();
221
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
222
+ const [user, setUser] = useState(null);
223
+ const [isLoading, setIsLoading] = useState(false);
224
+ const [error, setError] = useState(null);
225
+ useEffect(() => {
226
+ if (!autoRestore) {
227
+ return;
228
+ }
229
+ const restoreAuth = async () => {
230
+ try {
231
+ setIsLoading(true);
232
+ const authenticated = await authService.isAuthenticated();
233
+ if (authenticated) {
234
+ const storedTokens = await authService.getStoredTokens();
235
+ const storedUser = await authService.getStoredUser();
236
+ if (storedTokens && storedUser) {
237
+ authService["apiClient"].setToken(storedTokens.access.token);
238
+ setIsAuthenticated(true);
239
+ setUser(storedUser);
240
+ try {
241
+ const userResult = await authService.getCurrentUser();
242
+ if (userResult.success) {
243
+ setUser(userResult.user);
244
+ }
245
+ } catch (error2) {
246
+ console.warn("Failed to fetch fresh user info:", error2);
247
+ }
248
+ }
249
+ }
250
+ } catch (error2) {
251
+ console.error("Failed to restore authentication:", error2);
252
+ setError(error2.message || "Failed to restore authentication");
253
+ } finally {
254
+ setIsLoading(false);
255
+ }
256
+ };
257
+ restoreAuth();
258
+ }, [authService, autoRestore]);
259
+ const login = useCallback(
260
+ async (credentials) => {
261
+ try {
262
+ setIsLoading(true);
263
+ setError(null);
264
+ const result = await authService.login(credentials);
265
+ if (result.success) {
266
+ setIsAuthenticated(true);
267
+ setUser(result.user || null);
268
+ if (onLoginSuccess) {
269
+ onLoginSuccess(result.user);
270
+ }
271
+ } else {
272
+ setError(result.error);
273
+ if (onLoginError) {
274
+ onLoginError(result.error);
275
+ }
276
+ }
277
+ return result;
278
+ } catch (error2) {
279
+ const errorMessage = error2.message || "An error occurred during login";
280
+ setError(errorMessage);
281
+ if (onLoginError) {
282
+ onLoginError(errorMessage);
283
+ }
284
+ return {
285
+ success: false,
286
+ error: errorMessage,
287
+ code: "UNKNOWN_ERROR"
288
+ };
289
+ } finally {
290
+ setIsLoading(false);
291
+ }
292
+ },
293
+ [authService, onLoginSuccess, onLoginError]
294
+ );
295
+ const logout = useCallback(async () => {
296
+ try {
297
+ setIsLoading(true);
298
+ setError(null);
299
+ await authService.logout();
300
+ setIsAuthenticated(false);
301
+ setUser(null);
302
+ if (onLogoutSuccess) {
303
+ onLogoutSuccess();
304
+ }
305
+ } catch (error2) {
306
+ const errorMessage = error2.message || "An error occurred during logout";
307
+ setError(errorMessage);
308
+ console.error("Logout failed:", error2);
309
+ } finally {
310
+ setIsLoading(false);
311
+ }
312
+ }, [authService, onLogoutSuccess]);
313
+ const refreshToken = useCallback(async () => {
314
+ try {
315
+ const storedTokens = await authService.getStoredTokens();
316
+ if (!storedTokens) {
317
+ return false;
318
+ }
319
+ const result = await authService.refreshToken(storedTokens.refresh.token);
320
+ if (result.success) {
321
+ return true;
322
+ } else {
323
+ await logout();
324
+ return false;
325
+ }
326
+ } catch (error2) {
327
+ console.error("Token refresh failed:", error2);
328
+ await logout();
329
+ return false;
330
+ }
331
+ }, [authService, logout]);
332
+ const clearError = useCallback(() => {
333
+ setError(null);
334
+ }, []);
335
+ return {
336
+ login,
337
+ logout,
338
+ refreshToken,
339
+ isAuthenticated,
340
+ user,
341
+ isLoading,
342
+ error,
343
+ clearError
344
+ };
345
+ }
346
+
347
+ // src/hooks/useQRScanner.ts
348
+ import { useState as useState3, useCallback as useCallback3 } from "react";
349
+
350
+ // src/hooks/useLocation.ts
351
+ import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
352
+ import Geolocation from "@react-native-community/geolocation";
353
+ function useLocation(options = {}) {
354
+ const [location, setLocation] = useState2(null);
355
+ const [error, setError] = useState2(null);
356
+ const [isLoading, setIsLoading] = useState2(true);
357
+ const config = {
358
+ enableHighAccuracy: options.enableHighAccuracy ?? true,
359
+ timeout: options.timeout ?? 15e3,
360
+ maximumAge: options.maximumAge ?? 1e4
361
+ };
362
+ const refreshLocation = useCallback2(async () => {
363
+ setIsLoading(true);
364
+ setError(null);
365
+ return new Promise((resolve) => {
366
+ Geolocation.getCurrentPosition(
367
+ (position) => {
368
+ setLocation({
369
+ latitude: position.coords.latitude,
370
+ longitude: position.coords.longitude,
371
+ accuracy: position.coords.accuracy,
372
+ altitude: position.coords.altitude ?? void 0,
373
+ altitudeAccuracy: position.coords.altitudeAccuracy ?? void 0,
374
+ heading: position.coords.heading ?? void 0,
375
+ speed: position.coords.speed ?? void 0
376
+ });
377
+ setIsLoading(false);
378
+ resolve();
379
+ },
380
+ (err) => {
381
+ setError(new Error(err.message));
382
+ setIsLoading(false);
383
+ resolve();
384
+ },
385
+ config
386
+ );
387
+ });
388
+ }, [config.enableHighAccuracy, config.timeout, config.maximumAge]);
389
+ useEffect2(() => {
390
+ refreshLocation();
391
+ if (options.watchPosition) {
392
+ const watchId = Geolocation.watchPosition(
393
+ (position) => {
394
+ setLocation({
395
+ latitude: position.coords.latitude,
396
+ longitude: position.coords.longitude,
397
+ accuracy: position.coords.accuracy,
398
+ altitude: position.coords.altitude ?? void 0,
399
+ altitudeAccuracy: position.coords.altitudeAccuracy ?? void 0,
400
+ heading: position.coords.heading ?? void 0,
401
+ speed: position.coords.speed ?? void 0
402
+ });
403
+ },
404
+ (err) => {
405
+ setError(new Error(err.message));
406
+ },
407
+ config
408
+ );
409
+ return () => {
410
+ Geolocation.clearWatch(watchId);
411
+ };
412
+ }
413
+ return void 0;
414
+ }, [options.watchPosition, refreshLocation]);
415
+ return {
416
+ location,
417
+ error,
418
+ isLoading,
419
+ refreshLocation
420
+ };
421
+ }
422
+
423
+ // src/hooks/useQRScanner.ts
424
+ function useQRScanner(options = {}) {
425
+ const { qrAccessService, deviceAccessService } = usePassgageAccess();
426
+ const { location } = useLocation();
427
+ const [isLoading, setIsLoading] = useState3(false);
428
+ const [error, setError] = useState3(null);
429
+ const scan = useCallback3(
430
+ async (qrCode, device) => {
431
+ setIsLoading(true);
432
+ setError(null);
433
+ try {
434
+ let qrDevice = device;
435
+ if (!qrDevice) {
436
+ qrDevice = await deviceAccessService.findDeviceByQRCode(qrCode);
437
+ if (!qrDevice) {
438
+ throw new Error("QR device not found");
439
+ }
440
+ }
441
+ const result = await qrAccessService.validateQR({
442
+ qrCode,
443
+ device: qrDevice,
444
+ userLocation: location || void 0,
445
+ skipLocationCheck: options.skipLocationCheck,
446
+ skipRepetitiveCheck: options.skipRepetitiveCheck
447
+ });
448
+ if (!result.success) {
449
+ throw new Error(result.message);
450
+ }
451
+ options.onSuccess?.(result.entrance);
452
+ } catch (err) {
453
+ const error2 = err;
454
+ setError(error2);
455
+ options.onError?.(error2);
456
+ } finally {
457
+ setIsLoading(false);
458
+ }
459
+ },
460
+ [qrAccessService, deviceAccessService, location, options]
461
+ );
462
+ return {
463
+ scan,
464
+ isLoading,
465
+ error
466
+ };
467
+ }
468
+
469
+ // src/hooks/useNFCScanner.ts
470
+ import { useState as useState4, useCallback as useCallback4, useEffect as useEffect3 } from "react";
471
+ import NfcManager, { NfcTech } from "react-native-nfc-manager";
472
+ function reversedHexToDec(hexString) {
473
+ const hex = hexString.replace(/:/g, "");
474
+ return parseInt(hex, 16).toString();
475
+ }
476
+ function useNFCScanner(options = {}) {
477
+ const { nfcAccessService, deviceAccessService } = usePassgageAccess();
478
+ const { location } = useLocation();
479
+ const [isScanning, setIsScanning] = useState4(false);
480
+ const [error, setError] = useState4(null);
481
+ const stopScanning = useCallback4(async () => {
482
+ try {
483
+ await NfcManager.cancelTechnologyRequest();
484
+ setIsScanning(false);
485
+ } catch {
486
+ }
487
+ }, []);
488
+ const handleNFCTag = useCallback4(
489
+ async (tag) => {
490
+ if (!tag.id) {
491
+ return;
492
+ }
493
+ try {
494
+ const nfcCode = reversedHexToDec(tag.id);
495
+ const device = await deviceAccessService.findDeviceByNFCCode(nfcCode);
496
+ if (!device) {
497
+ throw new Error("NFC device not found");
498
+ }
499
+ const result = await nfcAccessService.validateNFC({
500
+ nfcCode,
501
+ device,
502
+ userLocation: location || void 0,
503
+ skipLocationCheck: options.skipLocationCheck,
504
+ skipRepetitiveCheck: options.skipRepetitiveCheck
505
+ });
506
+ if (!result.success) {
507
+ throw new Error(result.message);
508
+ }
509
+ options.onSuccess?.(result.entrance);
510
+ await stopScanning();
511
+ } catch (err) {
512
+ const error2 = err;
513
+ setError(error2);
514
+ options.onError?.(error2);
515
+ await stopScanning();
516
+ }
517
+ },
518
+ [nfcAccessService, deviceAccessService, location, options, stopScanning]
519
+ );
520
+ const startScanning = useCallback4(async () => {
521
+ setIsScanning(true);
522
+ setError(null);
523
+ try {
524
+ await NfcManager.requestTechnology(NfcTech.Ndef, {
525
+ invalidateAfterFirstRead: true
526
+ });
527
+ const tag = await NfcManager.getTag();
528
+ await handleNFCTag(tag || {});
529
+ } catch (err) {
530
+ const error2 = err;
531
+ setError(error2);
532
+ options.onError?.(error2);
533
+ setIsScanning(false);
534
+ }
535
+ }, [handleNFCTag, options]);
536
+ useEffect3(() => {
537
+ if (options.autoStart) {
538
+ startScanning();
539
+ }
540
+ return () => {
541
+ stopScanning();
542
+ };
543
+ }, [options.autoStart, startScanning, stopScanning]);
544
+ return {
545
+ startScanning,
546
+ stopScanning,
547
+ isScanning,
548
+ error
549
+ };
550
+ }
551
+
552
+ // src/hooks/useCheckIn.ts
553
+ import { useState as useState5, useEffect as useEffect4, useCallback as useCallback5 } from "react";
554
+ function useCheckIn(options = {}) {
555
+ const { checkInService, config: _config } = usePassgageAccess();
556
+ const { location } = useLocation();
557
+ const [nearbyBranches, setNearbyBranches] = useState5([]);
558
+ const [isLoading, setIsLoading] = useState5(false);
559
+ const [error, setError] = useState5(null);
560
+ const fetchNearbyBranches = useCallback5(async () => {
561
+ if (!location) {
562
+ return;
563
+ }
564
+ setIsLoading(true);
565
+ setError(null);
566
+ try {
567
+ const response = await checkInService.getNearbyBranches({
568
+ latitude: location.latitude,
569
+ longitude: location.longitude,
570
+ radius: options.radius
571
+ });
572
+ if (response.success && response.data) {
573
+ setNearbyBranches(response.data);
574
+ }
575
+ } catch (err) {
576
+ setError(err);
577
+ } finally {
578
+ setIsLoading(false);
579
+ }
580
+ }, [checkInService, location, options.radius]);
581
+ const checkIn = useCallback5(
582
+ async (params) => {
583
+ setIsLoading(true);
584
+ setError(null);
585
+ try {
586
+ const userId = "";
587
+ const result = await checkInService.checkIn({
588
+ ...params,
589
+ userId,
590
+ userLocation: location || void 0
591
+ });
592
+ if (!result.success) {
593
+ throw new Error(result.message);
594
+ }
595
+ return result.entrance;
596
+ } catch (err) {
597
+ setError(err);
598
+ throw err;
599
+ } finally {
600
+ setIsLoading(false);
601
+ }
602
+ },
603
+ [checkInService, location]
604
+ );
605
+ useEffect4(() => {
606
+ if (options.autoFetch && location) {
607
+ fetchNearbyBranches();
608
+ }
609
+ }, [options.autoFetch, location, fetchNearbyBranches]);
610
+ return {
611
+ nearbyBranches,
612
+ fetchNearbyBranches,
613
+ checkIn,
614
+ isLoading,
615
+ error
616
+ };
617
+ }
618
+
619
+ // src/hooks/useRemoteWork.ts
620
+ import { useState as useState6, useCallback as useCallback6 } from "react";
621
+ import { EntranceType } from "@passgage/sdk-core";
622
+ function useRemoteWork() {
623
+ const { remoteWorkService } = usePassgageAccess();
624
+ const [isLoading, setIsLoading] = useState6(false);
625
+ const [error, setError] = useState6(null);
626
+ const logRemoteWork = useCallback6(
627
+ async (entranceType, options = {}) => {
628
+ setIsLoading(true);
629
+ setError(null);
630
+ try {
631
+ const userId = "";
632
+ const result = await remoteWorkService.logRemoteWork({
633
+ userId,
634
+ entranceType,
635
+ timestamp: options.timestamp,
636
+ description: options.description
637
+ });
638
+ if (!result.success) {
639
+ throw new Error(result.message);
640
+ }
641
+ return result.entrance;
642
+ } catch (err) {
643
+ const error2 = err;
644
+ setError(error2);
645
+ throw error2;
646
+ } finally {
647
+ setIsLoading(false);
648
+ }
649
+ },
650
+ [remoteWorkService]
651
+ );
652
+ const logEntry = useCallback6(
653
+ async (options) => {
654
+ return logRemoteWork(EntranceType.ENTRY, options);
655
+ },
656
+ [logRemoteWork]
657
+ );
658
+ const logExit = useCallback6(
659
+ async (options) => {
660
+ return logRemoteWork(EntranceType.EXIT, options);
661
+ },
662
+ [logRemoteWork]
663
+ );
664
+ return {
665
+ logEntry,
666
+ logExit,
667
+ logRemoteWork,
668
+ isLoading,
669
+ error
670
+ };
671
+ }
672
+
673
+ // src/index.tsx
674
+ var SDK_VERSION = "1.0.0";
675
+ export {
676
+ PassgageAccessProvider,
677
+ SDK_VERSION,
678
+ useCheckIn,
679
+ useLocation,
680
+ useNFCScanner,
681
+ usePassgageAccess,
682
+ usePassgageAuth,
683
+ useQRScanner,
684
+ useRemoteWork
685
+ };