@explorins/pers-sdk-react-native 1.5.26 → 1.5.28

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.
@@ -1 +1 @@
1
- {"version":3,"file":"PersSDKProvider.d.ts","sourceRoot":"","sources":["../../src/providers/PersSDKProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAuC,SAAS,EAAuB,MAAM,OAAO,CAAC;AACnG,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGpF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAG3D,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,MAAM,WAAW,cAAc;IAE7B,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAGpB,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACtC,YAAY,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACxC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACnC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAGlC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IAGjC,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAGzC,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAG9B,UAAU,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,sBAAsB,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,eAAe,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3H,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAMD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,QAAQ,EAAE,SAAS,CAAC;CACrB,CAkHA,CAAC;AAGF,eAAO,MAAM,UAAU,QAAO,cAQ7B,CAAC"}
1
+ {"version":3,"file":"PersSDKProvider.d.ts","sourceRoot":"","sources":["../../src/providers/PersSDKProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAuC,SAAS,EAAuB,MAAM,OAAO,CAAC;AAEnG,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAsB,MAAM,0BAA0B,CAAC;AAIxG,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAG3D,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,MAAM,WAAW,cAAc;IAE7B,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAGpB,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACtC,YAAY,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACxC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACnC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAGlC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IAGjC,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAGzC,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAG9B,UAAU,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,sBAAsB,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,eAAe,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3H,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAMD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,QAAQ,EAAE,SAAS,CAAC;CACrB,CA4HA,CAAC;AAGF,eAAO,MAAM,UAAU,QAAO,cAQ7B,CAAC"}
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useState, useCallback, useRef } from 'react';
3
+ import { Platform } from 'react-native';
3
4
  import { PersSDK } from '@explorins/pers-sdk/core';
4
5
  import { ReactNativeHttpClient } from './react-native-http-client';
5
6
  import { createReactNativeAuthProvider } from './react-native-auth-provider';
7
+ import { ReactNativeDPoPProvider } from './rn-dpop-provider';
6
8
  // Create the context
7
9
  const SDKContext = createContext(null);
8
10
  // Provider component
@@ -39,6 +41,15 @@ export const PersSDKProvider = ({ children }) => {
39
41
  ...config,
40
42
  authProvider
41
43
  };
44
+ // Inject Native DPoP Provider (crypto-based) for mobile platforms
45
+ // Web platforms use built-in WebCrypto provider by default
46
+ if (Platform.OS !== 'web' && (!config.dpop?.cryptoProvider)) {
47
+ sdkConfig.dpop = {
48
+ ...(config.dpop || {}),
49
+ enabled: config.dpop?.enabled ?? true,
50
+ cryptoProvider: new ReactNativeDPoPProvider()
51
+ };
52
+ }
42
53
  // Initialize PersSDK with platform-aware auth provider
43
54
  const sdkInstance = new PersSDK(httpClient, sdkConfig);
44
55
  setAuthProvider(authProvider);
@@ -1 +1 @@
1
- {"version":3,"file":"react-native-auth-provider.d.ts","sourceRoot":"","sources":["../../src/providers/react-native-auth-provider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,mBAAmB,EAAW,QAAQ,EAA4B,MAAM,0BAA0B,CAAC;AAE5G,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAK7D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,qBAA0B,GACjC,mBAAmB,CAyBrB"}
1
+ {"version":3,"file":"react-native-auth-provider.d.ts","sourceRoot":"","sources":["../../src/providers/react-native-auth-provider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,mBAAmB,EAAW,QAAQ,EAA4B,MAAM,0BAA0B,CAAC;AAG5G,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAK7D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,qBAA0B,GACjC,mBAAmB,CAyBrB"}
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { Platform } from 'react-native';
9
9
  import { DefaultAuthProvider, LocalStorageTokenStorage } from '@explorins/pers-sdk/core';
10
- import { AsyncStorageTokenStorage } from '../storage/async-storage-token-storage';
10
+ import { ReactNativeSecureStorage } from '../storage/rn-secure-storage';
11
11
  // Use React Native's built-in platform detection
12
12
  const isWebPlatform = Platform.OS === 'web';
13
13
  /**
@@ -29,7 +29,7 @@ export function createReactNativeAuthProvider(projectKey, config = {}) {
29
29
  // Platform-specific storage selection
30
30
  const tokenStorage = customStorage || (isWebPlatform
31
31
  ? new LocalStorageTokenStorage()
32
- : new AsyncStorageTokenStorage(keyPrefix));
32
+ : new ReactNativeSecureStorage(keyPrefix));
33
33
  // Return DefaultAuthProvider configured with platform-appropriate storage
34
34
  return new DefaultAuthProvider({
35
35
  authType,
@@ -0,0 +1,19 @@
1
+ import { DPoPCryptoProvider, DPoPKeyPair } from '@explorins/pers-sdk/core';
2
+ export declare class ReactNativeDPoPProvider implements DPoPCryptoProvider {
3
+ /**
4
+ * Generates a new key pair (ES256 recommended)
5
+ *
6
+ * Note: options.extractable is ignored because for React Native Keychain storage,
7
+ * we MUST export the keys as JWK/strings. We rely on the Keychain encryption
8
+ * for security at rest (High Security), making the key "Extractable" in memory
9
+ * but protected by hardware encryption when stored.
10
+ */
11
+ generateKeyPair(options?: {
12
+ extractable?: boolean;
13
+ }): Promise<DPoPKeyPair>;
14
+ signProof(payload: Record<string, any>, keyPair: DPoPKeyPair): Promise<string>;
15
+ hashAccessToken(accessToken: string): Promise<string>;
16
+ private base64Url;
17
+ private base64UrlBuffer;
18
+ }
19
+ //# sourceMappingURL=rn-dpop-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rn-dpop-provider.d.ts","sourceRoot":"","sources":["../../src/providers/rn-dpop-provider.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAkB3E,qBAAa,uBAAwB,YAAW,kBAAkB;IAChE;;;;;;;OAOG;IACG,eAAe,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAoB1E,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAmC9E,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAO3D,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,eAAe;CAQxB"}
@@ -0,0 +1,93 @@
1
+ import { Platform } from 'react-native';
2
+ import { Buffer } from 'buffer';
3
+ // Conditionally require quick-crypto for Native platforms only
4
+ let crypto;
5
+ if (Platform.OS !== 'web') {
6
+ try {
7
+ crypto = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
8
+ }
9
+ catch (e) {
10
+ console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto', e);
11
+ }
12
+ }
13
+ else {
14
+ // on Web, we shouldn't be using this provider anyway (Core SDK has WebDPoPProvider)
15
+ // But to be safe, we can mock or throw
16
+ crypto = {
17
+ generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); }
18
+ };
19
+ }
20
+ export class ReactNativeDPoPProvider {
21
+ /**
22
+ * Generates a new key pair (ES256 recommended)
23
+ *
24
+ * Note: options.extractable is ignored because for React Native Keychain storage,
25
+ * we MUST export the keys as JWK/strings. We rely on the Keychain encryption
26
+ * for security at rest (High Security), making the key "Extractable" in memory
27
+ * but protected by hardware encryption when stored.
28
+ */
29
+ async generateKeyPair(options) {
30
+ // Generate P-256 Key Pair
31
+ return new Promise((resolve, reject) => {
32
+ crypto.generateKeyPair('ec', { namedCurve: 'P-256' }, (err, publicKey, privateKey) => {
33
+ if (err)
34
+ return reject(err);
35
+ // Export to JWK for SDK Compatibility
36
+ // We use 'export' because supportsObjects=false in our storage forces the SDK
37
+ // to expect serializable keys.
38
+ const pubJwk = publicKey.export({ format: 'jwk' });
39
+ const privJwk = privateKey.export({ format: 'jwk' });
40
+ resolve({
41
+ publicKey: pubJwk,
42
+ privateKey: privJwk
43
+ });
44
+ });
45
+ });
46
+ }
47
+ async signProof(payload, keyPair) {
48
+ // 1. Construct Header
49
+ const header = {
50
+ typ: 'dpop+jwt',
51
+ alg: 'ES256',
52
+ jwk: keyPair.publicKey
53
+ };
54
+ // 2. Add Claims (iat/jti)
55
+ const finalPayload = {
56
+ ...payload,
57
+ iat: payload.iat || Math.floor(Date.now() / 1000),
58
+ jti: payload.jti || crypto.randomUUID()
59
+ };
60
+ // 3. Encode
61
+ const b64Header = this.base64Url(JSON.stringify(header));
62
+ const b64Payload = this.base64Url(JSON.stringify(finalPayload));
63
+ const signingInput = `${b64Header}.${b64Payload}`;
64
+ // 4. Sign
65
+ const sign = crypto.createSign('SHA256');
66
+ sign.update(signingInput);
67
+ // sign.end() is not required/available in quick-crypto as it doesn't strictly follow stream interface
68
+ // Import private key back from JWK to sign
69
+ // The keyPair.privateKey is a JsonWebKey object because we exported it earlier.
70
+ // quick-crypto createPrivateKey expects jwk object if format is jwk
71
+ const privateKeyObj = crypto.createPrivateKey({ key: keyPair.privateKey, format: 'jwk' });
72
+ const signature = sign.sign(privateKeyObj);
73
+ // signature is a Buffer in quick-crypto
74
+ return `${signingInput}.${this.base64UrlBuffer(signature)}`;
75
+ }
76
+ async hashAccessToken(accessToken) {
77
+ const hash = crypto.createHash('sha256').update(accessToken).digest();
78
+ // digest returns Buffer in quick-crypto
79
+ return this.base64UrlBuffer(hash);
80
+ }
81
+ // --- Helpers ---
82
+ base64Url(str) {
83
+ return this.base64UrlBuffer(Buffer.from(str));
84
+ }
85
+ base64UrlBuffer(buf) {
86
+ // Ensure we have a Buffer
87
+ const buffer = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
88
+ return buffer.toString('base64')
89
+ .replace(/=/g, '')
90
+ .replace(/\+/g, '-')
91
+ .replace(/\//g, '_');
92
+ }
93
+ }
@@ -0,0 +1,18 @@
1
+ import { TokenStorage } from '@explorins/pers-sdk/core';
2
+ /**
3
+ * Secure Storage implementation for React Native
4
+ * Uses Keychain/Keystore for sensitive data and AsyncStorage for non-sensitive data.
5
+ */
6
+ export declare class ReactNativeSecureStorage implements TokenStorage {
7
+ private keyPrefix;
8
+ readonly supportsObjects = false;
9
+ private readonly SECURE_KEYS;
10
+ constructor(keyPrefix?: string);
11
+ set(key: string, value: unknown): Promise<void>;
12
+ get(key: string): Promise<unknown | null>;
13
+ remove(key: string): Promise<void>;
14
+ clear(): Promise<void>;
15
+ private getKeyName;
16
+ private tryParse;
17
+ }
18
+ //# sourceMappingURL=rn-secure-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rn-secure-storage.d.ts","sourceRoot":"","sources":["../../src/storage/rn-secure-storage.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EAGb,MAAM,0BAA0B,CAAC;AAYlC;;;GAGG;AACH,qBAAa,wBAAyB,YAAW,YAAY;IAY/C,OAAO,CAAC,SAAS;IAV7B,QAAQ,CAAC,eAAe,SAAS;IAGjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAKzB;gBAEiB,SAAS,GAAE,MAAuB;IAEhD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB/C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAyBzC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,QAAQ;CAOjB"}
@@ -0,0 +1,112 @@
1
+ import { Platform } from 'react-native';
2
+ import AsyncStorage from '@react-native-async-storage/async-storage';
3
+ import { DPOP_STORAGE_KEYS, AUTH_STORAGE_KEYS } from '@explorins/pers-sdk/core';
4
+ // Conditionally require Keychain to avoid Web bundler errors
5
+ let Keychain;
6
+ if (Platform.OS !== 'web') {
7
+ try {
8
+ Keychain = require('react-native-keychain');
9
+ }
10
+ catch (e) {
11
+ console.warn('ReactNativeSecureStorage: Failed to load react-native-keychain', e);
12
+ }
13
+ }
14
+ /**
15
+ * Secure Storage implementation for React Native
16
+ * Uses Keychain/Keystore for sensitive data and AsyncStorage for non-sensitive data.
17
+ */
18
+ export class ReactNativeSecureStorage {
19
+ constructor(keyPrefix = 'pers_tokens_') {
20
+ this.keyPrefix = keyPrefix;
21
+ // Set to false because Keychain stores strings, so we need extractable (serializable) keys
22
+ this.supportsObjects = false;
23
+ // Define which keys MUST go to secure hardware storage
24
+ this.SECURE_KEYS = new Set([
25
+ DPOP_STORAGE_KEYS.PRIVATE,
26
+ AUTH_STORAGE_KEYS.ACCESS_TOKEN,
27
+ AUTH_STORAGE_KEYS.REFRESH_TOKEN,
28
+ AUTH_STORAGE_KEYS.PROVIDER_TOKEN
29
+ ]);
30
+ }
31
+ async set(key, value) {
32
+ const prefixedKey = this.getKeyName(key);
33
+ const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
34
+ // Cast key to any to bypass strict literal type checking of the Set.has method
35
+ // In runtime, 'key' is just a string, so this is safe.
36
+ if (this.SECURE_KEYS.has(key)) {
37
+ // Store in Keychain/Keystore
38
+ // Service name acts as the key identifier in simple usage
39
+ await Keychain.setGenericPassword(prefixedKey, stringValue, { service: prefixedKey });
40
+ }
41
+ else {
42
+ // Store standard config/metadata in AsyncStorage
43
+ await AsyncStorage.setItem(prefixedKey, stringValue);
44
+ }
45
+ }
46
+ async get(key) {
47
+ const prefixedKey = this.getKeyName(key);
48
+ if (this.SECURE_KEYS.has(key)) {
49
+ try {
50
+ const credentials = await Keychain.getGenericPassword({ service: prefixedKey });
51
+ if (credentials) {
52
+ return this.tryParse(credentials.password);
53
+ }
54
+ return null;
55
+ }
56
+ catch (e) {
57
+ console.warn(`[ReactNativeSecureStorage] Failed to access keychain for ${key}`, e);
58
+ return null; // Key not found or access denied
59
+ }
60
+ }
61
+ else {
62
+ try {
63
+ const val = await AsyncStorage.getItem(prefixedKey);
64
+ return val ? this.tryParse(val) : null;
65
+ }
66
+ catch (e) {
67
+ console.warn(`[ReactNativeSecureStorage] Failed to access AsyncStorage for ${key}`, e);
68
+ return null;
69
+ }
70
+ }
71
+ }
72
+ async remove(key) {
73
+ const prefixedKey = this.getKeyName(key);
74
+ if (this.SECURE_KEYS.has(key)) {
75
+ await Keychain.resetGenericPassword({ service: prefixedKey });
76
+ }
77
+ else {
78
+ await AsyncStorage.removeItem(prefixedKey);
79
+ }
80
+ }
81
+ async clear() {
82
+ // Clear all known secure keys
83
+ for (const key of this.SECURE_KEYS) {
84
+ await Keychain.resetGenericPassword({ service: this.getKeyName(key) });
85
+ }
86
+ // Clear AsyncStorage keys related to PERS
87
+ try {
88
+ const allKeys = await AsyncStorage.getAllKeys();
89
+ const ourKeys = allKeys.filter(k => k.startsWith(this.keyPrefix));
90
+ if (ourKeys.length > 0) {
91
+ await AsyncStorage.multiRemove(ourKeys);
92
+ }
93
+ }
94
+ catch (e) {
95
+ console.warn('[ReactNativeSecureStorage] Failed to clear AsyncStorage', e);
96
+ }
97
+ }
98
+ getKeyName(key) {
99
+ // For Keychain, we might want to avoid prefixes if we want to share across apps,
100
+ // but for simple isolation, prefix is fine to avoid collisions if multiple instances exist.
101
+ // However, Keychain services are global to the app bundle ID usually.
102
+ return `${this.keyPrefix}${key}`;
103
+ }
104
+ tryParse(val) {
105
+ try {
106
+ return JSON.parse(val);
107
+ }
108
+ catch {
109
+ return val;
110
+ }
111
+ }
112
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@explorins/pers-sdk-react-native",
3
- "version": "1.5.26",
3
+ "version": "1.5.28",
4
4
  "description": "React Native SDK for PERS Platform - Tourism Loyalty System with Blockchain Transaction Signing and WebAuthn Authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,15 +37,16 @@
37
37
  "author": "eXplorins",
38
38
  "license": "MIT",
39
39
  "dependencies": {
40
- "@explorins/pers-sdk": "^1.6.28",
41
- "@explorins/pers-shared": "^2.1.44",
42
- "@explorins/pers-signer": "^1.0.26",
40
+ "@explorins/pers-sdk": "file:.yalc/@explorins/pers-sdk",
41
+ "@explorins/pers-shared": "^2.1.52",
42
+ "@explorins/pers-signer": "^1.0.32",
43
43
  "@explorins/web3-ts": "^0.3.75",
44
44
  "buffer": "^6.0.3",
45
45
  "ethers": "^6.15.0",
46
46
  "react-native-get-random-values": "^2.0.0",
47
47
  "react-native-keychain": "^10.0.0",
48
- "react-native-passkey": "^3.3.2"
48
+ "react-native-passkey": "^3.3.2",
49
+ "react-native-quick-crypto": "^1.0.6"
49
50
  },
50
51
  "browser": {
51
52
  "crypto": false,
@@ -1,81 +1,9 @@
1
1
  import { useCallback } from 'react';
2
2
  import { usePersSDK } from '../providers/PersSDKProvider';
3
-
4
- // Import analytics types directly from core SDK
5
- interface TransactionAnalyticsFilters {
6
- status?: string;
7
- tokenType?: string;
8
- triggerProcessType?: string;
9
- tenantId?: string;
10
- chainId?: number;
11
- type?: string;
12
- [key: string]: any;
13
- }
14
-
15
- type TransactionAnalyticsGroupBy =
16
- | 'month' | 'week' | 'day' | 'year' | 'quarter'
17
- | 'tokenType' | 'status' | 'chainId' | 'triggerProcessType' | 'type'
18
- | 'senderAddress' | 'recipientAddress' | 'senderId' | 'recipientId' | 'senderOwnerType' | 'recipientOwnerType' | 'createdAt';
19
-
20
- type TransactionAnalyticsMetric =
21
- | 'count' | 'sum' | 'avg' | 'min' | 'max';
22
-
23
- interface TransactionAnalyticsGroupByExpression {
24
- expression: string;
25
- alias: string;
26
- }
27
-
28
- /**
29
- * Request DTO for transaction analytics
30
- *
31
- * @interface TransactionAnalyticsRequestDTO
32
- */
33
- export interface TransactionAnalyticsRequestDTO {
34
- filters?: TransactionAnalyticsFilters;
35
- groupBy?: TransactionAnalyticsGroupBy[];
36
- groupByExpressions?: TransactionAnalyticsGroupByExpression[];
37
- metrics?: TransactionAnalyticsMetric[];
38
- orderBy?: string;
39
- orderDirection?: 'ASC' | 'DESC';
40
- limit?: number;
41
- startDate?: string;
42
- endDate?: string;
43
- }
44
-
45
- interface TransactionAnalyticsResultItem {
46
- [key: string]: string | number | Date | undefined;
47
- count?: string;
48
- sum?: number;
49
- avg?: number;
50
- min?: number;
51
- max?: number;
52
- tokentype?: string;
53
- tokenType?: string;
54
- year?: string;
55
- month?: string;
56
- type?: string;
57
- senderaddress?: string;
58
- senderAddress?: string;
59
- recipientaddress?: string;
60
- recipientAddress?: string;
61
- triggerprocesstype?: string;
62
- triggerProcessType?: string;
63
- userid?: string;
64
- userId?: string;
65
- }
66
-
67
- /**
68
- * Response DTO for transaction analytics
69
- *
70
- * @interface TransactionAnalyticsResponseDTO
71
- */
72
- export interface TransactionAnalyticsResponseDTO {
73
- results: TransactionAnalyticsResultItem[];
74
- totalGroups: number;
75
- metadata: {
76
- executionTime: string;
77
- };
78
- }
3
+ import {
4
+ TransactionAnalyticsRequestDTO,
5
+ TransactionAnalyticsResponseDTO
6
+ } from '@explorins/pers-shared';
79
7
 
80
8
  /**
81
9
  * React hook for analytics operations in the PERS SDK
@@ -5,9 +5,9 @@ import type {
5
5
  TransactionRequestDTO,
6
6
  TransactionRequestResponseDTO,
7
7
  TransactionDTO,
8
- TransactionRole
8
+ TransactionRole,
9
+ TransactionPaginationRequestDTO
9
10
  } from '@explorins/pers-shared';
10
- import type { TransactionPaginationParams } from '@explorins/pers-sdk/transaction';
11
11
 
12
12
  /**
13
13
  * React hook for transaction operations in the PERS SDK
@@ -196,7 +196,7 @@ export const useTransactions = () => {
196
196
  }
197
197
  }, [sdk, isInitialized]);
198
198
 
199
- const getPaginatedTransactions = useCallback(async (params: TransactionPaginationParams): Promise<any> => {
199
+ const getPaginatedTransactions = useCallback(async (params: TransactionPaginationRequestDTO): Promise<any> => {
200
200
  if (!isInitialized || !sdk) {
201
201
  throw new Error('SDK not initialized. Call initialize() first.');
202
202
  }
package/src/index.ts CHANGED
@@ -68,6 +68,19 @@ export {
68
68
  // SECURE STORAGE
69
69
  // ==============================================================================
70
70
 
71
+ /**
72
+ * Secure Token Storage with Keychain Integration
73
+ *
74
+ * Implements the TokenStorage interface using React Native Keychain for sensitive data
75
+ * (Private Keys, Access Tokens) and AsyncStorage for configuration/public data.
76
+ *
77
+ * - Encryption: Hardware-backed keystore
78
+ * - Persistence: Survives app uninstall (on iOS usually) or clears on uninstall (Android) if configured
79
+ *
80
+ * @see {@link ReactNativeSecureStorage} - The implementation class
81
+ */
82
+ export { ReactNativeSecureStorage } from './storage/rn-secure-storage';
83
+
71
84
  /**
72
85
  * Secure token storage using React Native AsyncStorage
73
86
  *
@@ -75,11 +88,22 @@ export {
75
88
  * Integrates with React Native Keychain for enhanced security on supported devices.
76
89
  *
77
90
  * @see {@link AsyncStorageTokenStorage} - Secure storage implementation
91
+ * @deprecated Use ReactNativeSecureStorage for better security (Keychain integration)
78
92
  */
79
93
  export {
80
94
  AsyncStorageTokenStorage,
81
95
  } from './storage/async-storage-token-storage';
82
96
 
97
+ // ==============================================================================
98
+ // DPoP PROVIDER
99
+ // ==============================================================================
100
+
101
+ /**
102
+ * React Native DPoP Crypto Provider
103
+ * implements DPoPCryptoProvider using react-native-quick-crypto
104
+ */
105
+ export { ReactNativeDPoPProvider } from './providers/rn-dpop-provider';
106
+
83
107
  // ==============================================================================
84
108
  // CORE PROVIDER & CONTEXT
85
109
  // ==============================================================================
@@ -1,7 +1,9 @@
1
1
  import React, { createContext, useContext, useState, ReactNode, useCallback, useRef } from 'react';
2
- import { PersSDK, PersConfig, DefaultAuthProvider } from '@explorins/pers-sdk/core';
2
+ import { Platform } from 'react-native';
3
+ import { PersSDK, PersConfig, DefaultAuthProvider, DPoPCryptoProvider } from '@explorins/pers-sdk/core';
3
4
  import { ReactNativeHttpClient } from './react-native-http-client';
4
5
  import { createReactNativeAuthProvider } from './react-native-auth-provider';
6
+ import { ReactNativeDPoPProvider } from './rn-dpop-provider';
5
7
  import { UserDTO, AdminDTO } from '@explorins/pers-shared';
6
8
 
7
9
  // Import manager types for TypeScript
@@ -102,6 +104,16 @@ export const PersSDKProvider: React.FC<{
102
104
  authProvider
103
105
  };
104
106
 
107
+ // Inject Native DPoP Provider (crypto-based) for mobile platforms
108
+ // Web platforms use built-in WebCrypto provider by default
109
+ if (Platform.OS !== 'web' && (!config.dpop?.cryptoProvider)) {
110
+ sdkConfig.dpop = {
111
+ ...(config.dpop || {}),
112
+ enabled: config.dpop?.enabled ?? true,
113
+ cryptoProvider: new ReactNativeDPoPProvider()
114
+ };
115
+ }
116
+
105
117
  // Initialize PersSDK with platform-aware auth provider
106
118
  const sdkInstance = new PersSDK(httpClient, sdkConfig);
107
119
 
@@ -9,6 +9,7 @@
9
9
  import { Platform } from 'react-native';
10
10
  import { DefaultAuthProvider, AuthApi, AuthType, LocalStorageTokenStorage } from '@explorins/pers-sdk/core';
11
11
  import { AsyncStorageTokenStorage } from '../storage/async-storage-token-storage';
12
+ import { ReactNativeSecureStorage } from '../storage/rn-secure-storage';
12
13
  import type { TokenStorage } from '@explorins/pers-sdk/core';
13
14
 
14
15
  // Use React Native's built-in platform detection
@@ -58,7 +59,7 @@ export function createReactNativeAuthProvider(
58
59
  const tokenStorage = customStorage || (
59
60
  isWebPlatform
60
61
  ? new LocalStorageTokenStorage()
61
- : new AsyncStorageTokenStorage(keyPrefix)
62
+ : new ReactNativeSecureStorage(keyPrefix)
62
63
  );
63
64
 
64
65
  // Return DefaultAuthProvider configured with platform-appropriate storage
@@ -0,0 +1,104 @@
1
+ import { Platform } from 'react-native';
2
+ import { Buffer } from 'buffer';
3
+ import { DPoPCryptoProvider, DPoPKeyPair } from '@explorins/pers-sdk/core';
4
+
5
+ // Conditionally require quick-crypto for Native platforms only
6
+ let crypto: any;
7
+ if (Platform.OS !== 'web') {
8
+ try {
9
+ crypto = require('react-native-quick-crypto').default || require('react-native-quick-crypto');
10
+ } catch (e) {
11
+ console.warn('ReactNativeDPoPProvider: Failed to load react-native-quick-crypto', e);
12
+ }
13
+ } else {
14
+ // on Web, we shouldn't be using this provider anyway (Core SDK has WebDPoPProvider)
15
+ // But to be safe, we can mock or throw
16
+ crypto = {
17
+ generateKeyPair: () => { throw new Error('ReactNativeDPoPProvider not supported on Web'); }
18
+ };
19
+ }
20
+
21
+ export class ReactNativeDPoPProvider implements DPoPCryptoProvider {
22
+ /**
23
+ * Generates a new key pair (ES256 recommended)
24
+ *
25
+ * Note: options.extractable is ignored because for React Native Keychain storage,
26
+ * we MUST export the keys as JWK/strings. We rely on the Keychain encryption
27
+ * for security at rest (High Security), making the key "Extractable" in memory
28
+ * but protected by hardware encryption when stored.
29
+ */
30
+ async generateKeyPair(options?: { extractable?: boolean }): Promise<DPoPKeyPair> {
31
+ // Generate P-256 Key Pair
32
+ return new Promise((resolve, reject) => {
33
+ crypto.generateKeyPair('ec', { namedCurve: 'P-256' }, (err: any, publicKey: any, privateKey: any) => {
34
+ if (err) return reject(err);
35
+
36
+ // Export to JWK for SDK Compatibility
37
+ // We use 'export' because supportsObjects=false in our storage forces the SDK
38
+ // to expect serializable keys.
39
+ const pubJwk = publicKey.export({ format: 'jwk' });
40
+ const privJwk = privateKey.export({ format: 'jwk' });
41
+
42
+ resolve({
43
+ publicKey: pubJwk,
44
+ privateKey: privJwk
45
+ });
46
+ });
47
+ });
48
+ }
49
+
50
+ async signProof(payload: Record<string, any>, keyPair: DPoPKeyPair): Promise<string> {
51
+ // 1. Construct Header
52
+ const header = {
53
+ typ: 'dpop+jwt',
54
+ alg: 'ES256',
55
+ jwk: keyPair.publicKey
56
+ };
57
+
58
+ // 2. Add Claims (iat/jti)
59
+ const finalPayload = {
60
+ ...payload,
61
+ iat: payload.iat || Math.floor(Date.now() / 1000),
62
+ jti: payload.jti || crypto.randomUUID()
63
+ };
64
+
65
+ // 3. Encode
66
+ const b64Header = this.base64Url(JSON.stringify(header));
67
+ const b64Payload = this.base64Url(JSON.stringify(finalPayload));
68
+ const signingInput = `${b64Header}.${b64Payload}`;
69
+
70
+ // 4. Sign
71
+ const sign = crypto.createSign('SHA256');
72
+ sign.update(signingInput);
73
+ // sign.end() is not required/available in quick-crypto as it doesn't strictly follow stream interface
74
+
75
+ // Import private key back from JWK to sign
76
+ // The keyPair.privateKey is a JsonWebKey object because we exported it earlier.
77
+ // quick-crypto createPrivateKey expects jwk object if format is jwk
78
+ const privateKeyObj = crypto.createPrivateKey({ key: keyPair.privateKey as any, format: 'jwk' });
79
+ const signature = sign.sign(privateKeyObj);
80
+ // signature is a Buffer in quick-crypto
81
+
82
+ return `${signingInput}.${this.base64UrlBuffer(signature)}`;
83
+ }
84
+
85
+ async hashAccessToken(accessToken: string): Promise<string> {
86
+ const hash = crypto.createHash('sha256').update(accessToken).digest();
87
+ // digest returns Buffer in quick-crypto
88
+ return this.base64UrlBuffer(hash);
89
+ }
90
+
91
+ // --- Helpers ---
92
+ private base64Url(str: string): string {
93
+ return this.base64UrlBuffer(Buffer.from(str));
94
+ }
95
+
96
+ private base64UrlBuffer(buf: Buffer | Uint8Array): string {
97
+ // Ensure we have a Buffer
98
+ const buffer = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
99
+ return buffer.toString('base64')
100
+ .replace(/=/g, '')
101
+ .replace(/\+/g, '-')
102
+ .replace(/\//g, '_');
103
+ }
104
+ }