jazz-react-native 0.8.15 → 0.8.16

Sign up to get free protection for your applications and to get access to all the features.
package/src/provider.tsx CHANGED
@@ -1,307 +1,298 @@
1
1
  import React, { useEffect, useState } from "react";
2
2
 
3
3
  import {
4
- Account,
5
- AccountClass,
6
- AnonymousJazzAgent,
7
- AuthMethod,
8
- CoValue,
9
- CoValueClass,
10
- DeeplyLoaded,
11
- DepthsIn,
12
- ID,
13
- subscribeToCoValue,
4
+ Account,
5
+ AccountClass,
6
+ AnonymousJazzAgent,
7
+ AuthMethod,
8
+ CoValue,
9
+ CoValueClass,
10
+ DeeplyLoaded,
11
+ DepthsIn,
12
+ ID,
13
+ subscribeToCoValue,
14
14
  } from "jazz-tools";
15
+ import { Linking } from "react-native";
15
16
  import {
16
- BrowserContext,
17
- BrowserGuestContext,
18
- createJazzRNContext,
19
- KvStoreContext,
20
- parseInviteLink,
17
+ BrowserContext,
18
+ BrowserGuestContext,
19
+ KvStoreContext,
20
+ createJazzRNContext,
21
+ parseInviteLink,
21
22
  } from "./index.js";
22
- import { Linking } from "react-native";
23
23
  import { ExpoSecureStoreAdapter } from "./storage/expo-secure-store-adapter.js";
24
24
 
25
25
  /** @category Context & Hooks */
