jazz-react-native 0.8.14 → 0.8.16

Sign up to get free protection for your applications and to get access to all the features.
package/src/index.ts CHANGED
@@ -1,25 +1,25 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  /* eslint-disable @typescript-eslint/no-unused-vars */
3
3
  import {
4
- CoValue,
5
- ID,
6
- AgentID,
7
- SessionID,
8
- cojsonInternals,
9
- InviteSecret,
10
- Account,
11
- CoValueClass,
12
- CryptoProvider,
13
- AuthMethod,
14
- createJazzContext,
15
- AnonymousJazzAgent,
4
+ Account,
5
+ AgentID,
6
+ AnonymousJazzAgent,
7
+ AuthMethod,
8
+ CoValue,
9
+ CoValueClass,
10
+ CryptoProvider,
11
+ ID,
12
+ InviteSecret,
13
+ SessionID,
14
+ cojsonInternals,
15
+ createJazzContext,
16
16
  } from "jazz-tools";
17
17
 
18
- import { PureJSCrypto } from "jazz-tools/native";
18
+ import NetInfo from "@react-native-community/netinfo";
19
19
  import { RawAccountID } from "cojson";
20
20
  import { createWebSocketPeer } from "cojson-transport-ws";
21
- import NetInfo from "@react-native-community/netinfo";
22
21
  import * as Linking from "expo-linking";
22
+ import { PureJSCrypto } from "jazz-tools/native";
23
23
 
24
24
  export { RNDemoAuth } from "./auth/DemoAuthMethod.js";
25
25
 
@@ -27,244 +27,240 @@ import { KvStoreContext } from "./storage/kv-store-context.js";
27
27
 
28
28
  /** @category Context Creation */
29
29
  export type BrowserContext<Acc extends Account> = {
30
- me: Acc;
31
- logOut: () => void;
32
- // TODO: Symbol.dispose?
33
- done: () => void;
30
+ me: Acc;
31
+ logOut: () => void;
32
+ // TODO: Symbol.dispose?
33
+ done: () => void;
34
34
  };
35
35
 
36
36
  export type BrowserGuestContext = {
37
- guest: AnonymousJazzAgent;
38
- logOut: () => void;
39
- done: () => void;
37
+ guest: AnonymousJazzAgent;
38
+ logOut: () => void;
39
+ done: () => void;
40
40
  };
41
41
 
42
42
  export type BrowserContextOptions<Acc extends Account> = {
43
- auth: AuthMethod;
44
- AccountSchema: CoValueClass<Acc> & {
45
- fromNode: (typeof Account)["fromNode"];
46
- };
43
+ auth: AuthMethod;
44
+ AccountSchema: CoValueClass<Acc> & {
45
+ fromNode: (typeof Account)["fromNode"];
46
+ };
47
47
  } & BaseBrowserContextOptions;
48
48
 
49
49
  export type BaseBrowserContextOptions = {
50
- peer: `wss://${string}` | `ws://${string}`;
51
- reconnectionTimeout?: number;
52
- storage?: "indexedDB" | "singleTabOPFS";
53
- crypto?: CryptoProvider;
50
+ peer: `wss://${string}` | `ws://${string}`;
51
+ reconnectionTimeout?: number;
52
+ storage?: "indexedDB" | "singleTabOPFS";
53
+ crypto?: CryptoProvider;
54
54
  };
55
55
 
56
56
  /** @category Context Creation */
57
57
  export async function createJazzRNContext<Acc extends Account>(
58
- options: BrowserContextOptions<Acc>,
58
+ options: BrowserContextOptions<Acc>,
59
59
  ): Promise<BrowserContext<Acc>>;
60
60
  export async function createJazzRNContext(
61
- options: BaseBrowserContextOptions,
61
+ options: BaseBrowserContextOptions,
62
62
  ): Promise<BrowserGuestContext>;
63
63
  export async function createJazzRNContext<Acc extends Account>(
64
- options: BrowserContextOptions<Acc> | BaseBrowserContextOptions,
64
+ options: BrowserContextOptions<Acc> | BaseBrowserContextOptions,
65
65
  ): Promise<BrowserContext<Acc> | BrowserGuestContext>;
