@evolu/react-native 12.0.1-preview.4 → 12.0.1-preview.6

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.
@@ -0,0 +1,8 @@
1
+ import { FC } from 'react';
2
+ import { OwnerId } from '@evolu/common';
3
+ export declare const EvoluAvatar: FC<{
4
+ id: OwnerId;
5
+ size?: number;
6
+ borderRadius?: number;
7
+ }>;
8
+ //# sourceMappingURL=EvoluAvatar.d.ts.map
@@ -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,CAgBA,CAAA"}
@@ -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
+ };
@@ -1,4 +1,5 @@
1
- import { CreateAppState, EvoluDeps } from "@evolu/common/evolu";
2
- export declare const createAppState: CreateAppState;
1
+ import { EvoluDeps } from "@evolu/common/evolu";
2
+ export * from "./components/EvoluAvatar.js";
3
+ export declare const localAuth: import("@evolu/common").LocalAuth;
3
4
  export declare const evoluReactNativeDeps: EvoluDeps;
4
5
  //# sourceMappingURL=expo-sqlite.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"expo-sqlite.d.ts","sourceRoot":"","sources":["../src/expo-sqlite.ts"],"names":[],"mappings":"AASA,OAAO,EACL,cAAc,EAId,SAAS,EACV,MAAM,qBAAqB,CAAC;AAS7B,eAAO,MAAM,cAAc,EAAE,cAI3B,CAAC;AAkBH,eAAO,MAAM,oBAAoB,EAAE,SAMlC,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"}
@@ -1,31 +1,33 @@
1
- import { createConsole, createEnglishMnemonic, createNanoIdLib, createRandom, createRandomBytes, createTime, } from "@evolu/common";
2
- import { createDbWorkerForPlatform, createWebSocketSync, } from "@evolu/common/evolu";
1
+ import { createConsole, createLocalAuth, createRandom, createRandomBytes, createTime, createWebSocket, } from "@evolu/common";
2
+ import { createDbWorkerForPlatform, } from "@evolu/common/evolu";
3
3
  import * as Expo from "expo";
4
4
  import { createExpoSqliteDriver } from "./providers/ExpoSqliteDriver.js";
5
+ import { createSecureStore } from "./utils/LocalAuth.js";
5
6
  import { polyfillHermes } from "./utils/Hermes.js";
6
7
  polyfillHermes();
7
- export const createAppState = () => ({
8
- reset: () => {
9
- void Expo.reloadAppAsync();
10
- },
11
- });
12
- const nanoIdLib = createNanoIdLib();
13
8
  const console = createConsole();
14
9
  const time = createTime();
10
+ const randomBytes = createRandomBytes();
15
11
  const createDbWorker = () => createDbWorkerForPlatform({
16
- createSqliteDriver: createExpoSqliteDriver,
17
- createSync: createWebSocketSync,
18
12
  console,
19
- time,
13
+ createSqliteDriver: createExpoSqliteDriver,
14
+ createWebSocket,
20
15
  random: createRandom(),
21
- nanoIdLib,
22
- createMnemonic: createEnglishMnemonic,
23
- createRandomBytes,
16
+ randomBytes,
17
+ time,
18
+ });
19
+ const reloadApp = () => {
20
+ void Expo.reloadAppAsync();
21
+ };
22
+ export * from "./components/EvoluAvatar.js";
23
+ export const localAuth = createLocalAuth({
24
+ randomBytes: randomBytes,
25
+ secureStorage: createSecureStore(),
24
26
  });
25
27
  export const evoluReactNativeDeps = {
26
- time,
27
- nanoIdLib,
28
28
  console,
29
- createAppState,
30
29
  createDbWorker,
30
+ randomBytes,
31
+ reloadApp,
32
+ time,
31
33
  };
@@ -1,4 +1,5 @@
1
- import { CreateAppState, EvoluDeps } from "@evolu/common/evolu";
2
- export declare const createAppState: CreateAppState;
1
+ import { EvoluDeps } from "@evolu/common/evolu";
2
+ export * from "./components/EvoluAvatar.js";
3
+ export declare const localAuth: import("@evolu/common").LocalAuth;
3
4
  export declare const evoluReactNativeDeps: EvoluDeps;
