@evolu/react-native 12.0.1-preview.5 → 12.0.1-preview.7
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/components/EvoluAvatar.d.ts +8 -0
- package/dist/components/EvoluAvatar.d.ts.map +1 -0
- package/dist/components/EvoluAvatar.js +14 -0
- package/dist/expo-sqlite.d.ts +2 -0
- package/dist/expo-sqlite.d.ts.map +1 -1
- package/dist/expo-sqlite.js +6 -6
- package/dist/op-sqlite.d.ts +2 -0
- package/dist/op-sqlite.d.ts.map +1 -1
- package/dist/op-sqlite.js +5 -5
- package/dist/utils/LocalAuth.d.ts +3 -0
- package/dist/utils/LocalAuth.d.ts.map +1 -0
- package/dist/utils/LocalAuth.js +110 -0
- package/package.json +21 -8
- package/src/components/EvoluAvatar.tsx +25 -0
- package/src/expo-sqlite.ts +8 -6
- package/src/op-sqlite.ts +7 -5
- package/src/utils/LocalAuth.ts +143 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EvoluAvatar.d.ts","sourceRoot":"","sources":["../../src/components/EvoluAvatar.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,EAAE,EAAW,MAAM,OAAO,CAAC;AAEpC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAcA,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { SvgXml } from "react-native-svg";
|
|
4
|
+
import { useMemo } from "react";
|
|
5
|
+
import { bloSvg } from "blo";
|
|
6
|
+
export const EvoluAvatar = ({ id, size = 32, borderRadius = 3 }) => {
|
|
7
|
+
const svg = useMemo(() => bloSvg(`0x${id}`), [id]);
|
|
8
|
+
return id ? (_jsx(View, { style: {
|
|
9
|
+
width: size,
|
|
10
|
+
height: size,
|
|
11
|
+
borderRadius,
|
|
12
|
+
overflow: "hidden",
|
|
13
|
+
}, children: _jsx(SvgXml, { xml: svg, width: size, height: size }) })) : null;
|
|
14
|
+
};
|
package/dist/expo-sqlite.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expo-sqlite.d.ts","sourceRoot":"","sources":["../src/expo-sqlite.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,SAAS,EAEV,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"expo-sqlite.d.ts","sourceRoot":"","sources":["../src/expo-sqlite.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,SAAS,EAEV,MAAM,qBAAqB,CAAC;AA2B7B,cAAc,6BAA6B,CAAC;AAE5C,eAAO,MAAM,SAAS,mCAGpB,CAAC;AAEH,eAAO,MAAM,oBAAoB,EAAE,SAMlC,CAAC"}
|
package/dist/expo-sqlite.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { createConsole, createLocalAuth, createRandom, createRandomBytes, createTime, createWebSocket, } from "@evolu/common";
|
|
2
2
|
import { createDbWorkerForPlatform, } from "@evolu/common/evolu";
|
|
3
3
|
import * as Expo from "expo";
|
|
4
|
-
import { SensitiveInfo } from "react-native-sensitive-info";
|
|
5
4
|
import { createExpoSqliteDriver } from "./providers/ExpoSqliteDriver.js";
|
|
5
|
+
import { createSecureStore } from "./utils/LocalAuth.js";
|
|
6
6
|
import { polyfillHermes } from "./utils/Hermes.js";
|
|
7
7
|
polyfillHermes();
|
|
8
8
|
const console = createConsole();
|
|
9
9
|
const time = createTime();
|
|
10
10
|
const randomBytes = createRandomBytes();
|
|
11
|
-
const localAuth = createLocalAuth({
|
|
12
|
-
randomBytes: randomBytes,
|
|
13
|
-
secureStorage: SensitiveInfo,
|
|
14
|
-
});
|
|
15
11
|
const createDbWorker = () => createDbWorkerForPlatform({
|
|
16
12
|
console,
|
|
17
13
|
createSqliteDriver: createExpoSqliteDriver,
|
|
@@ -23,11 +19,15 @@ const createDbWorker = () => createDbWorkerForPlatform({
|
|
|
23
19
|
const reloadApp = () => {
|
|
24
20
|
void Expo.reloadAppAsync();
|
|
25
21
|
};
|
|
22
|
+
export * from "./components/EvoluAvatar.js";
|
|
23
|
+
export const localAuth = createLocalAuth({
|
|
24
|
+
randomBytes: randomBytes,
|
|
25
|
+
secureStorage: createSecureStore(),
|
|
26
|
+
});
|
|
26
27
|
export const evoluReactNativeDeps = {
|
|
27
28
|
console,
|
|
28
29
|
createDbWorker,
|
|
29
30
|
randomBytes,
|
|
30
|
-
localAuth,
|
|
31
31
|
reloadApp,
|
|
32
32
|
time,
|
|
33
33
|
};
|
package/dist/op-sqlite.d.ts
CHANGED
package/dist/op-sqlite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"op-sqlite.d.ts","sourceRoot":"","sources":["../src/op-sqlite.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,SAAS,EAEV,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"op-sqlite.d.ts","sourceRoot":"","sources":["../src/op-sqlite.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,SAAS,EAEV,MAAM,qBAAqB,CAAC;AA+B7B,cAAc,6BAA6B,CAAC;AAE5C,eAAO,MAAM,SAAS,mCAGpB,CAAC;AAEH,eAAO,MAAM,oBAAoB,EAAE,SAMlC,CAAC"}
|
package/dist/op-sqlite.js
CHANGED
|
@@ -8,10 +8,6 @@ polyfillHermes();
|
|
|
8
8
|
const console = createConsole();
|
|
9
9
|
const time = createTime();
|
|
10
10
|
const randomBytes = createRandomBytes();
|
|
11
|
-
const localAuth = createLocalAuth({
|
|
12
|
-
randomBytes: randomBytes,
|
|
13
|
-
secureStorage: SensitiveInfo,
|
|
14
|
-
});
|
|
15
11
|
const createDbWorker = () => createDbWorkerForPlatform({
|
|
16
12
|
console,
|
|
17
13
|
createSqliteDriver: createOpSqliteDriver,
|
|
@@ -28,11 +24,15 @@ const reloadApp = () => {
|
|
|
28
24
|
// TODO: reload not implemented for bare rn
|
|
29
25
|
}
|
|
30
26
|
};
|
|
27
|
+
export * from "./components/EvoluAvatar.js";
|
|
28
|
+
export const localAuth = createLocalAuth({
|
|
29
|
+
randomBytes: randomBytes,
|
|
30
|
+
secureStorage: SensitiveInfo,
|
|
31
|
+
});
|
|
31
32
|
export const evoluReactNativeDeps = {
|
|
32
33
|
console,
|
|
33
34
|
createDbWorker,
|
|
34
35
|
randomBytes,
|
|
35
|
-
localAuth,
|
|
36
36
|
reloadApp,
|
|
37
37
|
time,
|
|
38
38
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalAuth.d.ts","sourceRoot":"","sources":["../../src/utils/LocalAuth.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,aAAa,EAId,MAAM,eAAe,CAAC;AAEvB,eAAO,MAAM,iBAAiB,QAAO,aAqEpC,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import KVStore from "expo-sqlite/kv-store";
|
|
2
|
+
import * as SecureStore from "expo-secure-store";
|
|
3
|
+
import { AUTH_DEFAULT_OPTIONS } from "@evolu/common";
|
|
4
|
+
export const createSecureStore = () => {
|
|
5
|
+
const store = {
|
|
6
|
+
setItem: async (key, value, options) => {
|
|
7
|
+
const rnsiOpts = convertOptions(options);
|
|
8
|
+
const service = options?.service ?? "default";
|
|
9
|
+
const metadata = createMetadata(options?.accessControl === "none");
|
|
10
|
+
await KVStore.setItem(`${service}-${key}`, "1");
|
|
11
|
+
await SecureStore.setItemAsync(key, JSON.stringify({ value, metadata }), rnsiOpts);
|
|
12
|
+
return { metadata };
|
|
13
|
+
},
|
|
14
|
+
getItem: async (key, options) => {
|
|
15
|
+
const rnsiOpts = convertOptions(options);
|
|
16
|
+
const service = options?.service ?? "default";
|
|
17
|
+
let data;
|
|
18
|
+
try {
|
|
19
|
+
const result = await SecureStore.getItemAsync(key, rnsiOpts);
|
|
20
|
+
if (!result)
|
|
21
|
+
return null;
|
|
22
|
+
data = JSON.parse(result);
|
|
23
|
+
}
|
|
24
|
+
catch (_error) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return { key, service, ...data };
|
|
28
|
+
},
|
|
29
|
+
deleteItem: async (key, options) => {
|
|
30
|
+
const rnsiOpts = convertOptions(options);
|
|
31
|
+
const service = options?.service ?? "default";
|
|
32
|
+
await Promise.all([
|
|
33
|
+
KVStore.removeItemAsync(`${service}-${key}`),
|
|
34
|
+
SecureStore.deleteItemAsync(key, rnsiOpts),
|
|
35
|
+
]);
|
|
36
|
+
return true;
|
|
37
|
+
},
|
|
38
|
+
getAllItems: async (options) => {
|
|
39
|
+
const keys = await KVStore.getAllKeysAsync();
|
|
40
|
+
const service = options?.service ?? "default";
|
|
41
|
+
const metadata = createMetadata(options?.accessControl === "none");
|
|
42
|
+
return keys
|
|
43
|
+
.filter((key) => key.startsWith(`${service}-`))
|
|
44
|
+
.map((key) => ({
|
|
45
|
+
key: key.slice(service.length + 1),
|
|
46
|
+
service,
|
|
47
|
+
metadata,
|
|
48
|
+
}));
|
|
49
|
+
},
|
|
50
|
+
clearService: async (options) => {
|
|
51
|
+
const rnsiOpts = convertOptions(options);
|
|
52
|
+
const service = options?.service ?? "default";
|
|
53
|
+
const items = await store.getAllItems(options);
|
|
54
|
+
await KVStore.multiRemove(items.map((item) => `${service}-${item.key}`));
|
|
55
|
+
await Promise.all(items.map(async (item) => {
|
|
56
|
+
await SecureStore.deleteItemAsync(item.key, rnsiOpts);
|
|
57
|
+
}));
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
return store;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Create default metadata for backwards compatibility with items that don't
|
|
64
|
+
* have stored metadata.
|
|
65
|
+
*/
|
|
66
|
+
const createMetadata = (isSecure = true) => {
|
|
67
|
+
return {
|
|
68
|
+
backend: "keychain",
|
|
69
|
+
accessControl: isSecure ? "biometryCurrentSet" : "none",
|
|
70
|
+
securityLevel: isSecure ? "biometry" : "software",
|
|
71
|
+
timestamp: Date.now(),
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
function convertOptions(options) {
|
|
75
|
+
const accessGroup = options?.keychainGroup ?? AUTH_DEFAULT_OPTIONS.keychainGroup ?? "";
|
|
76
|
+
const keychainService = options?.service ?? AUTH_DEFAULT_OPTIONS.service ?? "";
|
|
77
|
+
const keychainAccessible = convertKeychainAccessible(options?.accessControl ??
|
|
78
|
+
AUTH_DEFAULT_OPTIONS.accessControl ??
|
|
79
|
+
"biometryCurrentSet");
|
|
80
|
+
const authenticationPrompt = options?.authenticationPrompt?.title ??
|
|
81
|
+
AUTH_DEFAULT_OPTIONS.authenticationPrompt?.title ??
|
|
82
|
+
"";
|
|
83
|
+
return {
|
|
84
|
+
accessGroup,
|
|
85
|
+
keychainService,
|
|
86
|
+
keychainAccessible,
|
|
87
|
+
authenticationPrompt,
|
|
88
|
+
requireAuthentication: options?.accessControl !== "none",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function convertKeychainAccessible(accessControl) {
|
|
92
|
+
switch (accessControl) {
|
|
93
|
+
case "none":
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
95
|
+
return SecureStore.ALWAYS;
|
|
96
|
+
case "biometryCurrentSet":
|
|
97
|
+
return SecureStore.AFTER_FIRST_UNLOCK;
|
|
98
|
+
case "biometryAny":
|
|
99
|
+
return SecureStore.AFTER_FIRST_UNLOCK;
|
|
100
|
+
case "devicePasscode":
|
|
101
|
+
return SecureStore.AFTER_FIRST_UNLOCK;
|
|
102
|
+
case "secureEnclaveBiometry":
|
|
103
|
+
return SecureStore.AFTER_FIRST_UNLOCK;
|
|
104
|
+
// Exhaustive check
|
|
105
|
+
default:
|
|
106
|
+
accessControl;
|
|
107
|
+
// Default (for typescript, should never hit)
|
|
108
|
+
return SecureStore.AFTER_FIRST_UNLOCK;
|
|
109
|
+
}
|
|
110
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evolu/react-native",
|
|
3
|
-
"version": "12.0.1-preview.
|
|
3
|
+
"version": "12.0.1-preview.7",
|
|
4
4
|
"description": "Evolu for React Native and Expo",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"evolu",
|
|
@@ -47,29 +47,36 @@
|
|
|
47
47
|
"src/**",
|
|
48
48
|
"README.md"
|
|
49
49
|
],
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"blo": "^2.0.0"
|
|
52
|
+
},
|
|
50
53
|
"devDependencies": {
|
|
51
54
|
"@op-engineering/op-sqlite": "^15.0.3",
|
|
52
55
|
"expo": "^54.0.10",
|
|
56
|
+
"expo-secure-store": "~15.0.7",
|
|
53
57
|
"expo-sqlite": "~16.0.8",
|
|
54
58
|
"react-native": "^0.81.4",
|
|
55
59
|
"react-native-nitro-modules": "^0.31.2",
|
|
56
|
-
"react-native-sensitive-info": "^6.0.0-rc.
|
|
60
|
+
"react-native-sensitive-info": "^6.0.0-rc.8",
|
|
61
|
+
"react-native-svg": "^15.14.0",
|
|
57
62
|
"shx": "^0.4.0",
|
|
58
63
|
"typescript": "^5.9.2",
|
|
59
64
|
"vitest": "^4.0.4",
|
|
60
|
-
"@evolu/common": "6.0.1-preview.
|
|
61
|
-
"@evolu/react": "9.0.1-preview.
|
|
65
|
+
"@evolu/common": "6.0.1-preview.25",
|
|
66
|
+
"@evolu/react": "9.0.1-preview.6",
|
|
62
67
|
"@evolu/tsconfig": "0.0.2"
|
|
63
68
|
},
|
|
64
69
|
"peerDependencies": {
|
|
65
|
-
"@evolu/common": "^6.0.1-preview.
|
|
66
|
-
"@evolu/react": "^9.0.1-preview.
|
|
70
|
+
"@evolu/common": "^6.0.1-preview.25",
|
|
71
|
+
"@evolu/react": "^9.0.1-preview.6",
|
|
67
72
|
"@op-engineering/op-sqlite": ">=12",
|
|
68
73
|
"expo": ">=54",
|
|
69
|
-
"expo-
|
|
74
|
+
"expo-secure-store": ">=15",
|
|
75
|
+
"expo-sqlite": ">=16",
|
|
70
76
|
"react-native": ">=0.81",
|
|
71
77
|
"react-native-nitro-modules": ">=0.31",
|
|
72
|
-
"react-native-sensitive-info": ">=6"
|
|
78
|
+
"react-native-sensitive-info": ">=6",
|
|
79
|
+
"react-native-svg": ">=15"
|
|
73
80
|
},
|
|
74
81
|
"peerDependenciesMeta": {
|
|
75
82
|
"@op-engineering/op-sqlite": {
|
|
@@ -81,11 +88,17 @@
|
|
|
81
88
|
"expo-sqlite": {
|
|
82
89
|
"optional": true
|
|
83
90
|
},
|
|
91
|
+
"expo-secure-store": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
84
94
|
"react-native-nitro-modules": {
|
|
85
95
|
"optional": true
|
|
86
96
|
},
|
|
87
97
|
"react-native-sensitive-info": {
|
|
88
98
|
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"react-native-svg": {
|
|
101
|
+
"optional": true
|
|
89
102
|
}
|
|
90
103
|
},
|
|
91
104
|
"publishConfig": {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { View } from "react-native";
|
|
2
|
+
import { SvgXml } from "react-native-svg";
|
|
3
|
+
import { FC, useMemo } from "react";
|
|
4
|
+
import { bloSvg } from "blo";
|
|
5
|
+
import { OwnerId } from "@evolu/common";
|
|
6
|
+
|
|
7
|
+
export const EvoluAvatar: FC<{
|
|
8
|
+
id: OwnerId;
|
|
9
|
+
size?: number;
|
|
10
|
+
borderRadius?: number;
|
|
11
|
+
}> = ({ id, size = 32, borderRadius = 3 }) => {
|
|
12
|
+
const svg = useMemo(() => bloSvg(`0x${id}`), [id]);
|
|
13
|
+
return id ? (
|
|
14
|
+
<View
|
|
15
|
+
style={{
|
|
16
|
+
width: size,
|
|
17
|
+
height: size,
|
|
18
|
+
borderRadius,
|
|
19
|
+
overflow: "hidden",
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
22
|
+
<SvgXml xml={svg} width={size} height={size} />
|
|
23
|
+
</View>
|
|
24
|
+
) : null;
|
|
25
|
+
};
|
package/src/expo-sqlite.ts
CHANGED
|
@@ -15,8 +15,8 @@ import {
|
|
|
15
15
|
} from "@evolu/common/evolu";
|
|
16
16
|
|
|
17
17
|
import * as Expo from "expo";
|
|
18
|
-
import { SensitiveInfo } from "react-native-sensitive-info";
|
|
19
18
|
import { createExpoSqliteDriver } from "./providers/ExpoSqliteDriver.js";
|
|
19
|
+
import { createSecureStore } from "./utils/LocalAuth.js";
|
|
20
20
|
import { polyfillHermes } from "./utils/Hermes.js";
|
|
21
21
|
|
|
22
22
|
polyfillHermes();
|
|
@@ -24,10 +24,6 @@ polyfillHermes();
|
|
|
24
24
|
const console = createConsole();
|
|
25
25
|
const time = createTime();
|
|
26
26
|
const randomBytes = createRandomBytes();
|
|
27
|
-
const localAuth = createLocalAuth({
|
|
28
|
-
randomBytes: randomBytes,
|
|
29
|
-
secureStorage: SensitiveInfo,
|
|
30
|
-
});
|
|
31
27
|
|
|
32
28
|
const createDbWorker: CreateDbWorker = () =>
|
|
33
29
|
createDbWorkerForPlatform({
|
|
@@ -43,11 +39,17 @@ const reloadApp: ReloadApp = () => {
|
|
|
43
39
|
void Expo.reloadAppAsync();
|
|
44
40
|
};
|
|
45
41
|
|
|
42
|
+
export * from "./components/EvoluAvatar.js";
|
|
43
|
+
|
|
44
|
+
export const localAuth = createLocalAuth({
|
|
45
|
+
randomBytes: randomBytes,
|
|
46
|
+
secureStorage: createSecureStore(),
|
|
47
|
+
});
|
|
48
|
+
|
|
46
49
|
export const evoluReactNativeDeps: EvoluDeps = {
|
|
47
50
|
console,
|
|
48
51
|
createDbWorker,
|
|
49
52
|
randomBytes,
|
|
50
|
-
localAuth,
|
|
51
53
|
reloadApp,
|
|
52
54
|
time,
|
|
53
55
|
};
|
package/src/op-sqlite.ts
CHANGED
|
@@ -24,10 +24,6 @@ polyfillHermes();
|
|
|
24
24
|
const console = createConsole();
|
|
25
25
|
const time = createTime();
|
|
26
26
|
const randomBytes = createRandomBytes();
|
|
27
|
-
const localAuth = createLocalAuth({
|
|
28
|
-
randomBytes: randomBytes,
|
|
29
|
-
secureStorage: SensitiveInfo,
|
|
30
|
-
});
|
|
31
27
|
|
|
32
28
|
const createDbWorker: CreateDbWorker = () =>
|
|
33
29
|
createDbWorkerForPlatform({
|
|
@@ -47,11 +43,17 @@ const reloadApp: ReloadApp = () => {
|
|
|
47
43
|
}
|
|
48
44
|
};
|
|
49
45
|
|
|
46
|
+
export * from "./components/EvoluAvatar.js";
|
|
47
|
+
|
|
48
|
+
export const localAuth = createLocalAuth({
|
|
49
|
+
randomBytes: randomBytes,
|
|
50
|
+
secureStorage: SensitiveInfo,
|
|
51
|
+
});
|
|
52
|
+
|
|
50
53
|
export const evoluReactNativeDeps: EvoluDeps = {
|
|
51
54
|
console,
|
|
52
55
|
createDbWorker,
|
|
53
56
|
randomBytes,
|
|
54
|
-
localAuth,
|
|
55
57
|
reloadApp,
|
|
56
58
|
time,
|
|
57
59
|
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import KVStore from "expo-sqlite/kv-store";
|
|
2
|
+
import * as SecureStore from "expo-secure-store";
|
|
3
|
+
import { AUTH_DEFAULT_OPTIONS } from "@evolu/common";
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
LocalAuthOptions,
|
|
7
|
+
SecureStorage,
|
|
8
|
+
SensitiveInfoItem,
|
|
9
|
+
StorageMetadata,
|
|
10
|
+
AccessControl,
|
|
11
|
+
} from "@evolu/common";
|
|
12
|
+
|
|
13
|
+
export const createSecureStore = (): SecureStorage => {
|
|
14
|
+
const store: SecureStorage = {
|
|
15
|
+
setItem: async (key, value, options) => {
|
|
16
|
+
const rnsiOpts = convertOptions(options);
|
|
17
|
+
const service = options?.service ?? "default";
|
|
18
|
+
const metadata = createMetadata(options?.accessControl === "none");
|
|
19
|
+
await KVStore.setItem(`${service}-${key}`, "1");
|
|
20
|
+
await SecureStore.setItemAsync(
|
|
21
|
+
key,
|
|
22
|
+
JSON.stringify({ value, metadata }),
|
|
23
|
+
rnsiOpts,
|
|
24
|
+
);
|
|
25
|
+
return { metadata };
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
getItem: async (key, options) => {
|
|
29
|
+
const rnsiOpts = convertOptions(options);
|
|
30
|
+
const service = options?.service ?? "default";
|
|
31
|
+
let data: { value: string; metadata: StorageMetadata };
|
|
32
|
+
try {
|
|
33
|
+
const result = await SecureStore.getItemAsync(key, rnsiOpts);
|
|
34
|
+
if (!result) return null;
|
|
35
|
+
data = JSON.parse(result) as {
|
|
36
|
+
value: string;
|
|
37
|
+
metadata: StorageMetadata;
|
|
38
|
+
};
|
|
39
|
+
} catch (_error) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return { key, service, ...data };
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
deleteItem: async (key, options) => {
|
|
46
|
+
const rnsiOpts = convertOptions(options);
|
|
47
|
+
const service = options?.service ?? "default";
|
|
48
|
+
await Promise.all([
|
|
49
|
+
KVStore.removeItemAsync(`${service}-${key}`),
|
|
50
|
+
SecureStore.deleteItemAsync(key, rnsiOpts),
|
|
51
|
+
]);
|
|
52
|
+
return true;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
getAllItems: async (options) => {
|
|
56
|
+
const keys = await KVStore.getAllKeysAsync();
|
|
57
|
+
const service = options?.service ?? "default";
|
|
58
|
+
const metadata = createMetadata(options?.accessControl === "none");
|
|
59
|
+
return keys
|
|
60
|
+
.filter((key) => key.startsWith(`${service}-`))
|
|
61
|
+
.map((key) => ({
|
|
62
|
+
key: key.slice(service.length + 1),
|
|
63
|
+
service,
|
|
64
|
+
metadata,
|
|
65
|
+
}));
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
clearService: async (options) => {
|
|
69
|
+
const rnsiOpts = convertOptions(options);
|
|
70
|
+
const service = options?.service ?? "default";
|
|
71
|
+
const items = await store.getAllItems(options);
|
|
72
|
+
await KVStore.multiRemove(items.map((item) => `${service}-${item.key}`));
|
|
73
|
+
await Promise.all(
|
|
74
|
+
items.map(async (item) => {
|
|
75
|
+
await SecureStore.deleteItemAsync(item.key, rnsiOpts);
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return store;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Create default metadata for backwards compatibility with items that don't
|
|
86
|
+
* have stored metadata.
|
|
87
|
+
*/
|
|
88
|
+
const createMetadata = (isSecure = true): SensitiveInfoItem["metadata"] => {
|
|
89
|
+
return {
|
|
90
|
+
backend: "keychain",
|
|
91
|
+
accessControl: isSecure ? "biometryCurrentSet" : "none",
|
|
92
|
+
securityLevel: isSecure ? "biometry" : "software",
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
function convertOptions(
|
|
98
|
+
options?: LocalAuthOptions,
|
|
99
|
+
): SecureStore.SecureStoreOptions {
|
|
100
|
+
const accessGroup =
|
|
101
|
+
options?.keychainGroup ?? AUTH_DEFAULT_OPTIONS.keychainGroup ?? "";
|
|
102
|
+
const keychainService =
|
|
103
|
+
options?.service ?? AUTH_DEFAULT_OPTIONS.service ?? "";
|
|
104
|
+
const keychainAccessible = convertKeychainAccessible(
|
|
105
|
+
options?.accessControl ??
|
|
106
|
+
AUTH_DEFAULT_OPTIONS.accessControl ??
|
|
107
|
+
"biometryCurrentSet",
|
|
108
|
+
);
|
|
109
|
+
const authenticationPrompt =
|
|
110
|
+
options?.authenticationPrompt?.title ??
|
|
111
|
+
AUTH_DEFAULT_OPTIONS.authenticationPrompt?.title ??
|
|
112
|
+
"";
|
|
113
|
+
return {
|
|
114
|
+
accessGroup,
|
|
115
|
+
keychainService,
|
|
116
|
+
keychainAccessible,
|
|
117
|
+
authenticationPrompt,
|
|
118
|
+
requireAuthentication: options?.accessControl !== "none",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function convertKeychainAccessible(
|
|
123
|
+
accessControl: AccessControl,
|
|
124
|
+
): SecureStore.KeychainAccessibilityConstant {
|
|
125
|
+
switch (accessControl) {
|
|
126
|
+
case "none":
|
|
127
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
128
|
+
return SecureStore.ALWAYS;
|
|
129
|
+
case "biometryCurrentSet":
|
|
130
|
+
return SecureStore.AFTER_FIRST_UNLOCK;
|
|
131
|
+
case "biometryAny":
|
|
132
|
+
return SecureStore.AFTER_FIRST_UNLOCK;
|
|
133
|
+
case "devicePasscode":
|
|
134
|
+
return SecureStore.AFTER_FIRST_UNLOCK;
|
|
135
|
+
case "secureEnclaveBiometry":
|
|
136
|
+
return SecureStore.AFTER_FIRST_UNLOCK;
|
|
137
|
+
// Exhaustive check
|
|
138
|
+
default:
|
|
139
|
+
accessControl satisfies never;
|
|
140
|
+
// Default (for typescript, should never hit)
|
|
141
|
+
return SecureStore.AFTER_FIRST_UNLOCK;
|
|
142
|
+
}
|
|
143
|
+
}
|