jazz-react-native 0.8.3

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