4
5
  //# sourceMappingURL=op-sqlite.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"op-sqlite.d.ts","sourceRoot":"","sources":["../src/op-sqlite.ts"],"names":[],"mappings":"AASA,OAAO,EACL,cAAc,EAId,SAAS,EACV,MAAM,qBAAqB,CAAC;AAQ7B,eAAO,MAAM,cAAc,EAAE,cAQ3B,CAAC;AAkBH,eAAO,MAAM,oBAAoB,EAAE,SAMlC,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
@@ -1,36 +1,38 @@
1
- import { createConsole, createEnglishMnemonic, createNanoIdLib, createRandom, createRandomBytes, createTime, } from "@evolu/common";
2
- import { createDbWorkerForPlatform, createWebSocketSync, } from "@evolu/common/evolu";
1
+ import { createConsole, createLocalAuth, createRandom, createRandomBytes, createTime, createWebSocket, } from "@evolu/common";
2
+ import { createDbWorkerForPlatform, } from "@evolu/common/evolu";
3
3
  import { DevSettings } from "react-native";
4
+ import { SensitiveInfo } from "react-native-sensitive-info";
4
5
  import { createOpSqliteDriver } from "./providers/OpSqliteDriver.js";
5
6
  import { polyfillHermes } from "./utils/Hermes.js";
6
7
  polyfillHermes();
7
- export const createAppState = () => ({
8
- reset: () => {
9
- if (process.env.NODE_ENV === "development") {
10
- DevSettings.reload();
11
- }
12
- else {
13
- // TODO: reload not implemented for bare rn
14
- }
15
- },
16
- });
17
- const nanoIdLib = createNanoIdLib();
18
8
  const console = createConsole();
19
9
  const time = createTime();
10
+ const randomBytes = createRandomBytes();
20
11
  const createDbWorker = () => createDbWorkerForPlatform({
21
- createSqliteDriver: createOpSqliteDriver,
22
- createSync: createWebSocketSync,
23
12
  console,
24
- time,
13
+ createSqliteDriver: createOpSqliteDriver,
14
+ createWebSocket,
25
15
  random: createRandom(),
26
- nanoIdLib,
27
- createMnemonic: createEnglishMnemonic,
28
- createRandomBytes,
16
+ randomBytes,
17
+ time,
18
+ });
19
+ const reloadApp = () => {
20
+ if (process.env.NODE_ENV === "development") {
21
+ DevSettings.reload();
22
+ }
23
+ else {
24
+ // TODO: reload not implemented for bare rn
25
+ }
26
+ };
27
+ export * from "./components/EvoluAvatar.js";
28
+ export const localAuth = createLocalAuth({
29
+ randomBytes: randomBytes,
30
+ secureStorage: SensitiveInfo,
29
31
  });
