@savers_app/react-native-sdk 1.2.0 → 1.2.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/README.md +2 -2
- package/lib/module/services/navigation/openMap.js +23 -7
- package/lib/module/services/navigation/openMap.js.map +1 -1
- package/lib/module/utils/encryption.js +33 -13
- package/lib/module/utils/encryption.js.map +1 -1
- package/lib/typescript/src/services/navigation/openMap.d.ts.map +1 -1
- package/lib/typescript/src/utils/encryption.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/services/navigation/openMap.ts +29 -15
- package/src/utils/encryption.ts +47 -9
package/README.md
CHANGED
|
@@ -101,8 +101,8 @@ import { SaversAppSDK } from '@savers_app/react-native-sdk';
|
|
|
101
101
|
SaversAppSDK.initialized({
|
|
102
102
|
apiKey: 'YOUR_API_KEY',
|
|
103
103
|
encryptionKey: 'BASE64_256_BIT_ENCRYPTION_KEY',
|
|
104
|
-
pRefCode: '
|
|
105
|
-
authMode: '
|
|
104
|
+
pRefCode: 'PROGRAM_REF_CODE',
|
|
105
|
+
authMode: 'CUSTOMER_SIGN_IN_UP_MODE' // 'EMAIL' | 'PHONE'
|
|
106
106
|
});
|
|
107
107
|
```
|
|
108
108
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Linking } from 'react-native';
|
|
5
5
|
|
|
6
6
|
// Utilities / Helpers
|
|
7
|
-
import {
|
|
7
|
+
import { isValidCoordinates } from "../../utils/validator.js";
|
|
8
8
|
|
|
9
9
|
// Platform / Environment helpers
|
|
10
10
|
import { isIOS } from "../../utils/platformManager.js";
|
|
@@ -13,14 +13,30 @@ export async function openMap(lat, lng, label) {
|
|
|
13
13
|
throw new Error('INVALID_COORDINATES');
|
|
14
14
|
}
|
|
15
15
|
const iosUrl = `maps:0,0?q=${encodeURIComponent(label ?? '')}@${lat},${lng}`;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
if (isIOS()) {
|
|
17
|
+
try {
|
|
18
|
+
await Linking.openURL(iosUrl);
|
|
19
|
+
return;
|
|
20
|
+
} catch {}
|
|
21
|
+
const browserUrl = `https://maps.apple.com/?q=${encodeURIComponent(label ?? '')}&ll=${lat},${lng}`;
|
|
22
|
+
await Linking.openURL(browserUrl);
|
|
21
23
|
return;
|
|
22
24
|
}
|
|
23
|
-
const
|
|
25
|
+
const encLabel = label ? encodeURIComponent(label) : undefined;
|
|
26
|
+
const query = `${lat},${lng}${encLabel ? `(${encLabel})` : ''}`;
|
|
27
|
+
const candidates = [
|
|
28
|
+
// Prefer geo to show a pin at the destination (not navigation)
|
|
29
|
+
`geo:${lat},${lng}?q=${query}`, `geo:0,0?q=${query}`,
|
|
30
|
+
// Fallback to Google Maps custom scheme as a generic search/pin
|
|
31
|
+
`comgooglemaps://?q=${query}`];
|
|
32
|
+
for (const url of candidates) {
|
|
33
|
+
try {
|
|
34
|
+
await Linking.openURL(url);
|
|
35
|
+
return;
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
const browserQuery = label ? `${label} ${lat},${lng}` : `${lat},${lng}`;
|
|
39
|
+
const browserUrl = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(browserQuery)}`;
|
|
24
40
|
await Linking.openURL(browserUrl);
|
|
25
41
|
}
|
|
26
42
|
//# sourceMappingURL=openMap.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Linking","
|
|
1
|
+
{"version":3,"names":["Linking","isValidCoordinates","isIOS","openMap","lat","lng","label","Error","iosUrl","encodeURIComponent","openURL","browserUrl","encLabel","undefined","query","candidates","url","browserQuery"],"sourceRoot":"../../../../src","sources":["services/navigation/openMap.ts"],"mappings":";;AAAA;AACA,SAASA,OAAO,QAAQ,cAAc;;AAEtC;AACA,SAASC,kBAAkB,QAAQ,0BAAuB;;AAE1D;AACA,SAASC,KAAK,QAAQ,gCAA6B;AAEnD,OAAO,eAAeC,OAAOA,CAC3BC,GAAW,EACXC,GAAW,EACXC,KAAc,EACC;EACf,IAAI,CAACL,kBAAkB,CAACG,GAAG,EAAEC,GAAG,CAAC,EAAE;IACjC,MAAM,IAAIE,KAAK,CAAC,qBAAqB,CAAC;EACxC;EAEA,MAAMC,MAAM,GAAG,cAAcC,kBAAkB,CAACH,KAAK,IAAI,EAAE,CAAC,IAAIF,GAAG,IAAIC,GAAG,EAAE;EAE5E,IAAIH,KAAK,CAAC,CAAC,EAAE;IACX,IAAI;MACF,MAAMF,OAAO,CAACU,OAAO,CAACF,MAAM,CAAC;MAC7B;IACF,CAAC,CAAC,MAAM,CAAC;IACT,MAAMG,UAAU,GAAG,6BAA6BF,kBAAkB,CAChEH,KAAK,IAAI,EACX,CAAC,OAAOF,GAAG,IAAIC,GAAG,EAAE;IACpB,MAAML,OAAO,CAACU,OAAO,CAACC,UAAU,CAAC;IACjC;EACF;EAEA,MAAMC,QAAQ,GAAGN,KAAK,GAAGG,kBAAkB,CAACH,KAAK,CAAC,GAAGO,SAAS;EAC9D,MAAMC,KAAK,GAAG,GAAGV,GAAG,IAAIC,GAAG,GAAGO,QAAQ,GAAG,IAAIA,QAAQ,GAAG,GAAG,EAAE,EAAE;EAC/D,MAAMG,UAAU,GAAG;EACjB;EACA,OAAOX,GAAG,IAAIC,GAAG,MAAMS,KAAK,EAAE,EAC9B,aAAaA,KAAK,EAAE;EACpB;EACA,sBAAsBA,KAAK,EAAE,CAC9B;EACD,KAAK,MAAME,GAAG,IAAID,UAAU,EAAE;IAC5B,IAAI;MACF,MAAMf,OAAO,CAACU,OAAO,CAACM,GAAG,CAAC;MAC1B;IACF,CAAC,CAAC,MAAM,CAAC;EACX;EACA,MAAMC,YAAY,GAAGX,KAAK,GAAG,GAAGA,KAAK,IAAIF,GAAG,IAAIC,GAAG,EAAE,GAAG,GAAGD,GAAG,IAAIC,GAAG,EAAE;EACvE,MAAMM,UAAU,GAAG,mDAAmDF,kBAAkB,CACtFQ,YACF,CAAC,EAAE;EACH,MAAMjB,OAAO,CAACU,OAAO,CAACC,UAAU,CAAC;AACnC","ignoreList":[]}
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import { NativeModules } from 'react-native';
|
|
4
4
|
import AesGcmCrypto from 'react-native-aes-gcm-crypto';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Ensure native module is installed & linked
|
|
8
|
+
*/
|
|
9
|
+
function ensureAesAvailable() {
|
|
10
|
+
const nativeModule = NativeModules.AesGcmCrypto || NativeModules.RNAesGcmCrypto;
|
|
11
|
+
if (!AesGcmCrypto || !nativeModule) {
|
|
12
|
+
throw new Error('react-native-aes-gcm-crypto is not installed or linked correctly.\n\n' + 'Fix:\n' + '1. yarn add react-native-aes-gcm-crypto\n' + '2. cd ios && pod install\n' + '3. Rebuild the app\n');
|
|
13
|
+
}
|
|
14
|
+
if (typeof AesGcmCrypto.encrypt !== 'function') {
|
|
15
|
+
throw new Error('AES-GCM native functions unavailable. Please rebuild the app.');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
5
18
|
function isBase64(input) {
|
|
6
19
|
return /^[A-Za-z0-9+/]+={0,2}$/.test(input);
|
|
7
20
|
}
|
|
@@ -10,26 +23,33 @@ function base64ByteLength(b64) {
|
|
|
10
23
|
return Math.floor(cleaned.length * 3 / 4);
|
|
11
24
|
}
|
|
12
25
|
export async function aesEncrypt(text, key) {
|
|
26
|
+
ensureAesAvailable();
|
|
13
27
|
if (!isBase64(key) || base64ByteLength(key) !== 32) {
|
|
14
28
|
throw new Error('Encryption key must be base64-encoded 32-byte (256-bit) value');
|
|
15
29
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
try {
|
|
31
|
+
const {
|
|
32
|
+
iv,
|
|
33
|
+
tag,
|
|
34
|
+
content
|
|
35
|
+
} = await AesGcmCrypto.encrypt(text, false, key);
|
|
36
|
+
return base64Encrypt(`${iv}:${content}:${tag}`);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
throw new Error(`AES encryption failed: ${err?.message || err}`);
|
|
39
|
+
}
|
|
22
40
|
}
|
|
23
41
|
export async function aesDecrypt(ciphertext, key) {
|
|
42
|
+
ensureAesAvailable();
|
|
24
43
|
if (!isBase64(key) || base64ByteLength(key) !== 32) {
|
|
25
44
|
throw new Error('Encryption key must be base64-encoded 32-byte (256-bit) value');
|
|
26
45
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
try {
|
|
47
|
+
const [iv = '', content = '', tag = ''] = ciphertext.split(':');
|
|
48
|
+
const decrypted = await AesGcmCrypto.decrypt(content, key, iv, tag, false);
|
|
49
|
+
return decrypted;
|
|
50
|
+
} catch (err) {
|
|
51
|
+
throw new Error(`AES decryption failed: ${err?.message || err}`);
|
|
52
|
+
}
|
|
33
53
|
}
|
|
34
54
|
export function base64Encrypt(plain) {
|
|
35
55
|
const utf8 = encodeURIComponent(plain).replace(/%([0-9A-F]{2})/g, (_, p1) => String.fromCharCode(parseInt(p1, 16)));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["AesGcmCrypto","isBase64","input","test","base64ByteLength","b64","cleaned","replace","Math","floor","length","aesEncrypt","text","key","
|
|
1
|
+
{"version":3,"names":["NativeModules","AesGcmCrypto","ensureAesAvailable","nativeModule","RNAesGcmCrypto","Error","encrypt","isBase64","input","test","base64ByteLength","b64","cleaned","replace","Math","floor","length","aesEncrypt","text","key","iv","tag","content","base64Encrypt","err","message","aesDecrypt","ciphertext","split","decrypted","decrypt","plain","utf8","encodeURIComponent","_","p1","String","fromCharCode","parseInt","btoa","base64Decrypt","encoded","binary","atob","map","c","charCodeAt","toString","slice","join","decodeURIComponent"],"sourceRoot":"../../../src","sources":["utils/encryption.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openMap.d.ts","sourceRoot":"","sources":["../../../../../src/services/navigation/openMap.ts"],"names":[],"mappings":"AASA,wBAAsB,OAAO,CAC3B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"openMap.d.ts","sourceRoot":"","sources":["../../../../../src/services/navigation/openMap.ts"],"names":[],"mappings":"AASA,wBAAsB,OAAO,CAC3B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAuCf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../../../src/utils/encryption.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"encryption.d.ts","sourceRoot":"","sources":["../../../../src/utils/encryption.ts"],"names":[],"mappings":"AAoCA,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgB3E;AAED,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CAkBjB;AAKD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMnD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAOrD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savers_app/react-native-sdk",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Cross-platform React Native SDK exposing native features (maps, dial pad, browser), device ID/location and session utilities, a URL generator, and a WebView message bridge to trigger actions from web content.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"react-native": "./src/index.tsx",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"eslint-plugin-prettier": "^5.5.4",
|
|
80
80
|
"jest": "^29.7.0",
|
|
81
81
|
"lefthook": "^2.0.3",
|
|
82
|
-
"prettier": "^
|
|
82
|
+
"prettier": "^3.8.1",
|
|
83
83
|
"react": "19.1.0",
|
|
84
84
|
"react-native": "0.81.5",
|
|
85
85
|
"react-native-builder-bob": "^0.40.13",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Linking } from 'react-native';
|
|
3
3
|
|
|
4
4
|
// Utilities / Helpers
|
|
5
|
-
import {
|
|
5
|
+
import { isValidCoordinates } from '../../utils/validator';
|
|
6
6
|
|
|
7
7
|
// Platform / Environment helpers
|
|
8
8
|
import { isIOS } from '../../utils/platformManager';
|
|
@@ -17,23 +17,37 @@ export async function openMap(
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const iosUrl = `maps:0,0?q=${encodeURIComponent(label ?? '')}@${lat},${lng}`;
|
|
20
|
-
const androidUrl = `geo:0,0?q=${lat},${lng}${
|
|
21
|
-
label ? `(${encodeURIComponent(label)})` : ''
|
|
22
|
-
}`;
|
|
23
|
-
const deepLink = isIOS() ? iosUrl : androidUrl;
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
if (isIOS()) {
|
|
22
|
+
try {
|
|
23
|
+
await Linking.openURL(iosUrl);
|
|
24
|
+
return;
|
|
25
|
+
} catch {}
|
|
26
|
+
const browserUrl = `https://maps.apple.com/?q=${encodeURIComponent(
|
|
27
|
+
label ?? ''
|
|
28
|
+
)}&ll=${lat},${lng}`;
|
|
29
|
+
await Linking.openURL(browserUrl);
|
|
28
30
|
return;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
const encLabel = label ? encodeURIComponent(label) : undefined;
|
|
34
|
+
const query = `${lat},${lng}${encLabel ? `(${encLabel})` : ''}`;
|
|
35
|
+
const candidates = [
|
|
36
|
+
// Prefer geo to show a pin at the destination (not navigation)
|
|
37
|
+
`geo:${lat},${lng}?q=${query}`,
|
|
38
|
+
`geo:0,0?q=${query}`,
|
|
39
|
+
// Fallback to Google Maps custom scheme as a generic search/pin
|
|
40
|
+
`comgooglemaps://?q=${query}`,
|
|
41
|
+
];
|
|
42
|
+
for (const url of candidates) {
|
|
43
|
+
try {
|
|
44
|
+
await Linking.openURL(url);
|
|
45
|
+
return;
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
const browserQuery = label ? `${label} ${lat},${lng}` : `${lat},${lng}`;
|
|
49
|
+
const browserUrl = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
|
|
50
|
+
browserQuery
|
|
51
|
+
)}`;
|
|
38
52
|
await Linking.openURL(browserUrl);
|
|
39
53
|
}
|
package/src/utils/encryption.ts
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
import { NativeModules } from 'react-native';
|
|
2
2
|
import AesGcmCrypto from 'react-native-aes-gcm-crypto';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Ensure native module is installed & linked
|
|
6
|
+
*/
|
|
7
|
+
function ensureAesAvailable() {
|
|
8
|
+
const nativeModule =
|
|
9
|
+
NativeModules.AesGcmCrypto || NativeModules.RNAesGcmCrypto;
|
|
10
|
+
|
|
11
|
+
if (!AesGcmCrypto || !nativeModule) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
'react-native-aes-gcm-crypto is not installed or linked correctly.\n\n' +
|
|
14
|
+
'Fix:\n' +
|
|
15
|
+
'1. yarn add react-native-aes-gcm-crypto\n' +
|
|
16
|
+
'2. cd ios && pod install\n' +
|
|
17
|
+
'3. Rebuild the app\n'
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (typeof AesGcmCrypto.encrypt !== 'function') {
|
|
22
|
+
throw new Error(
|
|
23
|
+
'AES-GCM native functions unavailable. Please rebuild the app.'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
4
28
|
function isBase64(input: string): boolean {
|
|
5
29
|
return /^[A-Za-z0-9+/]+={0,2}$/.test(input);
|
|
6
30
|
}
|
|
@@ -11,30 +35,44 @@ function base64ByteLength(b64: string): number {
|
|
|
11
35
|
}
|
|
12
36
|
|
|
13
37
|
export async function aesEncrypt(text: string, key: string): Promise<string> {
|
|
38
|
+
ensureAesAvailable();
|
|
39
|
+
|
|
14
40
|
if (!isBase64(key) || base64ByteLength(key) !== 32) {
|
|
15
41
|
throw new Error(
|
|
16
42
|
'Encryption key must be base64-encoded 32-byte (256-bit) value'
|
|
17
43
|
);
|
|
18
44
|
}
|
|
19
|
-
|
|
20
|
-
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const { iv, tag, content } = await AesGcmCrypto.encrypt(text, false, key);
|
|
48
|
+
|
|
49
|
+
return base64Encrypt(`${iv}:${content}:${tag}`);
|
|
50
|
+
} catch (err: any) {
|
|
51
|
+
throw new Error(`AES encryption failed: ${err?.message || err}`);
|
|
52
|
+
}
|
|
21
53
|
}
|
|
22
54
|
|
|
23
55
|
export async function aesDecrypt(
|
|
24
56
|
ciphertext: string,
|
|
25
57
|
key: string
|
|
26
58
|
): Promise<string> {
|
|
59
|
+
ensureAesAvailable();
|
|
60
|
+
|
|
27
61
|
if (!isBase64(key) || base64ByteLength(key) !== 32) {
|
|
28
62
|
throw new Error(
|
|
29
63
|
'Encryption key must be base64-encoded 32-byte (256-bit) value'
|
|
30
64
|
);
|
|
31
65
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const [iv = '', content = '', tag = ''] = ciphertext.split(':');
|
|
69
|
+
|
|
70
|
+
const decrypted = await AesGcmCrypto.decrypt(content, key, iv, tag, false);
|
|
71
|
+
|
|
72
|
+
return decrypted;
|
|
73
|
+
} catch (err: any) {
|
|
74
|
+
throw new Error(`AES decryption failed: ${err?.message || err}`);
|
|
75
|
+
}
|
|
38
76
|
}
|
|
39
77
|
|
|
40
78
|
declare function btoa(data: string): string;
|