jazz-react-native 0.9.23 → 0.10.0

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.
@@ -1,79 +0,0 @@
1
- import NetInfo from "@react-native-community/netinfo";
2
- import { Peer } from "cojson";
3
- import { createWebSocketPeer } from "cojson-transport-ws";
4
-
5
- export function createWebSocketPeerWithReconnection(
6
- peer: string,
7
- reconnectionTimeout: number | undefined,
8
- addPeer: (peer: Peer) => void,
9
- ) {
10
- const firstWsPeer = createWebSocketPeer({
11
- websocket: new WebSocket(peer),
12
- id: peer,
13
- role: "server",
14
- onClose: reconnectWebSocket,
15
- });
16
-
17
- const initialReconnectionTimeout = reconnectionTimeout || 500;
18
- let shouldTryToReconnect = true;
19
- let currentReconnectionTimeout = initialReconnectionTimeout;
20
-
21
- const unsubscribeNetworkChange = NetInfo.addEventListener((state) => {
22
- if (state.isConnected) {
23
- currentReconnectionTimeout = initialReconnectionTimeout;
24
- }
25
- });
26
-
27
- async function reconnectWebSocket() {
28
- if (!shouldTryToReconnect) return;
29
-
30
- console.log(
31
- "Websocket disconnected, trying to reconnect in " +
32
- currentReconnectionTimeout +
33
- "ms",
34
- );
35
- currentReconnectionTimeout = Math.min(
36
- currentReconnectionTimeout * 2,
37
- 30000,
38
- );
39
-
40
- await waitForOnline(currentReconnectionTimeout);
41
-
42
- if (!shouldTryToReconnect) return;
43
-
44
- addPeer(
45
- createWebSocketPeer({
46
- websocket: new WebSocket(peer),
47
- id: peer,
48
- role: "server",
49
- onClose: reconnectWebSocket,
50
- }),
51
- );
52
- }
53
-
54
- return {
55
- peer: firstWsPeer,
56
- done: () => {
57
- shouldTryToReconnect = false;
58
- unsubscribeNetworkChange();
59
- },
60
- };
61
- }
62
-
63
- function waitForOnline(timeout: number) {
64
- return new Promise<void>((resolve) => {
65
- const unsubscribeNetworkChange = NetInfo.addEventListener((state) => {
66
- if (state.isConnected) {
67
- handleTimeoutOrOnline();
68
- }
69
- });
70
-
71
- function handleTimeoutOrOnline() {
72
- clearTimeout(timer);
73
- unsubscribeNetworkChange();
74
- resolve();
75
- }
76
-
77
- const timer = setTimeout(handleTimeoutOrOnline, timeout);
78
- });
79
- }
@@ -1,309 +0,0 @@
1
- import { AgentSecret } from "cojson";
2
- import { Account, ID } from "jazz-tools";
3
- import { beforeEach, describe, expect, it, vi } from "vitest";
4
- import { RNDemoAuth, encodeUsername } from "../auth/DemoAuthMethod";
5
- import { KvStore, KvStoreContext } from "../storage/kv-store-context";
6
-
7
- // Initialize mock storage
8
- const mockStorage: { [key: string]: string } = {};
9
-
10
- function validateKey(key: string) {
11
- if (key.includes("+") || key.includes("/") || key.includes("=")) {
12
- throw new Error("Invalid key");
13
- }
14
- }
15
-
16
- // Mock KvStore implementation
17
- const mockKvStore: KvStore = {
18
- get: vi.fn(async (key: string) => {
19
- validateKey(key);
20
- return mockStorage[key] || null;
21
- }),
22
- set: vi.fn(async (key: string, value: string) => {
23
- validateKey(key);
24
-
25
- mockStorage[key] = value;
26
- }),
27
- delete: vi.fn(async (key: string) => {
28
- validateKey(key);
29
- delete mockStorage[key];
30
- }),
31
- clearAll: vi.fn(async () => {
32
- Object.keys(mockStorage).forEach((key) => delete mockStorage[key]);
33
- }),
34
- };
35
-
36
- KvStoreContext.getInstance().initialize(mockKvStore);
37
-
38
- beforeEach(() => {
39
- mockKvStore.clearAll();
40
- vi.clearAllMocks();
41
- });
42
-
43
- function setup() {
44
- const mockDriver: RNDemoAuth.Driver = {
45
- onReady: vi.fn(),
46
- onSignedIn: vi.fn(),
47
- onError: vi.fn(),
48
- };
49
-
50
- return {
51
- mockStorage,
52
- mockKvStore,
53
- mockDriver,
54
- };
55
- }
56
-
57
- describe("RNDemoAuth", () => {
58
- describe("initialization", () => {
59
- it("should initialize with seed accounts", async () => {
60
- const { mockKvStore, mockDriver } = setup();
61
-
62
- const seedAccounts = {
63
- testUser: {
64
- accountID: "test-account-id" as ID<Account>,
65
- accountSecret: "test-secret" as AgentSecret,
66
- },
67
- };
68
-
69
- await RNDemoAuth.init(mockDriver, seedAccounts);
70
-
71
- expect(mockKvStore.set).toHaveBeenCalledWith(
72
- "demo-auth-existing-users",
73
- "testUser",
74
- );
75
- expect(mockKvStore.set).toHaveBeenCalledWith(
76
- "demo-auth-existing-users-" + encodeUsername("testUser"),
77
- expect.any(String),
78
- );
79
- });
80
- });
81
-
82
- describe("authentication", () => {
83
- it("should handle new user signup", async () => {
84
- const { mockDriver } = setup();
85
-
86
- mockDriver.onReady = vi.fn(({ signUp }) => {
87
- signUp("testUser");
88
- });
89
-
90
- const auth = await RNDemoAuth.init(mockDriver);
91
- const result = await auth.start();
92
-
93
- expect(mockDriver.onReady).toHaveBeenCalled();
94
- expect(result.type).toBe("new");
95
- expect(result.saveCredentials).toBeDefined();
96
- });
97
-
98
- it("should convert unsupported chars from base64 to a valid key", async () => {
99
- const { mockDriver } = setup();
100
-
101
- mockDriver.onReady = vi.fn(({ signUp }) => {
102
- signUp(atob("+/=="));
103
- });
104
-
105
- const auth = await RNDemoAuth.init(mockDriver);
106
- const result = await auth.start();
107
-
108
- expect(mockDriver.onReady).toHaveBeenCalled();
109
- expect(result.type).toBe("new");
110
- expect(result.saveCredentials).toBeDefined();
111
- });
112
-
113
- it("should handle existing user login", async () => {
114
- const { mockStorage, mockDriver } = setup();
115
-
116
- // Set up existing user in storage
117
- const existingUser = {
118
- accountID: "test-account-id" as ID<Account>,
119
- accountSecret: "test-secret" as AgentSecret,
120
- };
121
-
122
- mockStorage["demo-auth-logged-in-secret"] = JSON.stringify(existingUser);
123
-
124
- const auth = await RNDemoAuth.init(mockDriver);
125
- const result = await auth.start();
126
-
127
- if (result.type !== "existing") {
128
- throw new Error("Result is not a existing user");
129
- }
130
-
131
- expect(result.type).toBe("existing");
132
- expect(result.credentials).toEqual({
133
- accountID: existingUser.accountID,
134
- secret: existingUser.accountSecret,
135
- });
136
- });
137
-
138
- it("should handle logout", async () => {
139
- const { mockKvStore, mockDriver } = setup();
140
-
141
- mockDriver.onReady = vi.fn(({ signUp }) => {
142
- signUp("testUser");
143
- });
144
-
145
- const auth = await RNDemoAuth.init(mockDriver);
146
- const result = await auth.start();
147
-
148
- await result.logOut();
149
- expect(mockKvStore.delete).toHaveBeenCalledWith(
150
- "demo-auth-logged-in-secret",
151
- );
152
- });
153
- });
154
-
155
- describe("user management", () => {
156
- it("should signup a new user", async () => {
157
- const { mockKvStore, mockDriver } = setup();
158
-
159
- mockDriver.onReady = vi.fn(({ signUp }) => {
160
- return signUp("testUser");
161
- });
162
- const auth = await RNDemoAuth.init(mockDriver);
163
- const result = await auth.start();
164
-
165
- if (result.type !== "new") {
166
- throw new Error("Result is not a new user");
167
- }
168
-
169
- await result.saveCredentials({
170
- accountID: "test-account-id" as ID<Account>,
171
- secret: "test-secret" as AgentSecret,
172
- });
173
-
174
- expect(mockKvStore.set).toHaveBeenCalledWith(
175
- "demo-auth-existing-users-" + encodeUsername("testUser"),
176
- expect.any(String),
177
- );
178
-
179
- expect(mockKvStore.set).toHaveBeenCalledWith(
180
- "demo-auth-existing-users",
181
- "testUser",
182
- );
183
-
184
- expect(mockKvStore.set).toHaveBeenCalledWith(
185
- "demo-auth-logged-in-secret",
186
- expect.any(String),
187
- );
188
- });
189
-
190
- it("should login an existing user", async () => {
191
- const { mockStorage, mockKvStore, mockDriver } = setup();
192
-
193
- const credentials = {
194
- accountID: "test-account-id" as ID<Account>,
195
- accountSecret: "test-secret" as AgentSecret,
196
- };
197
-
198
- mockStorage["demo-auth-existing-users-" + encodeUsername("testUser")] =
199
- JSON.stringify(credentials);
200
-
201
- mockDriver.onReady = vi.fn(({ logInAs }) => {
202
- return logInAs("testUser");
203
- });
204
-
205
- const auth = await RNDemoAuth.init(mockDriver);
206
- const result = await auth.start();
207
-
208
- if (result.type !== "existing") {
209
- throw new Error("Result is not a existing user");
210
- }
211
-
212
- expect(result.credentials).toEqual({
213
- accountID: credentials.accountID,
214
- secret: credentials.accountSecret,
215
- });
216
-
217
- expect(mockKvStore.set).toHaveBeenCalledWith(
218
- "demo-auth-logged-in-secret",
219
- JSON.stringify(credentials),
220
- );
221
- });
222
-
223
- it("should handle duplicate usernames by adding suffix", async () => {
224
- const { mockStorage, mockKvStore, mockDriver } = setup();
225
-
226
- mockDriver.onReady = vi.fn(({ signUp }) => {
227
- return signUp("testUser");
228
- });
229
- mockStorage["demo-auth-existing-users"] = "testUser";
230
-
231
- const auth = await RNDemoAuth.init(mockDriver);
232
- const result = await auth.start();
233
-
234
- if (result.type !== "new") {
235
- throw new Error("Result is not a new user");
236
- }
237
-
238
- await result.saveCredentials({
239
- accountID: "test-account-id" as ID<Account>,
240
- secret: "test-secret" as AgentSecret,
241
- });
242
-
243
- expect(mockKvStore.set).toHaveBeenCalledWith(
244
- "demo-auth-existing-users-" + encodeUsername("testUser-2"),
245
- expect.any(String),
246
- );
247
-
248
- expect(mockKvStore.set).toHaveBeenCalledWith(
249
- "demo-auth-existing-users",
250
- "testUser,testUser-2",
251
- );
252
- });
253
-
254
- it("should retrieve existing users", async () => {
255
- const { mockStorage, mockDriver } = setup();
256
-
257
- mockDriver.onReady = vi.fn(({ signUp }) => {
258
- return signUp("testUser");
259
- });
260
-
261
- mockStorage["demo-auth-existing-users"] = "user1,user2,user3";
262
-
263
- const auth = await RNDemoAuth.init(mockDriver);
264
- const result = await auth.start();
265
-
266
- if (result.type !== "new") {
267
- throw new Error("Result is not a new user");
268
- }
269
-
270
- await result.saveCredentials({
271
- accountID: "test-account-id" as ID<Account>,
272
- secret: "test-secret" as AgentSecret,
273
- });
274
-
275
- const onReadyCall = vi.mocked(mockDriver.onReady).mock.calls[0]![0];
276
- const existingUsers = await onReadyCall.getExistingUsers();
277
-
278
- expect(existingUsers).toEqual(["user1", "user2", "user3", "testUser"]);
279
- });
280
-
281
- it("should migrate legacy user keys to the new format", async () => {
282
- const { mockStorage, mockKvStore, mockDriver } = setup();
283
-
284
- const value = JSON.stringify({
285
- accountID: "test-account-id" as ID<Account>,
286
- accountSecret: "test-secret" as AgentSecret,
287
- });
288
-
289
- mockStorage["demo-auth-existing-users"] = "testUser";
290
- mockStorage["demo-auth-existing-users-testUser"] = value;
291
-
292
- await RNDemoAuth.init(mockDriver);
293
-
294
- expect(mockKvStore.set).toHaveBeenCalledWith(
295
- "demo-auth-existing-users-" + encodeUsername("testUser"),
296
- value,
297
- );
298
-
299
- expect(mockKvStore.set).toHaveBeenCalledWith(
300
- "demo-auth-storage-version",
301
- "2",
302
- );
303
-
304
- expect(mockKvStore.delete).toHaveBeenCalledWith(
305
- "demo-auth-existing-users-testUser",
306
- );
307
- });
308
- });
309
- });
@@ -1,163 +0,0 @@
1
- // @vitest-environment happy-dom
2
-
3
- import NetInfo, {
4
- NetInfoChangeHandler,
5
- NetInfoState,
6
- } from "@react-native-community/netinfo";
7
- import { createWebSocketPeer } from "cojson-transport-ws";
8
- import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
9
- import { createWebSocketPeerWithReconnection } from "../createWebSocketPeerWithReconnection.js";
10
-
11
- // Mock WebSocket
12
- class MockWebSocket {
13
- addEventListener = vi.fn();
14
- removeEventListener = vi.fn();
15
- close = vi.fn();
16
- readyState = 1;
17
- }
18
-
19
- vi.mock("@react-native-community/netinfo", async () => {
20
- return {
21
- default: {
22
- addEventListener: vi.fn(),
23
- },
24
- };
25
- });
26
-
27
- vi.mock("cojson-transport-ws", () => ({
28
- createWebSocketPeer: vi.fn().mockImplementation(({ onClose }) => ({
29
- id: "test-peer",
30
- incoming: { push: vi.fn() },
31
- outgoing: { push: vi.fn(), close: vi.fn() },
32
- onClose,
33
- })),
34
- }));
35
-
36
- let listeners = new Set<NetInfoChangeHandler>();
37
- let unsubscribe: (() => void) | undefined = undefined;
38
-
39
- function mockNetInfo() {
40
- listeners.clear();
41
- vi.mocked(NetInfo.addEventListener).mockImplementation((listener) => {
42
- listeners.add(listener);
43
- unsubscribe = vi.fn();
44
- return unsubscribe;
45
- });
46
- }
47
-
48
- function dispatchNetInfoChange(isConnected: boolean) {
49
- listeners.forEach((listener) => listener({ isConnected } as NetInfoState));
50
- }
51
-
52
- describe("createWebSocketPeerWithReconnection", () => {
53
- beforeEach(() => {
54
- vi.clearAllMocks();
55
- vi.stubGlobal("WebSocket", MockWebSocket);
56
- mockNetInfo();
57
- });
58
-
59
- afterEach(() => {
60
- vi.useRealTimers();
61
- });
62
-
63
- test("should reset reconnection timeout when coming online", async () => {
64
- vi.useFakeTimers();
65
- const addPeerMock = vi.fn();
66
-
67
- const { done } = createWebSocketPeerWithReconnection(
68
- "ws://localhost:8080",
69
- 500,
70
- addPeerMock,
71
- );
72
-
73
- // Simulate multiple disconnections to increase timeout
74
- const initialPeer = vi.mocked(createWebSocketPeer).mock.results[0]!.value;
75
- initialPeer.onClose();
76
-
77
- await vi.advanceTimersByTimeAsync(1000);
78
-
79
- expect(addPeerMock).toHaveBeenCalledTimes(1);
80
-
81
- vi.mocked(createWebSocketPeer).mock.results[1]!.value.onClose();
82
- await vi.advanceTimersByTimeAsync(2000);
83
-
84
- expect(addPeerMock).toHaveBeenCalledTimes(2);
85
-
86
- // Resets the timeout to initial value
87
- dispatchNetInfoChange(true);
88
-
89
- // Next reconnection should use initial timeout
90
- vi.mocked(createWebSocketPeer).mock.results[2]!.value.onClose();
91
- await vi.advanceTimersByTimeAsync(1000);
92
-
93
- expect(addPeerMock).toHaveBeenCalledTimes(3);
94
-
95
- done();
96
- });
97
-
98
- test("should wait for online event or timeout before reconnecting", async () => {
99
- vi.useFakeTimers();
100
-
101
- const addPeerMock = vi.fn();
102
- const { done } = createWebSocketPeerWithReconnection(
103
- "ws://localhost:8080",
104
- 500,
105
- addPeerMock,
106
- );
107
-
108
- const initialPeer = vi.mocked(createWebSocketPeer).mock.results[0]!.value;
109
-
110
- // Simulate offline state
111
- vi.stubGlobal("navigator", { onLine: false });
112
-
113
- initialPeer.onClose();
114
-
115
- // Advance timer but not enough to trigger reconnection
116
- await vi.advanceTimersByTimeAsync(500);
117
- expect(createWebSocketPeer).toHaveBeenCalledTimes(1);
118
-
119
- // Simulate coming back online
120
- dispatchNetInfoChange(true);
121
-
122
- // Wait for event loop to settle
123
- await Promise.resolve().then();
124
-
125
- // Should reconnect immediately after coming online
126
- expect(createWebSocketPeer).toHaveBeenCalledTimes(2);
127
-
128
- done();
129
- });
130
-
131
- test("should clean up event listeners when done", () => {
132
- const addPeerMock = vi.fn();
133
- const { done } = createWebSocketPeerWithReconnection(
134
- "ws://localhost:8080",
135
- 1000,
136
- addPeerMock,
137
- );
138
-
139
- done();
140
-
141
- expect(unsubscribe).toHaveBeenCalled();
142
- });
143
-
144
- test("should not attempt reconnection after done is called", async () => {
145
- vi.useFakeTimers();
146
-
147
- const addPeerMock = vi.fn();
148
- const { done } = createWebSocketPeerWithReconnection(
149
- "ws://localhost:8080",
150
- 500,
151
- addPeerMock,
152
- );
153
-
154
- const initialPeer = vi.mocked(createWebSocketPeer).mock.results[0]!.value;
155
-
156
- done();
157
-
158
- initialPeer.onClose();
159
- await vi.advanceTimersByTimeAsync(1000);
160
-
161
- expect(createWebSocketPeer).toHaveBeenCalledTimes(1);
162
- });
163
- });