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/CHANGELOG.md +471 -455
- package/dist/auth/DemoAuthMethod.js +1 -2
- package/dist/auth/DemoAuthMethod.js.map +1 -1
- package/dist/auth/DemoAuthUI.d.ts +1 -1
- package/dist/auth/DemoAuthUI.js +25 -31
- package/dist/auth/DemoAuthUI.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -5
- package/dist/index.js.map +1 -1
- package/dist/media.d.ts +1 -1
- package/dist/media.js.map +1 -1
- package/dist/provider.js +2 -4
- package/dist/provider.js.map +1 -1
- package/dist/storage/expo-secure-store-adapter.js.map +1 -1
- package/dist/storage/kv-store-context.js.map +1 -1
- package/package.json +7 -11
- package/src/auth/DemoAuthMethod.ts +168 -187
- package/src/auth/DemoAuthUI.tsx +239 -253
- package/src/index.ts +198 -202
- package/src/media.tsx +50 -51
- package/src/provider.tsx +265 -274
- package/src/storage/expo-secure-store-adapter.ts +21 -21
- package/src/storage/kv-store-context.ts +21 -21
- package/tsconfig.json +4 -8
- package/.eslintrc.cjs +0 -24
- package/.prettierrc.js +0 -9
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
me: Acc;
|
31
|
+
logOut: () => void;
|
32
|
+
// TODO: Symbol.dispose?
|
33
|
+
done: () => void;
|
34
34
|
};
|
35
35
|
|
36
36
|
export type BrowserGuestContext = {
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
guest: AnonymousJazzAgent;
|
38
|
+
logOut: () => void;
|
39
|
+
done: () => void;
|
40
40
|
};
|
41
41
|
|
42
42
|
export type BrowserContextOptions<Acc extends Account> = {
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
58
|
+
options: BrowserContextOptions<Acc>,
|
59
59
|
): Promise<BrowserContext<Acc>>;
|
60
60
|
export async function createJazzRNContext(
|
61
|
-
|
61
|
+
options: BaseBrowserContextOptions,
|
62
62
|
): Promise<BrowserGuestContext>;
|
63
63
|
export async function createJazzRNContext<Acc extends Account>(
|
64
|
-
|
64
|
+
options: BrowserContextOptions<Acc> | BaseBrowserContextOptions,
|
65
65
|
): Promise<BrowserContext<Acc> | BrowserGuestContext>;
|
66
66
|
export async function createJazzRNContext<Acc extends Account>(
|
67
|
-
|
67
|
+
options: BrowserContextOptions<Acc> | BaseBrowserContextOptions,
|
68
68
|
): Promise<BrowserContext<Acc> | BrowserGuestContext> {
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
173
|
+
accountID: ID<Account> | AgentID,
|
176
174
|
) => Promise<SessionID>;
|
177
175
|
|
178
176
|
export async function provideLockSession(
|
179
|
-
|
180
|
-
|
177
|
+
accountID: ID<Account> | AgentID,
|
178
|
+
crypto: CryptoProvider,
|
181
179
|
) {
|
182
|
-
|
180
|
+
const sessionDone = () => {};
|
183
181
|
|
184
|
-
|
182
|
+
const kvStore = KvStoreContext.getInstance().getStorage();
|
185
183
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
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
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
189
|
+
return Promise.resolve({
|
190
|
+
sessionID,
|
191
|
+
sessionDone,
|
192
|
+
});
|
195
193
|
}
|
196
194
|
|
197
195
|
const window = {
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
206
|
+
value: C,
|
207
|
+
role: "reader" | "writer" | "admin",
|
208
|
+
{ baseURL, valueHint }: { baseURL?: string; valueHint?: string } = {},
|
211
209
|
): string {
|
212
|
-
|
213
|
-
|
210
|
+
const coValueCore = value._raw.core;
|
211
|
+
let currentCoValue = coValueCore;
|
214
212
|
|
215
|
-
|
216
|
-
|
217
|
-
|
213
|
+
while (currentCoValue.header.ruleset.type === "ownedByGroup") {
|
214
|
+
currentCoValue = currentCoValue.getGroup().core;
|
215
|
+
}
|
218
216
|
|
219
|
-
|
220
|
-
|
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
|
-
|
224
|
-
|
225
|
-
);
|
226
|
-
const inviteSecret = group.createInvite(role);
|
221
|
+
const group = cojsonInternals.expectGroup(currentCoValue.getCurrentContent());
|
222
|
+
const inviteSecret = group.createInvite(role);
|
227
223
|
|
228
|
-
|
229
|
-
|
230
|
-
|
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
|
-
|
231
|
+
inviteURL: string,
|
236
232
|
):
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
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
|
-
|
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
|
-
|
7
|
-
|
6
|
+
image,
|
7
|
+
maxWidth,
|
8
8
|
}: {
|
9
|
-
|
10
|
-
|
9
|
+
image: ImageDefinition | null | undefined;
|
10
|
+
maxWidth?: number;
|
11
11
|
}) {
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
>(undefined);
|
12
|
+
const [current, setCurrent] = useState<
|
13
|
+
{ src?: string; res?: `${number}x${number}` | "placeholder" } | undefined
|
14
|
+
>(undefined);
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
43
|
-
|
41
|
+
return unsub;
|
42
|
+
}, [image?.id, maxWidth]);
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
53
|
+
children,
|
54
|
+
image,
|
55
|
+
maxWidth,
|
57
56
|
}: {
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
67
|
-
|
65
|
+
const result = useProgressiveImg({ image, maxWidth });
|
66
|
+
return result && children(result);
|
68
67
|
}
|