66
66
  export async function createJazzRNContext<Acc extends Account>(
67
- options: BrowserContextOptions<Acc> | BaseBrowserContextOptions,
67
+ options: BrowserContextOptions<Acc> | BaseBrowserContextOptions,
68
68
  ): Promise<BrowserContext<Acc> | BrowserGuestContext> {
69
- const firstWsPeer = createWebSocketPeer({
70
- websocket: new WebSocket(options.peer),
71
- id: options.peer + "@" + new Date().toISOString(),
72
- role: "server",
73
- expectPings: true,
74
- });
75
- let shouldTryToReconnect = true;
76
-
77
- let currentReconnectionTimeout = options.reconnectionTimeout || 500;
78
-
79
- const unsubscribeNetworkChange = NetInfo.addEventListener((state) => {
80
- if (state.isConnected) {
81
- currentReconnectionTimeout = options.reconnectionTimeout || 500;
82
- }
83
- });
84
-
85
- const context =
86
- "auth" in options
87
- ? await createJazzContext({
88
- AccountSchema: options.AccountSchema,
89
- auth: options.auth,
90
- crypto: await PureJSCrypto.create(),
91
- peersToLoadFrom: [firstWsPeer],
92
- sessionProvider: provideLockSession,
93
- })
94
- : await createJazzContext({
95
- crypto: await PureJSCrypto.create(),
96
- peersToLoadFrom: [firstWsPeer],
97
- });
98
-
99
- const node =
100
- "account" in context
101
- ? context.account._raw.core.node
102
- : context.agent.node;
103
-
104
- async function websocketReconnectLoop() {
105
- while (shouldTryToReconnect) {
106
- if (
107
- Object.keys(node.syncManager.peers).some((peerId) =>
108
- peerId.includes(options.peer),
109
- )
110
- ) {
111
- // TODO: this might drain battery, use listeners instead
112
- await new Promise((resolve) => setTimeout(resolve, 100));
113
- } else {
114
- console.log(
115
- "Websocket disconnected, trying to reconnect in " +
116
- currentReconnectionTimeout +
117
- "ms",
118
- );
119
- currentReconnectionTimeout = Math.min(
120
- currentReconnectionTimeout * 2,
121
- 30000,
122
- );
123
- await new Promise<void>((resolve) => {
124
- setTimeout(resolve, currentReconnectionTimeout);
125
- const _unsubscribeNetworkChange = NetInfo.addEventListener(
126
- (state) => {
127
- if (state.isConnected) {
128
- resolve();
129
- _unsubscribeNetworkChange();
130
- }
131
- },
132
- );
133
- });
134
-
135
- node.syncManager.addPeer(
136
- createWebSocketPeer({
137
- websocket: new WebSocket(options.peer),
138
- id: options.peer + "@" + new Date().toISOString(),
139
- role: "server",
140
- }),
141
- );
142
- }
143
- }
69
+ const firstWsPeer = createWebSocketPeer({
70
+ websocket: new WebSocket(options.peer),
71
+ id: options.peer + "@" + new Date().toISOString(),
72
+ role: "server",
73
+ expectPings: true,
74
+ });
75
+ let shouldTryToReconnect = true;
76
+
77
+ let currentReconnectionTimeout = options.reconnectionTimeout || 500;
78
+
79
+ const unsubscribeNetworkChange = NetInfo.addEventListener((state) => {
80
+ if (state.isConnected) {
81
+ currentReconnectionTimeout = options.reconnectionTimeout || 500;
144
82
  }
145
-
146
- void websocketReconnectLoop();
147
-
148
- return "account" in context
149
- ? {
150
- me: context.account,
151
- done: () => {
152
- shouldTryToReconnect = false;
153
- unsubscribeNetworkChange?.();
154
- context.done();
155
- },
156
- logOut: () => {
157
- context.logOut();
158
- },
159
- }
160
- : {
161
- guest: context.agent,
162
- done: () => {
163
- shouldTryToReconnect = false;
164
- unsubscribeNetworkChange?.();
165
- context.done();
166
- },
167
- logOut: () => {
168
- context.logOut();
169
- },
170
- };
83
+ });
84
+
85
+ const context =
86
+ "auth" in options
87
+ ? await createJazzContext({
88
+ AccountSchema: options.AccountSchema,
89
+ auth: options.auth,
90
+ crypto: await PureJSCrypto.create(),
91
+ peersToLoadFrom: [firstWsPeer],
92
+ sessionProvider: provideLockSession,
93
+ })
94
+ : await createJazzContext({
95
+ crypto: await PureJSCrypto.create(),
96
+ peersToLoadFrom: [firstWsPeer],
97
+ });
98
+
99
+ const node =
100
+ "account" in context ? context.account._raw.core.node : context.agent.node;
101
+
102
+ async function websocketReconnectLoop() {
103
+ while (shouldTryToReconnect) {
104
+ if (
105
+ Object.keys(node.syncManager.peers).some((peerId) =>
106
+ peerId.includes(options.peer),
107
+ )
108
+ ) {
109
+ // TODO: this might drain battery, use listeners instead
110
+ await new Promise((resolve) => setTimeout(resolve, 100));
111
+ } else {
112
+ console.log(
113
+ "Websocket disconnected, trying to reconnect in " +
114
+ currentReconnectionTimeout +
115
+ "ms",
116
+ );
117
+ currentReconnectionTimeout = Math.min(
118
+ currentReconnectionTimeout * 2,
119
+ 30000,
120
+ );
121
+ await new Promise<void>((resolve) => {
122
+ setTimeout(resolve, currentReconnectionTimeout);
123
+ const _unsubscribeNetworkChange = NetInfo.addEventListener(
124
+ (state) => {
125
+ if (state.isConnected) {
126
+ resolve();
127
+ _unsubscribeNetworkChange();
128
+ }
129
+ },
130
+ );
131
+ });
132
+
133
+ node.syncManager.addPeer(
134
+ createWebSocketPeer({
135
+ websocket: new WebSocket(options.peer),
136
+ id: options.peer + "@" + new Date().toISOString(),
137
+ role: "server",
138
+ }),
139
+ );
140
+ }
141
+ }
142
+ }
143
+
144
+ void websocketReconnectLoop();
145
+
146
+ return "account" in context
147
+ ? {
148
+ me: context.account,
149
+ done: () => {
150
+ shouldTryToReconnect = false;
151
+ unsubscribeNetworkChange?.();
152
+ context.done();
153
+ },
154
+ logOut: () => {
155
+ context.logOut();
156
+ },
157
+ }
158
+ : {
159
+ guest: context.agent,
160
+ done: () => {
161
+ shouldTryToReconnect = false;
162
+ unsubscribeNetworkChange?.();
163
+ context.done();
164
+ },
165
+ logOut: () => {
166
+ context.logOut();
167
+ },
168
+ };
171
169
  }
172
170
 
173
171
  /** @category Auth Providers */
174
172
  export type SessionProvider = (
175
- accountID: ID<Account> | AgentID,
173
+ accountID: ID<Account> | AgentID,
176
174
  ) => Promise<SessionID>;
177
175
 
178
176
  export async function provideLockSession(
179
- accountID: ID<Account> | AgentID,
180
- crypto: CryptoProvider,
177
+ accountID: ID<Account> | AgentID,
178
+ crypto: CryptoProvider,
181
179
  ) {
182
- const sessionDone = () => {};
180
+ const sessionDone = () => {};
183
181
 
184
- const kvStore = KvStoreContext.getInstance().getStorage();
182
+ const kvStore = KvStoreContext.getInstance().getStorage();
185
183
 
186
- const sessionID =
187
- ((await kvStore.get(accountID)) as SessionID) ||
188
- crypto.newRandomSessionID(accountID as RawAccountID | AgentID);
189
- await kvStore.set(accountID, sessionID);
184
+ const sessionID =
185
+ ((await kvStore.get(accountID)) as SessionID) ||
186
+ crypto.newRandomSessionID(accountID as RawAccountID | AgentID);
187
+ await kvStore.set(accountID, sessionID);
190
188
 
191
- return Promise.resolve({
192
- sessionID,
193
- sessionDone,
194
- });
189
+ return Promise.resolve({
190
+ sessionID,
191
+ sessionDone,
192
+ });
195
193
  }
196
194
 
197
195
  const window = {
198
- location: {
199
- href: "#",
200
- },
201
- history: {
202
- replaceState: (a: any, b: any, c: any) => {},
203
- },
196
+ location: {
197
+ href: "#",
198
+ },
199
+ history: {
200
+ replaceState: (a: any, b: any, c: any) => {},
201
+ },
204
202
  };
205
203
 
206
204
  /** @category Invite Links */
207
205
  export function createInviteLink<C extends CoValue>(
208
- value: C,
209
- role: "reader" | "writer" | "admin",
210
- { baseURL, valueHint }: { baseURL?: string; valueHint?: string } = {},
206
+ value: C,
207
+ role: "reader" | "writer" | "admin",
208
+ { baseURL, valueHint }: { baseURL?: string; valueHint?: string } = {},
211
209
  ): string {
212
- const coValueCore = value._raw.core;
213
- let currentCoValue = coValueCore;
210
+ const coValueCore = value._raw.core;
211
+ let currentCoValue = coValueCore;
214
212
 
215
- while (currentCoValue.header.ruleset.type === "ownedByGroup") {
216
- currentCoValue = currentCoValue.getGroup().core;
217
- }
213
+ while (currentCoValue.header.ruleset.type === "ownedByGroup") {
214
+ currentCoValue = currentCoValue.getGroup().core;
215
+ }
218
216
 
219
- if (currentCoValue.header.ruleset.type !== "group") {
220
- throw new Error("Can't create invite link for object without group");
221
- }
217
+ if (currentCoValue.header.ruleset.type !== "group") {
218
+ throw new Error("Can't create invite link for object without group");
219
+ }
222
220
 
223
- const group = cojsonInternals.expectGroup(
224
- currentCoValue.getCurrentContent(),
225
- );
226
- const inviteSecret = group.createInvite(role);
221
+ const group = cojsonInternals.expectGroup(currentCoValue.getCurrentContent());
222
+ const inviteSecret = group.createInvite(role);
227
223
 
228
- return `${baseURL}/invite/${valueHint ? valueHint + "/" : ""}${
229
- value.id
230
- }/${inviteSecret}`;
224
+ return `${baseURL}/invite/${valueHint ? valueHint + "/" : ""}${
225
+ value.id
226
+ }/${inviteSecret}`;
231
227
  }
232
228
 
233
229
  /** @category Invite Links */
234
230
  export function parseInviteLink<C extends CoValue>(
235
- inviteURL: string,
231
+ inviteURL: string,
236
232
  ):
237
- | {
238
- valueID: ID<C>;
239
- valueHint?: string;
240
- inviteSecret: InviteSecret;
241
- }
242
- | undefined {
243
- const url = Linking.parse(inviteURL);
244
- const parts = url.path?.split("/");
245
-
246
- if (!parts || parts[0] !== "invite") {
247
- return undefined;
248
- }
249
-
250
- let valueHint: string | undefined;
251
- let valueID: ID<C> | undefined;
252
- let inviteSecret: InviteSecret | undefined;
253
-
254
- if (parts.length === 4) {
255
- valueHint = parts[1];
256
- valueID = parts[2] as ID<C>;
257
- inviteSecret = parts[3] as InviteSecret;
258
- } else if (parts.length === 3) {
259
- valueID = parts[1] as ID<C>;
260
- inviteSecret = parts[2] as InviteSecret;
261
- }
262
-
263
- if (!valueID || !inviteSecret) {
264
- return undefined;
233
+ | {
234
+ valueID: ID<C>;
235
+ valueHint?: string;
236
+ inviteSecret: InviteSecret;
265
237
  }
266
-
267
- return { valueID, inviteSecret, valueHint };
238
+ | undefined {
239
+ const url = Linking.parse(inviteURL);
240
+ const parts = url.path?.split("/");
241
+
242
+ if (!parts || parts[0] !== "invite") {
243
+ return undefined;
244
+ }
245
+
246
+ let valueHint: string | undefined;
247
+ let valueID: ID<C> | undefined;
248
+ let inviteSecret: InviteSecret | undefined;
249
+
250
+ if (parts.length === 4) {
251
+ valueHint = parts[1];
252
+ valueID = parts[2] as ID<C>;
253
+ inviteSecret = parts[3] as InviteSecret;
254
+ } else if (parts.length === 3) {
255
+ valueID = parts[1] as ID<C>;
256
+ inviteSecret = parts[2] as InviteSecret;
257
+ }
258
+
259
+ if (!valueID || !inviteSecret) {
260
+ return undefined;
261
+ }
262
+
263
+ return { valueID, inviteSecret, valueHint };
268
264
  }
269
265
 
270
266
  /////////
package/src/media.tsx CHANGED
@@ -1,68 +1,67 @@
1
- import React, { useEffect, useState } from "react";
2
1
  import { ImageDefinition } from "jazz-tools";
2
+ import React, { useEffect, useState } from "react";
3
3
 
4
4
  /** @category Media */
5
5
  export function useProgressiveImg({
6
- image,
7
- maxWidth,
6
+ image,
7
+ maxWidth,
8
8
  }: {
9
- image: ImageDefinition | null | undefined;
10
- maxWidth?: number;
9
+ image: ImageDefinition | null | undefined;
10
+ maxWidth?: number;
11
11
  }) {
12
- const [current, setCurrent] = useState<
13
- | { src?: string; res?: `${number}x${number}` | "placeholder" }
14
- | undefined
15
- >(undefined);
12
+ const [current, setCurrent] = useState<
13
+ { src?: string; res?: `${number}x${number}` | "placeholder" } | undefined
14
+ >(undefined);
16
15
 
17
- useEffect(() => {
18
- let lastHighestRes: string | undefined;
19
- if (!image) return;
20
- const unsub = image.subscribe({}, (update) => {
21
- const highestRes = update?.highestResAvailable({ maxWidth });
22
- if (highestRes) {
23
- if (highestRes.res !== lastHighestRes) {
24
- lastHighestRes = highestRes.res;
25
- const blob = highestRes.stream.toBlob();
26
- if (blob) {
27
- const blobURI = URL.createObjectURL(blob);
28
- setCurrent({ src: blobURI, res: highestRes.res });
29
- return () => {
30
- setTimeout(() => URL.revokeObjectURL(blobURI), 200);
31
- };
32
- }
33
- }
34
- } else {
35
- setCurrent({
36
- src: update?.placeholderDataURL,
37
- res: "placeholder",
38
- });
39
- }
16
+ useEffect(() => {
17
+ let lastHighestRes: string | undefined;
18
+ if (!image) return;
19
+ const unsub = image.subscribe({}, (update) => {
20
+ const highestRes = update?.highestResAvailable({ maxWidth });
21
+ if (highestRes) {
22
+ if (highestRes.res !== lastHighestRes) {
23
+ lastHighestRes = highestRes.res;
24
+ const blob = highestRes.stream.toBlob();
25
+ if (blob) {
26
+ const blobURI = URL.createObjectURL(blob);
27
+ setCurrent({ src: blobURI, res: highestRes.res });
28
+ return () => {
29
+ setTimeout(() => URL.revokeObjectURL(blobURI), 200);
30
+ };
31
+ }
32
+ }
33
+ } else {
34
+ setCurrent({
35
+ src: update?.placeholderDataURL,
36
+ res: "placeholder",
40
37
  });
38
+ }
39
+ });
41
40
 
42
- return unsub;
43
- }, [image?.id, maxWidth]);
41
+ return unsub;
42
+ }, [image?.id, maxWidth]);
44
43
 
45
- return {
46
- src: current?.src,
47
- res: current?.res,
48
- originalSize: image?.originalSize,
49
- };
44
+ return {
45
+ src: current?.src,
46
+ res: current?.res,
47
+ originalSize: image?.originalSize,
48
+ };
50
49
  }
51
50
 
52
51
  /** @category Media */
53
52
  export function ProgressiveImg({
54
- children,
55
- image,
56
- maxWidth,
53
+ children,
54
+ image,
55
+ maxWidth,
57
56
  }: {
58
- children: (result: {
59
- src: string | undefined;
60
- res: `${number}x${number}` | "placeholder" | undefined;
61
- originalSize: readonly [number, number] | undefined;
62
- }) => React.ReactNode;
63
- image: ImageDefinition | null | undefined;
64
- maxWidth?: number;
57
+ children: (result: {
58
+ src: string | undefined;
59
+ res: `${number}x${number}` | "placeholder" | undefined;
60
+ originalSize: readonly [number, number] | undefined;
61
+ }) => React.ReactNode;
62
+ image: ImageDefinition | null | undefined;
63
+ maxWidth?: number;
65
64
  }) {
66
- const result = useProgressiveImg({ image, maxWidth });
67
- return result && children(result);
65
+ const result = useProgressiveImg({ image, maxWidth });
66
+ return result && children(result);
68
67
  }