26
26
  export function createJazzRNApp<Acc extends Account>({
27
- kvStore = new ExpoSecureStoreAdapter(),
28
- AccountSchema = Account as unknown as AccountClass<Acc>,
27
+ kvStore = new ExpoSecureStoreAdapter(),
28
+ AccountSchema = Account as unknown as AccountClass<Acc>,
29
29
  } = {}): JazzReactApp<Acc> {
30
- const JazzContext = React.createContext<
31
- BrowserContext<Acc> | BrowserGuestContext | undefined
32
- >(undefined);
33
-
34
- if (!kvStore) {
35
- throw new Error("kvStore is required");
30
+ const JazzContext = React.createContext<
31
+ BrowserContext<Acc> | BrowserGuestContext | undefined
32
+ >(undefined);
33
+
34
+ if (!kvStore) {
35
+ throw new Error("kvStore is required");
36
+ }
37
+
38
+ KvStoreContext.getInstance().initialize(kvStore);
39
+
40
+ function Provider({
41
+ children,
42
+ auth,
43
+ peer,
44
+ storage,
45
+ }: {
46
+ children: React.ReactNode;
47
+ auth: AuthMethod | "guest";
48
+ peer: `wss://${string}` | `ws://${string}`;
49
+ storage?: "indexedDB" | "singleTabOPFS";
50
+ }) {
51
+ const [ctx, setCtx] = useState<
52
+ BrowserContext<Acc> | BrowserGuestContext | undefined
53
+ >();
54
+
55
+ const [sessionCount, setSessionCount] = useState(0);
56
+
57
+ useEffect(() => {
58
+ const promiseWithDoneCallback = createJazzRNContext<Acc>(
59
+ auth === "guest"
60
+ ? {
61
+ peer,
62
+ storage,
63
+ }
64
+ : {
65
+ AccountSchema,
66
+ auth: auth,
67
+ peer,
68
+ storage,
69
+ },
70
+ ).then((context) => {
71
+ setCtx({
72
+ ...context,
73
+ logOut: () => {
74
+ context.logOut();
75
+ setCtx(undefined);
76
+ setSessionCount(sessionCount + 1);
77
+ },
78
+ });
79
+ return context.done;
80
+ });
81
+
82
+ return () => {
83
+ void promiseWithDoneCallback.then((done) => done());
84
+ };
85
+ }, [AccountSchema, auth, peer, storage, sessionCount]);
86
+
87
+ return (
88
+ <JazzContext.Provider value={ctx}>{ctx && children}</JazzContext.Provider>
89
+ );
90
+ }
91
+
92
+ function useAccount(): { me: Acc; logOut: () => void };
93
+ function useAccount<D extends DepthsIn<Acc>>(
94
+ depth: D,
95
+ ): { me: DeeplyLoaded<Acc, D> | undefined; logOut: () => void };
96
+ function useAccount<D extends DepthsIn<Acc>>(
97
+ depth?: D,
98
+ ): { me: Acc | DeeplyLoaded<Acc, D> | undefined; logOut: () => void } {
99
+ const context = React.useContext(JazzContext);
100
+
101
+ if (!context) {
102
+ throw new Error("useAccount must be used within a JazzProvider");
36
103
  }
37
104
 
38
- KvStoreContext.getInstance().initialize(kvStore);
39
-
40
- function Provider({
41
- children,
42
- auth,
43
- peer,
44
- storage,
45
- }: {
46
- children: React.ReactNode;
47
- auth: AuthMethod | "guest";
48
- peer: `wss://${string}` | `ws://${string}`;
49
- storage?: "indexedDB" | "singleTabOPFS";
50
- }) {
51
- const [ctx, setCtx] = useState<
52
- BrowserContext<Acc> | BrowserGuestContext | undefined
53
- >();
54
-
55
- const [sessionCount, setSessionCount] = useState(0);
56
-
57
- useEffect(() => {
58
- const promiseWithDoneCallback = createJazzRNContext<Acc>(
59
- auth === "guest"
60
- ? {
61
- peer,
62
- storage,
63
- }
64
- : {
65
- AccountSchema,
66
- auth: auth,
67
- peer,
68
- storage,
69
- },
70
- ).then((context) => {
71
- setCtx({
72
- ...context,
73
- logOut: () => {
74
- context.logOut();
75
- setCtx(undefined);
76
- setSessionCount(sessionCount + 1);
77
- },
78
- });
79
- return context.done;
80
- });
81
-
82
- return () => {
83
- void promiseWithDoneCallback.then((done) => done());
84
- };
85
- }, [AccountSchema, auth, peer, storage, sessionCount]);
86
-
87
- return (
88
- <JazzContext.Provider value={ctx}>
89
- {ctx && children}
90
- </JazzContext.Provider>
91
- );
105
+ if (!("me" in context)) {
106
+ throw new Error(
107
+ "useAccount can't be used in a JazzProvider with auth === 'guest' - consider using useAccountOrGuest()",
108
+ );
92
109
  }
93
110
 
94
- function useAccount(): { me: Acc; logOut: () => void };
95
- function useAccount<D extends DepthsIn<Acc>>(
96
- depth: D,
97
- ): { me: DeeplyLoaded<Acc, D> | undefined; logOut: () => void };
98
- function useAccount<D extends DepthsIn<Acc>>(
99
- depth?: D,
100
- ): { me: Acc | DeeplyLoaded<Acc, D> | undefined; logOut: () => void } {
101
- const context = React.useContext(JazzContext);
102
-
103
- if (!context) {
104
- throw new Error("useAccount must be used within a JazzProvider");
105
- }
106
-
107
- if (!("me" in context)) {
108
- throw new Error(
109
- "useAccount can't be used in a JazzProvider with auth === 'guest' - consider using useAccountOrGuest()",
110
- );
111
- }
111
+ const me = useCoState<Acc, D>(
112
+ context?.me.constructor as CoValueClass<Acc>,
113
+ context?.me.id,
114
+ depth,
115
+ );
112
116
 
113
- const me = useCoState<Acc, D>(
114
- context?.me.constructor as CoValueClass<Acc>,
115
- context?.me.id,
116
- depth,
117
- );
118
-
119
- return {
120
- me: depth === undefined ? me || context.me : me,
121
- logOut: context.logOut,
122
- };
117
+ return {
118
+ me: depth === undefined ? me || context.me : me,
119
+ logOut: context.logOut,
120
+ };
121
+ }
122
+
123
+ function useAccountOrGuest(): { me: Acc | AnonymousJazzAgent };
124
+ function useAccountOrGuest<D extends DepthsIn<Acc>>(
125
+ depth: D,
126
+ ): { me: DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent };
127
+ function useAccountOrGuest<D extends DepthsIn<Acc>>(
128
+ depth?: D,
129
+ ): { me: Acc | DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent } {
130
+ const context = React.useContext(JazzContext);
131
+
132
+ if (!context) {
133
+ throw new Error("useAccountOrGuest must be used within a JazzProvider");
123
134
  }
124
135
 
125
- function useAccountOrGuest(): { me: Acc | AnonymousJazzAgent };
126
- function useAccountOrGuest<D extends DepthsIn<Acc>>(
127
- depth: D,
128
- ): { me: DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent };
129
- function useAccountOrGuest<D extends DepthsIn<Acc>>(
130
- depth?: D,
131
- ): { me: Acc | DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent } {
132
- const context = React.useContext(JazzContext);
133
-
134
- if (!context) {
135
- throw new Error(
136
- "useAccountOrGuest must be used within a JazzProvider",
137
- );
138
- }
139
-
140
- const contextMe = "me" in context ? context.me : undefined;
136
+ const contextMe = "me" in context ? context.me : undefined;
141
137
 
142
- const me = useCoState<Acc, D>(
143
- contextMe?.constructor as CoValueClass<Acc>,
144
- contextMe?.id,
145
- depth,
146
- );
138
+ const me = useCoState<Acc, D>(
139
+ contextMe?.constructor as CoValueClass<Acc>,
140
+ contextMe?.id,
141
+ depth,
142
+ );
147
143
 
148
- if ("me" in context) {
149
- return {
150
- me: depth === undefined ? me || context.me : me,
151
- };
152
- } else {
153
- return { me: context.guest };
154
- }
144
+ if ("me" in context) {
145
+ return {
146
+ me: depth === undefined ? me || context.me : me,
147
+ };
148
+ } else {
149
+ return { me: context.guest };
155
150
  }
156
-
157
- function useCoState<V extends CoValue, D>(
158
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
159
- Schema: CoValueClass<V>,
160
- id: ID<V> | undefined,
161
- depth: D & DepthsIn<V> = [] as D & DepthsIn<V>,
162
- ): DeeplyLoaded<V, D> | undefined {
163
- const [state, setState] = useState<{
164
- value: DeeplyLoaded<V, D> | undefined;
165
- }>({ value: undefined });
166
- const context = React.useContext(JazzContext);
167
-
168
- if (!context) {
169
- throw new Error("useCoState must be used within a JazzProvider");
170
- }
171
-
172
- useEffect(() => {
173
- if (!id) return;
174
-
175
- return subscribeToCoValue(
176
- Schema,
177
- id,
178
- "me" in context ? context.me : context.guest,
179
- depth,
180
- (value) => {
181
- setState({ value });
182
- },
183
- );
184
- }, [Schema, id, context]);
185
-
186
- return state.value;
151
+ }
152
+
153
+ function useCoState<V extends CoValue, D>(
154
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
155
+ Schema: CoValueClass<V>,
156
+ id: ID<V> | undefined,
157
+ depth: D & DepthsIn<V> = [] as D & DepthsIn<V>,
158
+ ): DeeplyLoaded<V, D> | undefined {
159
+ const [state, setState] = useState<{
160
+ value: DeeplyLoaded<V, D> | undefined;
161
+ }>({ value: undefined });
162
+ const context = React.useContext(JazzContext);
163
+
164
+ if (!context) {
165
+ throw new Error("useCoState must be used within a JazzProvider");
187
166
  }
188
167
 
189
- function useAcceptInvite<V extends CoValue>({
190
- invitedObjectSchema,
191
- onAccept,
192
- forValueHint,
193
- }: {
194
- invitedObjectSchema: CoValueClass<V>;
195
- onAccept: (projectID: ID<V>) => void;
196
- forValueHint?: string;
197
- }): void {
198
- const context = React.useContext(JazzContext);
199
-
200
- if (!context) {
201
- throw new Error(
202
- "useAcceptInvite must be used within a JazzProvider",
203
- );
204
- }
205
-
206
- if (!("me" in context)) {
207
- throw new Error(
208
- "useAcceptInvite can't be used in a JazzProvider with auth === 'guest'.",
209
- );
210
- }
211
-
212
- useEffect(() => {
213
- const handleDeepLink = ({ url }: { url: string }) => {
214
- const result = parseInviteLink<V>(url);
215
- if (result && result.valueHint === forValueHint) {
216
- context.me
217
- .acceptInvite(
218
- result.valueID,
219
- result.inviteSecret,
220
- invitedObjectSchema,
221
- )
222
- .then(() => {
223
- onAccept(result.valueID);
224
- })
225
- .catch((e) => {
226
- console.error("Failed to accept invite", e);
227
- });
228
- }
229
- };
230
-
231
- const linkingListener = Linking.addEventListener(
232
- "url",
233
- handleDeepLink,
234
- );
235
-
236
- void Linking.getInitialURL().then((url) => {
237
- if (url) handleDeepLink({ url });
238
- });
168
+ useEffect(() => {
169
+ if (!id) return;
170
+
171
+ return subscribeToCoValue(
172
+ Schema,
173
+ id,
174
+ "me" in context ? context.me : context.guest,
175
+ depth,
176
+ (value) => {
177
+ setState({ value });
178
+ },
179
+ );
180
+ }, [Schema, id, context]);
181
+
182
+ return state.value;
183
+ }
184
+
185
+ function useAcceptInvite<V extends CoValue>({
186
+ invitedObjectSchema,
187
+ onAccept,
188
+ forValueHint,
189
+ }: {
190
+ invitedObjectSchema: CoValueClass<V>;
191
+ onAccept: (projectID: ID<V>) => void;
192
+ forValueHint?: string;
193
+ }): void {
194
+ const context = React.useContext(JazzContext);
195
+
196
+ if (!context) {
197
+ throw new Error("useAcceptInvite must be used within a JazzProvider");
198
+ }
239
199
 
240
- return () => {
241
- linkingListener.remove();
242
- };
243
- }, [context, onAccept, invitedObjectSchema, forValueHint]);
200
+ if (!("me" in context)) {
201
+ throw new Error(
202
+ "useAcceptInvite can't be used in a JazzProvider with auth === 'guest'.",
203
+ );
244
204
  }
245
205
 
246
- return {
247
- Provider,
248
- useAccount,
249
- useAccountOrGuest,
250
- useCoState,
251
- useAcceptInvite,
252
- };
206
+ useEffect(() => {
207
+ const handleDeepLink = ({ url }: { url: string }) => {
208
+ const result = parseInviteLink<V>(url);
209
+ if (result && result.valueHint === forValueHint) {
210
+ context.me
211
+ .acceptInvite(
212
+ result.valueID,
213
+ result.inviteSecret,
214
+ invitedObjectSchema,
215
+ )
216
+ .then(() => {
217
+ onAccept(result.valueID);
218
+ })
219
+ .catch((e) => {
220
+ console.error("Failed to accept invite", e);
221
+ });
222
+ }
223
+ };
224
+
225
+ const linkingListener = Linking.addEventListener("url", handleDeepLink);
226
+
227
+ void Linking.getInitialURL().then((url) => {
228
+ if (url) handleDeepLink({ url });
229
+ });
230
+
231
+ return () => {
232
+ linkingListener.remove();
233
+ };
234
+ }, [context, onAccept, invitedObjectSchema, forValueHint]);
235
+ }
236
+
237
+ return {
238
+ Provider,
239
+ useAccount,
240
+ useAccountOrGuest,
241
+ useCoState,
242
+ useAcceptInvite,
243
+ };
253
244
  }
254
245
 
255
246
  /** @category Context & Hooks */
256
247
  export interface JazzReactApp<Acc extends Account> {
257
- /** @category Provider Component */
258
- Provider: React.FC<{
259
- children: React.ReactNode;
260
- auth: AuthMethod | "guest";
261
- peer: `wss://${string}` | `ws://${string}`;
262
- storage?: "indexedDB" | "singleTabOPFS";
263
- }>;
264
-
265
- /** @category Hooks */
266
- useAccount(): {
267
- me: Acc;
268
- logOut: () => void;
269
- };
270
- /** @category Hooks */
271
- useAccount<D extends DepthsIn<Acc>>(
272
- depth: D,
273
- ): {
274
- me: DeeplyLoaded<Acc, D> | undefined;
275
- logOut: () => void;
276
- };
277
-
278
- /** @category Hooks */
279
- useAccountOrGuest(): {
280
- me: Acc | AnonymousJazzAgent;
281
- };
282
- useAccountOrGuest<D extends DepthsIn<Acc>>(
283
- depth: D,
284
- ): {
285
- me: DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent;
286
- };
287
- /** @category Hooks */
288
- useCoState<V extends CoValue, D>(
289
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
290
- Schema: { new (...args: any[]): V } & CoValueClass,
291
- id: ID<V> | undefined,
292
- depth?: D & DepthsIn<V>,
293
- ): DeeplyLoaded<V, D> | undefined;
294
-
295
- /** @category Hooks */
296
- useAcceptInvite<V extends CoValue>({
297
- invitedObjectSchema,
298
- onAccept,
299
- forValueHint,
300
- }: {
301
- invitedObjectSchema: CoValueClass<V>;
302
- onAccept: (projectID: ID<V>) => void;
303
- forValueHint?: string;
304
- }): void;
248
+ /** @category Provider Component */
249
+ Provider: React.FC<{
250
+ children: React.ReactNode;
251
+ auth: AuthMethod | "guest";
252
+ peer: `wss://${string}` | `ws://${string}`;
253
+ storage?: "indexedDB" | "singleTabOPFS";
254
+ }>;
255
+
256
+ /** @category Hooks */
257
+ useAccount(): {
258
+ me: Acc;
259
+ logOut: () => void;
260
+ };
261
+ /** @category Hooks */
262
+ useAccount<D extends DepthsIn<Acc>>(
263
+ depth: D,
264
+ ): {
265
+ me: DeeplyLoaded<Acc, D> | undefined;
266
+ logOut: () => void;
267
+ };
268
+
269
+ /** @category Hooks */
270
+ useAccountOrGuest(): {
271
+ me: Acc | AnonymousJazzAgent;
272
+ };
273
+ useAccountOrGuest<D extends DepthsIn<Acc>>(
274
+ depth: D,
275
+ ): {
276
+ me: DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent;
277
+ };
278
+ /** @category Hooks */
279
+ useCoState<V extends CoValue, D>(
280
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
281
+ Schema: { new (...args: any[]): V } & CoValueClass,
282
+ id: ID<V> | undefined,
283
+ depth?: D & DepthsIn<V>,
284
+ ): DeeplyLoaded<V, D> | undefined;
285
+
286
+ /** @category Hooks */
287
+ useAcceptInvite<V extends CoValue>({
288
+ invitedObjectSchema,
289
+ onAccept,
290
+ forValueHint,
291
+ }: {
292
+ invitedObjectSchema: CoValueClass<V>;
293
+ onAccept: (projectID: ID<V>) => void;
294
+ forValueHint?: string;
295
+ }): void;
305
296
  }
306
297
 
307
298
  export * from "./media.js";
@@ -2,28 +2,28 @@ import * as SecureStore from "expo-secure-store";
2
2
  import type { KvStore } from "./kv-store-context.js";
3
3
 
4
4
  export class ExpoSecureStoreAdapter implements KvStore {
5
- get(key: string): Promise<string | null> {
6
- return SecureStore.getItemAsync(key, {
7
- requireAuthentication: SecureStore.canUseBiometricAuthentication(),
8
- keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
9
- });
10
- }
5
+ get(key: string): Promise<string | null> {
6
+ return SecureStore.getItemAsync(key, {
7
+ requireAuthentication: SecureStore.canUseBiometricAuthentication(),
8
+ keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
9
+ });
10
+ }
11
11
 
12
- async set(key: string, value: string): Promise<void> {
13
- return SecureStore.setItemAsync(key, value, {
14
- requireAuthentication: SecureStore.canUseBiometricAuthentication(),
15
- keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
16
- });
17
- }
12
+ async set(key: string, value: string): Promise<void> {
13
+ return SecureStore.setItemAsync(key, value, {
14
+ requireAuthentication: SecureStore.canUseBiometricAuthentication(),
15
+ keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
16
+ });
17
+ }
18
18
 
19
- async delete(key: string): Promise<void> {
20
- return SecureStore.deleteItemAsync(key, {
21
- requireAuthentication: SecureStore.canUseBiometricAuthentication(),
22
- keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
23
- });
24
- }
19
+ async delete(key: string): Promise<void> {
20
+ return SecureStore.deleteItemAsync(key, {
21
+ requireAuthentication: SecureStore.canUseBiometricAuthentication(),
22
+ keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
23
+ });
24
+ }
25
25
 
26
- async clearAll(): Promise<void> {
27
- throw new Error("Not implemented");
28
- }
26
+ async clearAll(): Promise<void> {
27
+ throw new Error("Not implemented");
28
+ }
29
29
  }
@@ -1,35 +1,35 @@
1
1
  export interface KvStore {
2
- get(key: string): Promise<string | null>;
3
- set(key: string, value: string): Promise<void>;
4
- delete(key: string): Promise<void>;
5
- clearAll(): Promise<void>;
2
+ get(key: string): Promise<string | null>;
3
+ set(key: string, value: string): Promise<void>;
4
+ delete(key: string): Promise<void>;
5
+ clearAll(): Promise<void>;
6
6
  }
7
7
 
8
8
  export class KvStoreContext {
9
- private static instance: KvStoreContext;
10
- private storageInstance: KvStore | null = null;
9
+ private static instance: KvStoreContext;
10
+ private storageInstance: KvStore | null = null;
11
11
 
12
- private constructor() {}
12
+ private constructor() {}
13
13
 
14
- public static getInstance(): KvStoreContext {
15
- if (!KvStoreContext.instance) {
16
- KvStoreContext.instance = new KvStoreContext();
17
- }
18
- return KvStoreContext.instance;
14
+ public static getInstance(): KvStoreContext {
15
+ if (!KvStoreContext.instance) {
16
+ KvStoreContext.instance = new KvStoreContext();
19
17
  }
18
+ return KvStoreContext.instance;
19
+ }
20
20
 
21
- public initialize(store: KvStore): void {
22
- if (!this.storageInstance) {
23
- this.storageInstance = store;
24
- }
21
+ public initialize(store: KvStore): void {
22
+ if (!this.storageInstance) {
23
+ this.storageInstance = store;
25
24
  }
25
+ }
26
26
 
27
- public getStorage(): KvStore {
28
- if (!this.storageInstance) {
29
- throw new Error("Storage instance is not initialized.");
30
- }
31
- return this.storageInstance;
27
+ public getStorage(): KvStore {
28
+ if (!this.storageInstance) {
29
+ throw new Error("Storage instance is not initialized.");
32
30
  }
31
+ return this.storageInstance;
32
+ }
33
33
  }
34
34
 
35
35
  export default KvStoreContext;