jazz-react-native 0.12.1 → 0.13.0
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +31 -0
- package/README.md +3 -229
- package/dist/index.d.ts +3 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -6
- package/dist/index.js.map +1 -1
- package/dist/provider.d.ts +2 -13
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +8 -48
- package/dist/provider.js.map +1 -1
- package/dist/storage/mmkv-store-adapter.d.ts +8 -0
- package/dist/storage/mmkv-store-adapter.d.ts.map +1 -0
- package/dist/storage/mmkv-store-adapter.js +19 -0
- package/dist/storage/mmkv-store-adapter.js.map +1 -0
- package/dist/storage/op-sqlite-adapter.d.ts +18 -0
- package/dist/storage/op-sqlite-adapter.d.ts.map +1 -0
- package/dist/storage/op-sqlite-adapter.js +127 -0
- package/dist/storage/op-sqlite-adapter.js.map +1 -0
- package/dist/testing.d.ts +1 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/package.json +10 -18
- package/src/index.ts +4 -7
- package/src/provider.tsx +12 -91
- package/src/storage/mmkv-store-adapter.ts +24 -0
- package/src/storage/op-sqlite-adapter.ts +168 -0
- package/src/testing.ts +1 -0
- package/dist/ReactNativeContextManager.d.ts +0 -30
- package/dist/ReactNativeContextManager.d.ts.map +0 -1
- package/dist/ReactNativeContextManager.js +0 -39
- package/dist/ReactNativeContextManager.js.map +0 -1
- package/dist/auth/DemoAuthUI.d.ts +0 -8
- package/dist/auth/DemoAuthUI.d.ts.map +0 -1
- package/dist/auth/DemoAuthUI.js +0 -156
- package/dist/auth/DemoAuthUI.js.map +0 -1
- package/dist/auth/auth.d.ts +0 -3
- package/dist/auth/auth.d.ts.map +0 -1
- package/dist/auth/auth.js +0 -12
- package/dist/auth/auth.js.map +0 -1
- package/dist/crypto/RNQuickCrypto.d.ts +0 -14
- package/dist/crypto/RNQuickCrypto.d.ts.map +0 -1
- package/dist/crypto/RNQuickCrypto.js +0 -31
- package/dist/crypto/RNQuickCrypto.js.map +0 -1
- package/dist/crypto/index.d.ts +0 -2
- package/dist/crypto/index.d.ts.map +0 -1
- package/dist/crypto/index.js +0 -2
- package/dist/crypto/index.js.map +0 -1
- package/dist/hooks.d.ts +0 -14
- package/dist/hooks.d.ts.map +0 -1
- package/dist/hooks.js +0 -35
- package/dist/hooks.js.map +0 -1
- package/dist/media.d.ts +0 -24
- package/dist/media.d.ts.map +0 -1
- package/dist/media.js +0 -61
- package/dist/media.js.map +0 -1
- package/dist/platform.d.ts +0 -46
- package/dist/platform.d.ts.map +0 -1
- package/dist/platform.js +0 -155
- package/dist/platform.js.map +0 -1
- package/dist/storage/expo-secure-store-adapter.d.ts +0 -8
- package/dist/storage/expo-secure-store-adapter.d.ts.map +0 -1
- package/dist/storage/expo-secure-store-adapter.js +0 -25
- package/dist/storage/expo-secure-store-adapter.js.map +0 -1
- package/dist/storage/kv-store-context.d.ts +0 -17
- package/dist/storage/kv-store-context.d.ts.map +0 -1
- package/dist/storage/kv-store-context.js +0 -27
- package/dist/storage/kv-store-context.js.map +0 -1
- package/src/ReactNativeContextManager.ts +0 -70
- package/src/auth/DemoAuthUI.tsx +0 -202
- package/src/auth/auth.ts +0 -14
- package/src/crypto/RNQuickCrypto.ts +0 -59
- package/src/crypto/index.ts +0 -1
- package/src/hooks.tsx +0 -72
- package/src/media.tsx +0 -92
- package/src/platform.ts +0 -238
- package/src/storage/expo-secure-store-adapter.ts +0 -29
- package/src/storage/kv-store-context.ts +0 -39
- package/src/testing.tsx +0 -1
package/src/auth/DemoAuthUI.tsx
DELETED
@@ -1,202 +0,0 @@
|
|
1
|
-
import { useDemoAuth } from "jazz-react-core";
|
2
|
-
import React, { useState } from "react";
|
3
|
-
import {
|
4
|
-
StyleSheet,
|
5
|
-
Text,
|
6
|
-
TextInput,
|
7
|
-
TouchableOpacity,
|
8
|
-
View,
|
9
|
-
useColorScheme,
|
10
|
-
} from "react-native";
|
11
|
-
|
12
|
-
export const DemoAuthBasicUI = ({
|
13
|
-
appName,
|
14
|
-
auth,
|
15
|
-
children,
|
16
|
-
}: {
|
17
|
-
appName: string;
|
18
|
-
auth: ReturnType<typeof useDemoAuth>;
|
19
|
-
children: React.ReactNode;
|
20
|
-
}) => {
|
21
|
-
const colorScheme = useColorScheme();
|
22
|
-
const darkMode = colorScheme === "dark";
|
23
|
-
const [username, setUsername] = useState<string>("");
|
24
|
-
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
25
|
-
|
26
|
-
const handleSignUp = () => {
|
27
|
-
setErrorMessage(null);
|
28
|
-
|
29
|
-
auth.signUp(username).catch((error) => {
|
30
|
-
setErrorMessage(error.message);
|
31
|
-
});
|
32
|
-
};
|
33
|
-
|
34
|
-
const handleLogIn = (username: string) => {
|
35
|
-
setErrorMessage(null);
|
36
|
-
|
37
|
-
auth.logIn(username).catch((error) => {
|
38
|
-
setErrorMessage(error.message);
|
39
|
-
});
|
40
|
-
};
|
41
|
-
|
42
|
-
if (auth.state === "signedIn") {
|
43
|
-
return children;
|
44
|
-
}
|
45
|
-
|
46
|
-
return (
|
47
|
-
<View
|
48
|
-
style={[
|
49
|
-
styles.container,
|
50
|
-
darkMode ? styles.darkBackground : styles.lightBackground,
|
51
|
-
]}
|
52
|
-
>
|
53
|
-
<View style={styles.formContainer}>
|
54
|
-
<Text
|
55
|
-
style={[
|
56
|
-
styles.headerText,
|
57
|
-
darkMode ? styles.darkText : styles.lightText,
|
58
|
-
]}
|
59
|
-
>
|
60
|
-
{appName}
|
61
|
-
</Text>
|
62
|
-
|
63
|
-
{errorMessage && <Text style={styles.errorText}>{errorMessage}</Text>}
|
64
|
-
|
65
|
-
<TextInput
|
66
|
-
placeholder="Display name"
|
67
|
-
value={username}
|
68
|
-
onChangeText={setUsername}
|
69
|
-
placeholderTextColor={darkMode ? "#fff" : "#000"}
|
70
|
-
style={[
|
71
|
-
styles.textInput,
|
72
|
-
darkMode ? styles.darkInput : styles.lightInput,
|
73
|
-
]}
|
74
|
-
/>
|
75
|
-
|
76
|
-
<TouchableOpacity
|
77
|
-
onPress={handleSignUp}
|
78
|
-
style={[
|
79
|
-
styles.button,
|
80
|
-
darkMode ? styles.darkButton : styles.lightButton,
|
81
|
-
]}
|
82
|
-
>
|
83
|
-
<Text
|
84
|
-
style={darkMode ? styles.darkButtonText : styles.lightButtonText}
|
85
|
-
>
|
86
|
-
Sign Up as new account
|
87
|
-
</Text>
|
88
|
-
</TouchableOpacity>
|
89
|
-
|
90
|
-
<View style={styles.existingUsersContainer}>
|
91
|
-
{auth.existingUsers.map((user) => (
|
92
|
-
<TouchableOpacity
|
93
|
-
key={user}
|
94
|
-
onPress={() => handleLogIn(user)}
|
95
|
-
style={[
|
96
|
-
styles.existingUserButton,
|
97
|
-
darkMode ? styles.darkUserButton : styles.lightUserButton,
|
98
|
-
]}
|
99
|
-
>
|
100
|
-
<Text style={darkMode ? styles.darkText : styles.lightText}>
|
101
|
-
Log In as "{user}"
|
102
|
-
</Text>
|
103
|
-
</TouchableOpacity>
|
104
|
-
))}
|
105
|
-
</View>
|
106
|
-
</View>
|
107
|
-
</View>
|
108
|
-
);
|
109
|
-
};
|
110
|
-
|
111
|
-
const styles = StyleSheet.create({
|
112
|
-
container: {
|
113
|
-
flex: 1,
|
114
|
-
justifyContent: "center",
|
115
|
-
alignItems: "center",
|
116
|
-
padding: 20,
|
117
|
-
},
|
118
|
-
formContainer: {
|
119
|
-
width: "80%",
|
120
|
-
alignItems: "center",
|
121
|
-
justifyContent: "center",
|
122
|
-
},
|
123
|
-
headerText: {
|
124
|
-
fontSize: 24,
|
125
|
-
marginBottom: 20,
|
126
|
-
},
|
127
|
-
errorText: {
|
128
|
-
color: "red",
|
129
|
-
marginVertical: 5,
|
130
|
-
textAlign: "center",
|
131
|
-
},
|
132
|
-
textInput: {
|
133
|
-
borderWidth: 1,
|
134
|
-
padding: 10,
|
135
|
-
marginVertical: 10,
|
136
|
-
width: "100%",
|
137
|
-
borderRadius: 6,
|
138
|
-
},
|
139
|
-
darkInput: {
|
140
|
-
borderColor: "#444",
|
141
|
-
backgroundColor: "#000",
|
142
|
-
color: "#fff",
|
143
|
-
},
|
144
|
-
lightInput: {
|
145
|
-
borderColor: "#ddd",
|
146
|
-
backgroundColor: "#fff",
|
147
|
-
color: "#000",
|
148
|
-
},
|
149
|
-
button: {
|
150
|
-
paddingVertical: 15,
|
151
|
-
paddingHorizontal: 10,
|
152
|
-
borderRadius: 6,
|
153
|
-
width: "100%",
|
154
|
-
marginVertical: 10,
|
155
|
-
},
|
156
|
-
darkButton: {
|
157
|
-
backgroundColor: "#444",
|
158
|
-
},
|
159
|
-
lightButton: {
|
160
|
-
backgroundColor: "#ddd",
|
161
|
-
},
|
162
|
-
darkButtonText: {
|
163
|
-
color: "#fff",
|
164
|
-
textAlign: "center",
|
165
|
-
},
|
166
|
-
lightButtonText: {
|
167
|
-
color: "#000",
|
168
|
-
textAlign: "center",
|
169
|
-
},
|
170
|
-
existingUsersContainer: {
|
171
|
-
width: "100%",
|
172
|
-
marginTop: 20,
|
173
|
-
},
|
174
|
-
existingUserButton: {
|
175
|
-
paddingVertical: 15,
|
176
|
-
paddingHorizontal: 10,
|
177
|
-
borderRadius: 6,
|
178
|
-
marginVertical: 5,
|
179
|
-
},
|
180
|
-
darkUserButton: {
|
181
|
-
backgroundColor: "#222",
|
182
|
-
},
|
183
|
-
lightUserButton: {
|
184
|
-
backgroundColor: "#eee",
|
185
|
-
},
|
186
|
-
loadingText: {
|
187
|
-
fontSize: 18,
|
188
|
-
color: "#888",
|
189
|
-
},
|
190
|
-
darkText: {
|
191
|
-
color: "#fff",
|
192
|
-
},
|
193
|
-
lightText: {
|
194
|
-
color: "#000",
|
195
|
-
},
|
196
|
-
darkBackground: {
|
197
|
-
backgroundColor: "#000",
|
198
|
-
},
|
199
|
-
lightBackground: {
|
200
|
-
backgroundColor: "#fff",
|
201
|
-
},
|
202
|
-
});
|
package/src/auth/auth.ts
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
import KvStoreContext from "../storage/kv-store-context.js";
|
2
|
-
|
3
|
-
export * from "./DemoAuthUI.js";
|
4
|
-
|
5
|
-
export function clearUserCredentials() {
|
6
|
-
const kvStore = KvStoreContext.getInstance().getStorage();
|
7
|
-
|
8
|
-
// TODO: Migrate the Auth methods to use the same storage key/interface
|
9
|
-
return Promise.all([
|
10
|
-
kvStore.delete("demo-auth-logged-in-secret"),
|
11
|
-
kvStore.delete("jazz-clerk-auth"),
|
12
|
-
kvStore.delete("jazz-logged-in-secret"),
|
13
|
-
]);
|
14
|
-
}
|
@@ -1,59 +0,0 @@
|
|
1
|
-
import { base58 } from "@scure/base";
|
2
|
-
import { JsonValue } from "cojson";
|
3
|
-
import { CojsonInternalTypes, cojsonInternals } from "cojson";
|
4
|
-
import { PureJSCrypto } from "cojson/dist/crypto/PureJSCrypto"; // Importing from dist to not rely on the exports field
|
5
|
-
import { Ed } from "react-native-quick-crypto";
|
6
|
-
const { stableStringify } = cojsonInternals;
|
7
|
-
|
8
|
-
const textEncoder = new TextEncoder();
|
9
|
-
|
10
|
-
export class RNQuickCrypto extends PureJSCrypto {
|
11
|
-
ed: Ed;
|
12
|
-
|
13
|
-
constructor() {
|
14
|
-
super();
|
15
|
-
this.ed = new Ed("ed25519", {});
|
16
|
-
}
|
17
|
-
|
18
|
-
static async create(): Promise<RNQuickCrypto> {
|
19
|
-
return new RNQuickCrypto();
|
20
|
-
}
|
21
|
-
|
22
|
-
newEd25519SigningKey(): Uint8Array {
|
23
|
-
this.ed.generateKeyPairSync();
|
24
|
-
return new Uint8Array(this.ed.getPrivateKey());
|
25
|
-
}
|
26
|
-
|
27
|
-
getSignerID(
|
28
|
-
secret: CojsonInternalTypes.SignerSecret,
|
29
|
-
): CojsonInternalTypes.SignerID {
|
30
|
-
return `signer_z${base58.encode(
|
31
|
-
base58.decode(secret.substring("signerSecret_z".length)),
|
32
|
-
)}`;
|
33
|
-
}
|
34
|
-
|
35
|
-
sign(
|
36
|
-
secret: CojsonInternalTypes.SignerSecret,
|
37
|
-
message: JsonValue,
|
38
|
-
): CojsonInternalTypes.Signature {
|
39
|
-
const signature = new Uint8Array(
|
40
|
-
this.ed.signSync(
|
41
|
-
textEncoder.encode(stableStringify(message)),
|
42
|
-
base58.decode(secret.substring("signerSecret_z".length)),
|
43
|
-
),
|
44
|
-
);
|
45
|
-
return `signature_z${base58.encode(signature)}`;
|
46
|
-
}
|
47
|
-
|
48
|
-
verify(
|
49
|
-
signature: CojsonInternalTypes.Signature,
|
50
|
-
message: JsonValue,
|
51
|
-
id: CojsonInternalTypes.SignerID,
|
52
|
-
): boolean {
|
53
|
-
return this.ed.verifySync(
|
54
|
-
base58.decode(signature.substring("signature_z".length)),
|
55
|
-
textEncoder.encode(stableStringify(message)),
|
56
|
-
base58.decode(id.substring("signer_z".length)),
|
57
|
-
);
|
58
|
-
}
|
59
|
-
}
|
package/src/crypto/index.ts
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
export { RNQuickCrypto } from "./RNQuickCrypto.js";
|
package/src/hooks.tsx
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
import { useEffect } from "react";
|
2
|
-
|
3
|
-
import { useJazzContext } from "jazz-react-core";
|
4
|
-
import { CoValue, CoValueClass, ID, parseInviteLink } from "jazz-tools";
|
5
|
-
import { Linking } from "react-native";
|
6
|
-
import { RegisteredAccount } from "./provider.js";
|
7
|
-
|
8
|
-
export {
|
9
|
-
useCoState,
|
10
|
-
experimental_useInboxSender,
|
11
|
-
useDemoAuth,
|
12
|
-
usePassphraseAuth,
|
13
|
-
useJazzContext,
|
14
|
-
useAuthSecretStorage,
|
15
|
-
useIsAuthenticated,
|
16
|
-
useAccount,
|
17
|
-
useAccountOrGuest,
|
18
|
-
} from "jazz-react-core";
|
19
|
-
|
20
|
-
declare module "jazz-react-core" {
|
21
|
-
export interface Register {
|
22
|
-
Account: RegisteredAccount;
|
23
|
-
}
|
24
|
-
}
|
25
|
-
|
26
|
-
export function useAcceptInvite<V extends CoValue>({
|
27
|
-
invitedObjectSchema,
|
28
|
-
onAccept,
|
29
|
-
forValueHint,
|
30
|
-
}: {
|
31
|
-
invitedObjectSchema: CoValueClass<V>;
|
32
|
-
onAccept: (projectID: ID<V>) => void;
|
33
|
-
forValueHint?: string;
|
34
|
-
}): void {
|
35
|
-
const context = useJazzContext();
|
36
|
-
|
37
|
-
if (!("me" in context)) {
|
38
|
-
throw new Error(
|
39
|
-
"useAcceptInvite can't be used in a JazzProvider with auth === 'guest'.",
|
40
|
-
);
|
41
|
-
}
|
42
|
-
|
43
|
-
useEffect(() => {
|
44
|
-
const handleDeepLink = ({ url }: { url: string }) => {
|
45
|
-
const result = parseInviteLink<V>(url);
|
46
|
-
if (result && result.valueHint === forValueHint) {
|
47
|
-
context.me
|
48
|
-
.acceptInvite(
|
49
|
-
result.valueID,
|
50
|
-
result.inviteSecret,
|
51
|
-
invitedObjectSchema,
|
52
|
-
)
|
53
|
-
.then(() => {
|
54
|
-
onAccept(result.valueID);
|
55
|
-
})
|
56
|
-
.catch((e) => {
|
57
|
-
console.error("Failed to accept invite", e);
|
58
|
-
});
|
59
|
-
}
|
60
|
-
};
|
61
|
-
|
62
|
-
const linkingListener = Linking.addEventListener("url", handleDeepLink);
|
63
|
-
|
64
|
-
void Linking.getInitialURL().then((url) => {
|
65
|
-
if (url) handleDeepLink({ url });
|
66
|
-
});
|
67
|
-
|
68
|
-
return () => {
|
69
|
-
linkingListener.remove();
|
70
|
-
};
|
71
|
-
}, [context, onAccept, invitedObjectSchema, forValueHint]);
|
72
|
-
}
|
package/src/media.tsx
DELETED
@@ -1,92 +0,0 @@
|
|
1
|
-
import { ImageDefinition } from "jazz-tools";
|
2
|
-
import React, { useEffect, useState } from "react";
|
3
|
-
|
4
|
-
/** @category Media */
|
5
|
-
export function useProgressiveImg({
|
6
|
-
image,
|
7
|
-
maxWidth,
|
8
|
-
targetWidth,
|
9
|
-
}: {
|
10
|
-
image: ImageDefinition | null | undefined;
|
11
|
-
maxWidth?: number;
|
12
|
-
targetWidth?: number;
|
13
|
-
}) {
|
14
|
-
const [current, setCurrent] = useState<
|
15
|
-
{ src?: string; res?: `${number}x${number}` | "placeholder" } | undefined
|
16
|
-
>(undefined);
|
17
|
-
|
18
|
-
useEffect(() => {
|
19
|
-
let lastHighestRes: string | undefined;
|
20
|
-
if (!image) return;
|
21
|
-
const unsub = image.subscribe({}, (update) => {
|
22
|
-
const highestRes = update?.highestResAvailable({ maxWidth, targetWidth });
|
23
|
-
if (highestRes && highestRes.res !== lastHighestRes) {
|
24
|
-
lastHighestRes = highestRes.res;
|
25
|
-
// use the base64 data directly
|
26
|
-
const chunks = highestRes.stream.getChunks();
|
27
|
-
if (chunks?.chunks && chunks.chunks.length > 0) {
|
28
|
-
// convert chunks to base64
|
29
|
-
const totalLength = chunks.chunks.reduce(
|
30
|
-
(acc, chunk) => acc + chunk.length,
|
31
|
-
0,
|
32
|
-
);
|
33
|
-
const combinedArray = new Uint8Array(totalLength);
|
34
|
-
let offset = 0;
|
35
|
-
chunks.chunks.forEach((chunk) => {
|
36
|
-
combinedArray.set(chunk, offset);
|
37
|
-
offset += chunk.length;
|
38
|
-
});
|
39
|
-
|
40
|
-
// Create data URL
|
41
|
-
const base64 = Buffer.from(combinedArray).toString("base64");
|
42
|
-
const dataUrl = `data:${chunks.mimeType};base64,${base64}`;
|
43
|
-
|
44
|
-
setCurrent({
|
45
|
-
src: dataUrl,
|
46
|
-
res: highestRes.res,
|
47
|
-
});
|
48
|
-
} else {
|
49
|
-
// Fallback to placeholder if chunks aren't available
|
50
|
-
console.warn("No chunks available for image", image.id);
|
51
|
-
setCurrent({
|
52
|
-
src: update?.placeholderDataURL,
|
53
|
-
res: "placeholder",
|
54
|
-
});
|
55
|
-
}
|
56
|
-
} else if (!highestRes) {
|
57
|
-
setCurrent({
|
58
|
-
src: update?.placeholderDataURL,
|
59
|
-
res: "placeholder",
|
60
|
-
});
|
61
|
-
}
|
62
|
-
});
|
63
|
-
|
64
|
-
return unsub;
|
65
|
-
}, [image?.id, maxWidth]);
|
66
|
-
|
67
|
-
return {
|
68
|
-
src: current?.src,
|
69
|
-
res: current?.res,
|
70
|
-
originalSize: image?.originalSize,
|
71
|
-
};
|
72
|
-
}
|
73
|
-
|
74
|
-
/** @category Media */
|
75
|
-
export function ProgressiveImg({
|
76
|
-
children,
|
77
|
-
image,
|
78
|
-
maxWidth,
|
79
|
-
targetWidth,
|
80
|
-
}: {
|
81
|
-
children: (result: {
|
82
|
-
src: string | undefined;
|
83
|
-
res: `${number}x${number}` | "placeholder" | undefined;
|
84
|
-
originalSize: readonly [number, number] | undefined;
|
85
|
-
}) => React.ReactNode;
|
86
|
-
image: ImageDefinition | null | undefined;
|
87
|
-
maxWidth?: number;
|
88
|
-
targetWidth?: number;
|
89
|
-
}) {
|
90
|
-
const result = useProgressiveImg({ image, maxWidth, targetWidth });
|
91
|
-
return result && children(result);
|
92
|
-
}
|
package/src/platform.ts
DELETED
@@ -1,238 +0,0 @@
|
|
1
|
-
import NetInfo from "@react-native-community/netinfo";
|
2
|
-
import { LocalNode, Peer, RawAccountID } from "cojson";
|
3
|
-
import { SQLiteStorage } from "cojson-storage-rn-sqlite";
|
4
|
-
import { PureJSCrypto } from "cojson/dist/crypto/PureJSCrypto"; // Importing from dist to not rely on the exports field
|
5
|
-
import {
|
6
|
-
Account,
|
7
|
-
AgentID,
|
8
|
-
AuthCredentials,
|
9
|
-
AuthSecretStorage,
|
10
|
-
CoValue,
|
11
|
-
CoValueClass,
|
12
|
-
CryptoProvider,
|
13
|
-
ID,
|
14
|
-
NewAccountProps,
|
15
|
-
SessionID,
|
16
|
-
SyncConfig,
|
17
|
-
createInviteLink as baseCreateInviteLink,
|
18
|
-
createAnonymousJazzContext,
|
19
|
-
createJazzContext,
|
20
|
-
} from "jazz-tools";
|
21
|
-
|
22
|
-
import { WebSocketPeerWithReconnection } from "cojson-transport-ws";
|
23
|
-
import type { RNQuickCrypto } from "./crypto/RNQuickCrypto.js";
|
24
|
-
import { ExpoSecureStoreAdapter } from "./storage/expo-secure-store-adapter.js";
|
25
|
-
import { KvStoreContext } from "./storage/kv-store-context.js";
|
26
|
-
|
27
|
-
export type BaseReactNativeContextOptions = {
|
28
|
-
sync: SyncConfig;
|
29
|
-
reconnectionTimeout?: number;
|
30
|
-
storage?: "sqlite" | "disabled";
|
31
|
-
CryptoProvider?: typeof PureJSCrypto | typeof RNQuickCrypto;
|
32
|
-
authSecretStorage: AuthSecretStorage;
|
33
|
-
};
|
34
|
-
|
35
|
-
class ReactNativeWebSocketPeerWithReconnection extends WebSocketPeerWithReconnection {
|
36
|
-
onNetworkChange(callback: (connected: boolean) => void): () => void {
|
37
|
-
return NetInfo.addEventListener((state) =>
|
38
|
-
callback(state.isConnected ?? false),
|
39
|
-
);
|
40
|
-
}
|
41
|
-
}
|
42
|
-
|
43
|
-
async function setupPeers(options: BaseReactNativeContextOptions) {
|
44
|
-
const CryptoProvider = options.CryptoProvider || PureJSCrypto;
|
45
|
-
const crypto = await CryptoProvider.create();
|
46
|
-
let node: LocalNode | undefined = undefined;
|
47
|
-
|
48
|
-
const peersToLoadFrom: Peer[] = [];
|
49
|
-
|
50
|
-
if (options.storage === "sqlite") {
|
51
|
-
const storage = await SQLiteStorage.asPeer({
|
52
|
-
filename: "jazz-storage",
|
53
|
-
trace: false,
|
54
|
-
});
|
55
|
-
peersToLoadFrom.push(storage);
|
56
|
-
}
|
57
|
-
|
58
|
-
if (options.sync.when === "never") {
|
59
|
-
return {
|
60
|
-
toggleNetwork: () => {},
|
61
|
-
peersToLoadFrom,
|
62
|
-
setNode: () => {},
|
63
|
-
crypto,
|
64
|
-
};
|
65
|
-
}
|
66
|
-
|
67
|
-
const wsPeer = new ReactNativeWebSocketPeerWithReconnection({
|
68
|
-
peer: options.sync.peer,
|
69
|
-
reconnectionTimeout: options.reconnectionTimeout,
|
70
|
-
addPeer: (peer) => {
|
71
|
-
if (node) {
|
72
|
-
node.syncManager.addPeer(peer);
|
73
|
-
} else {
|
74
|
-
peersToLoadFrom.push(peer);
|
75
|
-
}
|
76
|
-
},
|
77
|
-
removePeer: (peer) => {
|
78
|
-
peersToLoadFrom.splice(peersToLoadFrom.indexOf(peer), 1);
|
79
|
-
},
|
80
|
-
});
|
81
|
-
|
82
|
-
function toggleNetwork(enabled: boolean) {
|
83
|
-
if (enabled) {
|
84
|
-
wsPeer.enable();
|
85
|
-
} else {
|
86
|
-
wsPeer.disable();
|
87
|
-
}
|
88
|
-
}
|
89
|
-
|
90
|
-
function setNode(value: LocalNode) {
|
91
|
-
node = value;
|
92
|
-
}
|
93
|
-
|
94
|
-
if (options.sync.when === "always" || !options.sync.when) {
|
95
|
-
toggleNetwork(true);
|
96
|
-
}
|
97
|
-
|
98
|
-
return {
|
99
|
-
toggleNetwork,
|
100
|
-
peersToLoadFrom,
|
101
|
-
setNode,
|
102
|
-
crypto,
|
103
|
-
};
|
104
|
-
}
|
105
|
-
|
106
|
-
export async function createJazzReactNativeGuestContext(
|
107
|
-
options: BaseReactNativeContextOptions,
|
108
|
-
) {
|
109
|
-
const { toggleNetwork, peersToLoadFrom, setNode, crypto } =
|
110
|
-
await setupPeers(options);
|
111
|
-
|
112
|
-
const context = await createAnonymousJazzContext({
|
113
|
-
crypto,
|
114
|
-
peersToLoadFrom,
|
115
|
-
});
|
116
|
-
|
117
|
-
setNode(context.agent.node);
|
118
|
-
|
119
|
-
options.authSecretStorage.emitUpdate(null);
|
120
|
-
|
121
|
-
return {
|
122
|
-
guest: context.agent,
|
123
|
-
node: context.agent.node,
|
124
|
-
done: () => {
|
125
|
-
// TODO: Sync all the covalues before closing the connection & context
|
126
|
-
toggleNetwork(false);
|
127
|
-
context.done();
|
128
|
-
},
|
129
|
-
logOut: () => {
|
130
|
-
return context.logOut();
|
131
|
-
},
|
132
|
-
};
|
133
|
-
}
|
134
|
-
|
135
|
-
export type ReactNativeContextOptions<Acc extends Account> = {
|
136
|
-
credentials?: AuthCredentials;
|
137
|
-
AccountSchema?: CoValueClass<Acc> & {
|
138
|
-
fromNode: (typeof Account)["fromNode"];
|
139
|
-
};
|
140
|
-
newAccountProps?: NewAccountProps;
|
141
|
-
defaultProfileName?: string;
|
142
|
-
} & BaseReactNativeContextOptions;
|
143
|
-
|
144
|
-
export async function createJazzReactNativeContext<Acc extends Account>(
|
145
|
-
options: ReactNativeContextOptions<Acc>,
|
146
|
-
) {
|
147
|
-
const { toggleNetwork, peersToLoadFrom, setNode, crypto } =
|
148
|
-
await setupPeers(options);
|
149
|
-
|
150
|
-
let unsubscribeAuthUpdate = () => {};
|
151
|
-
|
152
|
-
if (options.sync.when === "signedUp") {
|
153
|
-
const authSecretStorage = options.authSecretStorage;
|
154
|
-
const credentials = options.credentials ?? (await authSecretStorage.get());
|
155
|
-
|
156
|
-
// To update the internal state with the current credentials
|
157
|
-
authSecretStorage.emitUpdate(credentials);
|
158
|
-
|
159
|
-
function handleAuthUpdate(isAuthenticated: boolean) {
|
160
|
-
if (isAuthenticated) {
|
161
|
-
toggleNetwork(true);
|
162
|
-
} else {
|
163
|
-
toggleNetwork(false);
|
164
|
-
}
|
165
|
-
}
|
166
|
-
|
167
|
-
unsubscribeAuthUpdate = authSecretStorage.onUpdate(handleAuthUpdate);
|
168
|
-
handleAuthUpdate(authSecretStorage.isAuthenticated);
|
169
|
-
}
|
170
|
-
|
171
|
-
const context = await createJazzContext({
|
172
|
-
credentials: options.credentials,
|
173
|
-
newAccountProps: options.newAccountProps,
|
174
|
-
peersToLoadFrom,
|
175
|
-
crypto,
|
176
|
-
defaultProfileName: options.defaultProfileName,
|
177
|
-
AccountSchema: options.AccountSchema,
|
178
|
-
sessionProvider: provideLockSession,
|
179
|
-
authSecretStorage: options.authSecretStorage,
|
180
|
-
});
|
181
|
-
|
182
|
-
setNode(context.node);
|
183
|
-
|
184
|
-
return {
|
185
|
-
me: context.account,
|
186
|
-
node: context.node,
|
187
|
-
authSecretStorage: context.authSecretStorage,
|
188
|
-
done: () => {
|
189
|
-
// TODO: Sync all the covalues before closing the connection & context
|
190
|
-
toggleNetwork(false);
|
191
|
-
unsubscribeAuthUpdate();
|
192
|
-
context.done();
|
193
|
-
},
|
194
|
-
logOut: () => {
|
195
|
-
unsubscribeAuthUpdate();
|
196
|
-
return context.logOut();
|
197
|
-
},
|
198
|
-
};
|
199
|
-
}
|
200
|
-
|
201
|
-
/** @category Auth Providers */
|
202
|
-
export type SessionProvider = (
|
203
|
-
accountID: ID<Account> | AgentID,
|
204
|
-
) => Promise<SessionID>;
|
205
|
-
|
206
|
-
export async function provideLockSession(
|
207
|
-
accountID: ID<Account> | AgentID,
|
208
|
-
crypto: CryptoProvider,
|
209
|
-
) {
|
210
|
-
const sessionDone = () => {};
|
211
|
-
|
212
|
-
const kvStore = KvStoreContext.getInstance().getStorage();
|
213
|
-
|
214
|
-
const sessionID =
|
215
|
-
((await kvStore.get(accountID)) as SessionID) ||
|
216
|
-
crypto.newRandomSessionID(accountID as RawAccountID | AgentID);
|
217
|
-
await kvStore.set(accountID, sessionID);
|
218
|
-
|
219
|
-
return Promise.resolve({
|
220
|
-
sessionID,
|
221
|
-
sessionDone,
|
222
|
-
});
|
223
|
-
}
|
224
|
-
|
225
|
-
/** @category Invite Links */
|
226
|
-
export function createInviteLink<C extends CoValue>(
|
227
|
-
value: C,
|
228
|
-
role: "reader" | "writer" | "admin",
|
229
|
-
{ baseURL, valueHint }: { baseURL?: string; valueHint?: string } = {},
|
230
|
-
): string {
|
231
|
-
return baseCreateInviteLink(value, role, baseURL ?? "", valueHint);
|
232
|
-
}
|
233
|
-
|
234
|
-
export function setupKvStore(kvStore = new ExpoSecureStoreAdapter()) {
|
235
|
-
KvStoreContext.getInstance().initialize(kvStore);
|
236
|
-
|
237
|
-
return kvStore;
|
238
|
-
}
|
@@ -1,29 +0,0 @@
|
|
1
|
-
import * as SecureStore from "expo-secure-store";
|
2
|
-
import type { KvStore } from "./kv-store-context.js";
|
3
|
-
|
4
|
-
export class ExpoSecureStoreAdapter implements KvStore {
|
5
|
-
get(key: string): Promise<string | null> {
|
6
|
-
return SecureStore.getItemAsync(key, {
|
7
|
-
requireAuthentication: false,
|
8
|
-
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
|
9
|
-
});
|
10
|
-
}
|
11
|
-
|
12
|
-
async set(key: string, value: string): Promise<void> {
|
13
|
-
return SecureStore.setItemAsync(key, value, {
|
14
|
-
requireAuthentication: false,
|
15
|
-
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
|
16
|
-
});
|
17
|
-
}
|
18
|
-
|
19
|
-
async delete(key: string): Promise<void> {
|
20
|
-
return SecureStore.deleteItemAsync(key, {
|
21
|
-
requireAuthentication: false,
|
22
|
-
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
|
23
|
-
});
|
24
|
-
}
|
25
|
-
|
26
|
-
async clearAll(): Promise<void> {
|
27
|
-
throw new Error("Not implemented");
|
28
|
-
}
|
29
|
-
}
|