jazz-react-native 0.8.14 → 0.8.16

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/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
  }