30
32
  export const evoluReactNativeDeps = {
31
- time,
32
- nanoIdLib,
33
33
  console,
34
- createAppState,
35
34
  createDbWorker,
35
+ randomBytes,
36
+ reloadApp,
37
+ time,
36
38
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoSqliteDriver.d.ts","sourceRoot":"","sources":["../../src/providers/ExpoSqliteDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,kBAAkB,EAGnB,MAAM,eAAe,CAAC;AAIvB,eAAO,MAAM,sBAAsB,EAAE,kBAiDpC,CAAC"}
1
+ {"version":3,"file":"ExpoSqliteDriver.d.ts","sourceRoot":"","sources":["../../src/providers/ExpoSqliteDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,kBAAkB,EAInB,MAAM,eAAe,CAAC;AAIvB,eAAO,MAAM,sBAAsB,EAAE,kBAwDpC,CAAC"}
@@ -1,7 +1,14 @@
1
- import { createPreparedStatementsCache, } from "@evolu/common";
1
+ import { createPreparedStatementsCache, bytesToHex, } from "@evolu/common";
2
2
  import { openDatabaseSync } from "expo-sqlite";
3
3
  export const createExpoSqliteDriver = (name, options) => {
4
4
  const db = openDatabaseSync(options?.memory ? ":memory:" : `evolu1-${name}.db`);
5
+ if (options?.encryptionKey) {
6
+ db.execSync(`
7
+ PRAGMA cipher = 'sqlcipher';
8
+ PRAGMA legacy = 4;
9
+ PRAGMA key = "x'${bytesToHex(options.encryptionKey)}'";
10
+ `);
11
+ }
5
12
  let isDisposed = false;
6
13
  const cache = createPreparedStatementsCache((sql) => db.prepareSync(sql), (statement) => {
7
14
  statement.finalizeSync();
@@ -1 +1 @@
1
- {"version":3,"file":"OpSqliteDriver.d.ts","sourceRoot":"","sources":["../../src/providers/OpSqliteDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,kBAAkB,EAGnB,MAAM,eAAe,CAAC;AAIvB,eAAO,MAAM,oBAAoB,EAAE,kBAyDlC,CAAC"}
1
+ {"version":3,"file":"OpSqliteDriver.d.ts","sourceRoot":"","sources":["../../src/providers/OpSqliteDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,kBAAkB,EAInB,MAAM,eAAe,CAAC;AAIvB,eAAO,MAAM,oBAAoB,EAAE,kBA8DlC,CAAC"}
@@ -1,10 +1,15 @@
1
- import { constVoid, createPreparedStatementsCache, isSqlMutation, } from "@evolu/common";
1
+ import { constVoid, createPreparedStatementsCache, isSqlMutation, bytesToHex, } from "@evolu/common";
2
2
  import { open } from "@op-engineering/op-sqlite";
3
3
  export const createOpSqliteDriver = (name, options) => {
4
4
  // https://op-engineering.github.io/op-sqlite/docs/configuration#in-memory
5
5
  const db = open(options?.memory
6
6
  ? { name: `inMemoryDb`, location: ":memory:" }
7
- : { name: `evolu1-${name}.db` });
7
+ : {
8
+ name: `evolu1-${name}.db`,
9
+ ...(options?.encryptionKey && {
10
+ encryptionKey: `x'${bytesToHex(options.encryptionKey)}'`,
11
+ }),
12
+ });
8
13
  let isDisposed = false;
9
14
  const cache = createPreparedStatementsCache((sql) => db.prepareStatement(sql),
10
15
  // op-sqlite doesn't have API for that
@@ -0,0 +1,3 @@
1
+ import type { SecureStorage } from "@evolu/common";
2
+ export declare const createSecureStore: () => SecureStorage;
3
+ //# sourceMappingURL=LocalAuth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalAuth.d.ts","sourceRoot":"","sources":["../../src/utils/LocalAuth.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAoB,aAAa,EAAqD,MAAM,eAAe,CAAC;AAExH,eAAO,MAAM,iBAAiB,QAAO,aAwDpC,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 => ({ key: key.slice(service.length + 1), service, metadata }));
45
+ },
46
+ clearService: async (options) => {
47
+ const rnsiOpts = convertOptions(options);
48
+ const service = options?.service ?? 'default';
49
+ const items = await store.getAllItems(options);
50
+ await KVStore.multiRemove(items.map(item => `${service}-${item.key}`));
51
+ await Promise.all(items.map(async (item) => {
52
+ await SecureStore.deleteItemAsync(item.key, rnsiOpts);
53
+ }));
54
+ },
55
+ };
56
+ return store;
57
+ };
58
+ /**
59
+ * Create default metadata for backwards compatibility with items that don't
60
+ * have stored metadata.
61
+ */
62
+ const createMetadata = (isSecure = true) => {
63
+ return {
64
+ backend: "keychain",
65
+ accessControl: isSecure ? "biometryCurrentSet" : "none",
66
+ securityLevel: isSecure ? "biometry" : "software",
67
+ timestamp: Date.now(),
68
+ };
69
+ };
70
+ function convertOptions(options) {
71
+ const accessGroup = options?.keychainGroup
72
+ ?? AUTH_DEFAULT_OPTIONS.keychainGroup
73
+ ?? '';
74
+ const keychainService = options?.service
75
+ ?? AUTH_DEFAULT_OPTIONS.service
76
+ ?? '';
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.4",
3
+ "version": "12.0.1-preview.6",
4
4
  "description": "Evolu for React Native and Expo",
5
5
  "keywords": [
6
6
  "evolu",
@@ -48,23 +48,32 @@
48
48
  "README.md"
49
49
  ],
50
50
  "devDependencies": {
51
- "@op-engineering/op-sqlite": "^14.1.4",
52
- "expo": "^53.0.20",
53
- "expo-sqlite": "~15.2.14",
54
- "react-native": "^0.79.3",
51
+ "@op-engineering/op-sqlite": "^15.0.3",
52
+ "expo": "^54.0.10",
53
+ "expo-secure-store": "~15.0.7",
54
+ "expo-sqlite": "~16.0.8",
55
+ "react-native": "^0.81.4",
56
+ "react-native-nitro-modules": "^0.31.2",
57
+ "react-native-sensitive-info": "^6.0.0-rc.8",
58
+ "react-native-svg": "^15.14.0",
59
+ "shx": "^0.4.0",
55
60
  "typescript": "^5.9.2",
56
- "vitest": "^3.2.3",
57
- "@evolu/common": "6.0.1-preview.18",
58
- "@evolu/react": "9.0.1-preview.4",
61
+ "vitest": "^4.0.4",
62
+ "@evolu/common": "6.0.1-preview.23",
63
+ "@evolu/react": "9.0.1-preview.6",
59
64
  "@evolu/tsconfig": "0.0.2"
60
65
  },
61
66
  "peerDependencies": {
62
- "@evolu/common": "^6.0.1-preview.18",
63
- "@evolu/react": "^9.0.1-preview.4",
67
+ "@evolu/common": "^6.0.1-preview.23",
68
+ "@evolu/react": "^9.0.1-preview.6",
64
69
  "@op-engineering/op-sqlite": ">=12",
65
- "expo": ">=52",
66
- "expo-sqlite": ">=15",
67
- "react-native": ">=0.76"
70
+ "expo": ">=54",
71
+ "expo-secure-store": ">=15",
72
+ "expo-sqlite": ">=16",
73
+ "react-native": ">=0.81",
74
+ "react-native-nitro-modules": ">=0.31",
75
+ "react-native-sensitive-info": ">=6",
76
+ "react-native-svg": ">=15"
68
77
  },
69
78
  "peerDependenciesMeta": {
70
79
  "@op-engineering/op-sqlite": {
@@ -75,6 +84,18 @@
75
84
  },
76
85
  "expo-sqlite": {
77
86
  "optional": true
87
+ },
88
+ "expo-secure-store": {
89
+ "optional": true
90
+ },
91
+ "react-native-nitro-modules": {
92
+ "optional": true
93
+ },
94
+ "react-native-sensitive-info": {
95
+ "optional": true
96
+ },
97
+ "react-native-svg": {
98
+ "optional": true
78
99
  }
79
100
  },
80
101
  "publishConfig": {
@@ -0,0 +1,27 @@
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 style={{
15
+ width: size,
16
+ height: size,
17
+ borderRadius,
18
+ overflow: 'hidden',
19
+ }}>
20
+ <SvgXml
21
+ xml={svg}
22
+ width={size}
23
+ height={size}
24
+ />
25
+ </View>
26
+ ) : null;
27
+ }
@@ -1,53 +1,55 @@
1
1
  import {
2
2
  createConsole,
3
- createEnglishMnemonic,
4
- createNanoIdLib,
3
+ createLocalAuth,
5
4
  createRandom,
6
5
  createRandomBytes,
7
6
  createTime,
7
+ createWebSocket,
8
8
  } from "@evolu/common";
9
9
 
10
10
  import {
11
- CreateAppState,
12
11
  CreateDbWorker,
13
12
  createDbWorkerForPlatform,
14
- createWebSocketSync,
15
13
  EvoluDeps,
14
+ ReloadApp,
16
15
  } from "@evolu/common/evolu";
17
16
 
18
17
  import * as Expo from "expo";
19
-
20
18
  import { createExpoSqliteDriver } from "./providers/ExpoSqliteDriver.js";
19
+ import { createSecureStore } from "./utils/LocalAuth.js";
21
20
  import { polyfillHermes } from "./utils/Hermes.js";
22
21
 
23
22
  polyfillHermes();
24
23
 
25
- export const createAppState: CreateAppState = () => ({
26
- reset: () => {
27
- void Expo.reloadAppAsync();
28
- },
29
- });
30
-
31
- const nanoIdLib = createNanoIdLib();
32
24
  const console = createConsole();
33
25
  const time = createTime();
26
+ const randomBytes = createRandomBytes();
34
27
 
35
28
  const createDbWorker: CreateDbWorker = () =>
36
29
  createDbWorkerForPlatform({
37
- createSqliteDriver: createExpoSqliteDriver,
38
- createSync: createWebSocketSync,
39
30
  console,
40
- time,
31
+ createSqliteDriver: createExpoSqliteDriver,
32
+ createWebSocket,
41
33
  random: createRandom(),
42
- nanoIdLib,
43
- createMnemonic: createEnglishMnemonic,
44
- createRandomBytes,
34
+ randomBytes,
35
+ time,
45
36
  });
46
37
 
38
+ const reloadApp: ReloadApp = () => {
39
+ void Expo.reloadAppAsync();
40
+ };
41
+
42
+ export * from "./components/EvoluAvatar.js";
43
+
44
+ export const localAuth = createLocalAuth({
45
+ randomBytes: randomBytes,
46
+ secureStorage: createSecureStore(),
47
+ });
48
+
47
49
  export const evoluReactNativeDeps: EvoluDeps = {
48
- time,
49
- nanoIdLib,
50
50
  console,
51
- createAppState,
52
51
  createDbWorker,
52
+ randomBytes,
53
+ reloadApp,
54
+ time,
53
55
  };
package/src/op-sqlite.ts CHANGED
@@ -1,56 +1,59 @@
1
1
  import {
2
2
  createConsole,
3
- createEnglishMnemonic,
4
- createNanoIdLib,
3
+ createLocalAuth,
5
4
  createRandom,
6
5
  createRandomBytes,
7
6
  createTime,
7
+ createWebSocket,
8
8
  } from "@evolu/common";
9
9
 
10
10
  import {
11
- CreateAppState,
12
11
  CreateDbWorker,
13
12
  createDbWorkerForPlatform,
14
- createWebSocketSync,
15
13
  EvoluDeps,
14
+ ReloadApp,
16
15
  } from "@evolu/common/evolu";
17
16
 
18
17
  import { DevSettings } from "react-native";
18
+ import { SensitiveInfo } from "react-native-sensitive-info";
19
19
  import { createOpSqliteDriver } from "./providers/OpSqliteDriver.js";
20
20
  import { polyfillHermes } from "./utils/Hermes.js";
21
21
 
22
22
  polyfillHermes();
23
23
 
24
- export const createAppState: CreateAppState = () => ({
25
- reset: () => {
26
- if (process.env.NODE_ENV === "development") {
27
- DevSettings.reload();
28
- } else {
29
- // TODO: reload not implemented for bare rn
30
- }
31
- },
32
- });
33
-
34
- const nanoIdLib = createNanoIdLib();
35
24
  const console = createConsole();
36
25
  const time = createTime();
26
+ const randomBytes = createRandomBytes();
37
27
 
38
28
  const createDbWorker: CreateDbWorker = () =>
39
29
  createDbWorkerForPlatform({
40
- createSqliteDriver: createOpSqliteDriver,
41
- createSync: createWebSocketSync,
42
30
  console,
43
- time,
31
+ createSqliteDriver: createOpSqliteDriver,
32
+ createWebSocket,
44
33
  random: createRandom(),
45
- nanoIdLib,
46
- createMnemonic: createEnglishMnemonic,
47
- createRandomBytes,
34
+ randomBytes,
35
+ time,
48
36
  });
49
37
 
38
+ const reloadApp: ReloadApp = () => {
39
+ if (process.env.NODE_ENV === "development") {
40
+ DevSettings.reload();
41
+ } else {
42
+ // TODO: reload not implemented for bare rn
43
+ }
44
+ };
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
- time,
52
- nanoIdLib,
53
54
  console,
54
- createAppState,
55
55
  createDbWorker,
56
+ randomBytes,
57
+ reloadApp,
58
+ time,
56
59
  };
@@ -3,6 +3,7 @@ import {
3
3
  CreateSqliteDriver,
4
4
  SqliteDriver,
5
5
  SqliteRow,
6
+ bytesToHex,
6
7
  } from "@evolu/common";
7
8
 
8
9
  import { openDatabaseSync, SQLiteStatement } from "expo-sqlite";
@@ -11,6 +12,13 @@ export const createExpoSqliteDriver: CreateSqliteDriver = (name, options) => {
11
12
  const db = openDatabaseSync(
12
13
  options?.memory ? ":memory:" : `evolu1-${name}.db`,
13
14
  );
15
+ if (options?.encryptionKey) {
16
+ db.execSync(`
17
+ PRAGMA cipher = 'sqlcipher';
18
+ PRAGMA legacy = 4;
19
+ PRAGMA key = "x'${bytesToHex(options.encryptionKey)}'";
20
+ `);
21
+ }
14
22
  let isDisposed = false;
15
23
 
16
24
  const cache = createPreparedStatementsCache<SQLiteStatement>(
@@ -5,6 +5,7 @@ import {
5
5
  CreateSqliteDriver,
6
6
  SqliteDriver,
7
7
  SqliteRow,
8
+ bytesToHex,
8
9
  } from "@evolu/common";
9
10
 
10
11
  import { open, PreparedStatement } from "@op-engineering/op-sqlite";
@@ -14,7 +15,12 @@ export const createOpSqliteDriver: CreateSqliteDriver = (name, options) => {
14
15
  const db = open(
15
16
  options?.memory
16
17
  ? { name: `inMemoryDb`, location: ":memory:" }
17
- : { name: `evolu1-${name}.db` },
18
+ : {
19
+ name: `evolu1-${name}.db`,
20
+ ...(options?.encryptionKey && {
21
+ encryptionKey: `x'${bytesToHex(options.encryptionKey)}'`,
22
+ }),
23
+ },
18
24
  );
19
25
  let isDisposed = false;
20
26
 
@@ -0,0 +1,118 @@
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 { LocalAuthOptions, SecureStorage, SensitiveInfoItem, StorageMetadata, AccessControl } from "@evolu/common";
6
+
7
+ export const createSecureStore = (): SecureStorage => {
8
+ const store: SecureStorage = {
9
+ setItem: async (key, value, options) => {
10
+ const rnsiOpts = convertOptions(options);
11
+ const service = options?.service ?? 'default';
12
+ const metadata = createMetadata(options?.accessControl === 'none');
13
+ await KVStore.setItem(`${service}-${key}`, '1');
14
+ await SecureStore.setItemAsync(key, JSON.stringify({value, metadata}), rnsiOpts);
15
+ return { metadata };
16
+ },
17
+
18
+ getItem: async (key, options) => {
19
+ const rnsiOpts = convertOptions(options);
20
+ const service = options?.service ?? 'default';
21
+ let data: {value: string; metadata: StorageMetadata};
22
+ try {
23
+ const result = await SecureStore.getItemAsync(key, rnsiOpts);
24
+ if (!result) return null;
25
+ data = JSON.parse(result) as {value: string; metadata: StorageMetadata};
26
+ } catch (_error) {
27
+ return null;
28
+ }
29
+ return { key, service, ...data };
30
+ },
31
+
32
+ deleteItem: async (key, options) => {
33
+ const rnsiOpts = convertOptions(options);
34
+ const service = options?.service ?? 'default';
35
+ await Promise.all([
36
+ KVStore.removeItemAsync(`${service}-${key}`),
37
+ SecureStore.deleteItemAsync(key, rnsiOpts),
38
+ ]);
39
+ return true;
40
+ },
41
+
42
+ getAllItems: async (options) => {
43
+ const keys = await KVStore.getAllKeysAsync();
44
+ const service = options?.service ?? 'default';
45
+ const metadata = createMetadata(options?.accessControl === 'none');
46
+ return keys
47
+ .filter(key => key.startsWith(`${service}-`))
48
+ .map(key => ({ key: key.slice(service.length + 1), service, metadata }));
49
+ },
50
+
51
+ clearService: async (options) => {
52
+ const rnsiOpts = convertOptions(options);
53
+ const service = options?.service ?? 'default';
54
+ const items = await store.getAllItems(options);
55
+ await KVStore.multiRemove(items.map(item => `${service}-${item.key}`));
56
+ await Promise.all(items.map(async (item) => {
57
+ await SecureStore.deleteItemAsync(item.key, rnsiOpts);
58
+ }));
59
+ },
60
+ };
61
+
62
+ return store;
63
+ };
64
+
65
+ /**
66
+ * Create default metadata for backwards compatibility with items that don't
67
+ * have stored metadata.
68
+ */
69
+ const createMetadata = (isSecure = true): SensitiveInfoItem["metadata"] => {
70
+ return {
71
+ backend: "keychain",
72
+ accessControl: isSecure ? "biometryCurrentSet" : "none",
73
+ securityLevel: isSecure ? "biometry" : "software",
74
+ timestamp: Date.now(),
75
+ };
76
+ };
77
+
78
+ function convertOptions(options?: LocalAuthOptions): SecureStore.SecureStoreOptions {
79
+ const accessGroup = options?.keychainGroup
80
+ ?? AUTH_DEFAULT_OPTIONS.keychainGroup
81
+ ?? '';
82
+ const keychainService = options?.service
83
+ ?? AUTH_DEFAULT_OPTIONS.service
84
+ ?? '';
85
+ const keychainAccessible = convertKeychainAccessible(options?.accessControl
86
+ ?? AUTH_DEFAULT_OPTIONS.accessControl
87
+ ?? 'biometryCurrentSet');
88
+ const authenticationPrompt = options?.authenticationPrompt?.title
89
+ ?? AUTH_DEFAULT_OPTIONS.authenticationPrompt?.title
90
+ ?? '';
91
+ return {
92
+ accessGroup,
93
+ keychainService,
94
+ keychainAccessible,
95
+ authenticationPrompt,
96
+ requireAuthentication: options?.accessControl !== 'none',
97
+ };
98
+ }
99
+
100
+ function convertKeychainAccessible(accessControl: AccessControl): SecureStore.KeychainAccessibilityConstant {
101
+ switch (accessControl) {
102
+ case "none":
103
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
104
+ return SecureStore.ALWAYS;
105
+ case "biometryCurrentSet":
106
+ return SecureStore.AFTER_FIRST_UNLOCK;
107
+ case "biometryAny":
108
+ return SecureStore.AFTER_FIRST_UNLOCK;
109
+ case "devicePasscode":
110
+ return SecureStore.AFTER_FIRST_UNLOCK;
111
+ case "secureEnclaveBiometry":
112
+ return SecureStore.AFTER_FIRST_UNLOCK;
113
+ // Exhaustive check
114
+ default: accessControl satisfies never;
115
+ // Default (for typescript, should never hit)
116
+ return SecureStore.AFTER_FIRST_UNLOCK;
117
+ }
118
+ }