jazz-react-native 0.8.38 → 0.8.41

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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # jazz-browser
2
2
 
3
+ ## 0.8.41
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [3252502]
8
+ - Updated dependencies [6370348]
9
+ - Updated dependencies [ac216b9]
10
+ - cojson@0.8.41
11
+ - cojson-transport-ws@0.8.41
12
+ - jazz-tools@0.8.41
13
+
14
+ ## 0.8.39
15
+
16
+ ### Patch Changes
17
+
18
+ - 0c6b0f3: Reconnect automatically when the WebSocket is closed by the server
19
+ - Updated dependencies [249eecb]
20
+ - Updated dependencies [3121551]
21
+ - jazz-tools@0.8.39
22
+ - cojson@0.8.39
23
+ - cojson-transport-ws@0.8.39
24
+
3
25
  ## 0.8.38
4
26
 
5
27
  ### Patch Changes
@@ -0,0 +1,5 @@
1
+ import { Peer } from "cojson";
2
+ export declare function createWebSocketPeerWithReconnection(peer: string, reconnectionTimeout: number | undefined, addPeer: (peer: Peer) => void): {
3
+ peer: Peer;
4
+ done: () => void;
5
+ };
@@ -0,0 +1,58 @@
1
+ import NetInfo from "@react-native-community/netinfo";
2
+ import { createWebSocketPeer } from "cojson-transport-ws";
3
+ export function createWebSocketPeerWithReconnection(peer, reconnectionTimeout, addPeer) {
4
+ const firstWsPeer = createWebSocketPeer({
5
+ websocket: new WebSocket(peer),
6
+ id: peer,
7
+ role: "server",
8
+ onClose: reconnectWebSocket,
9
+ });
10
+ const initialReconnectionTimeout = reconnectionTimeout || 500;
11
+ let shouldTryToReconnect = true;
12
+ let currentReconnectionTimeout = initialReconnectionTimeout;
13
+ const unsubscribeNetworkChange = NetInfo.addEventListener((state) => {
14
+ if (state.isConnected) {
15
+ currentReconnectionTimeout = initialReconnectionTimeout;
16
+ }
17
+ });
18
+ async function reconnectWebSocket() {
19
+ if (!shouldTryToReconnect)
20
+ return;
21
+ console.log("Websocket disconnected, trying to reconnect in " +
22
+ currentReconnectionTimeout +
23
+ "ms");
24
+ currentReconnectionTimeout = Math.min(currentReconnectionTimeout * 2, 30000);
25
+ await waitForOnline(currentReconnectionTimeout);
26
+ if (!shouldTryToReconnect)
27
+ return;
28
+ addPeer(createWebSocketPeer({
29
+ websocket: new WebSocket(peer),
30
+ id: peer,
31
+ role: "server",
32
+ onClose: reconnectWebSocket,
33
+ }));
34
+ }
35
+ return {
36
+ peer: firstWsPeer,
37
+ done: () => {
38
+ shouldTryToReconnect = false;
39
+ unsubscribeNetworkChange();
40
+ },
41
+ };
42
+ }
43
+ function waitForOnline(timeout) {
44
+ return new Promise((resolve) => {
45
+ const unsubscribeNetworkChange = NetInfo.addEventListener((state) => {
46
+ if (state.isConnected) {
47
+ handleTimeoutOrOnline();
48
+ }
49
+ });
50
+ function handleTimeoutOrOnline() {
51
+ clearTimeout(timer);
52
+ unsubscribeNetworkChange();
53
+ resolve();
54
+ }
55
+ const timer = setTimeout(handleTimeoutOrOnline, timeout);
56
+ });
57
+ }
58
+ //# sourceMappingURL=createWebSocketPeerWithReconnection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createWebSocketPeerWithReconnection.js","sourceRoot":"","sources":["../src/createWebSocketPeerWithReconnection.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,iCAAiC,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,MAAM,UAAU,mCAAmC,CACjD,IAAY,EACZ,mBAAuC,EACvC,OAA6B;IAE7B,MAAM,WAAW,GAAG,mBAAmB,CAAC;QACtC,SAAS,EAAE,IAAI,SAAS,CAAC,IAAI,CAAC;QAC9B,EAAE,EAAE,IAAI;QACR,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,kBAAkB;KAC5B,CAAC,CAAC;IAEH,MAAM,0BAA0B,GAAG,mBAAmB,IAAI,GAAG,CAAC;IAC9D,IAAI,oBAAoB,GAAG,IAAI,CAAC;IAChC,IAAI,0BAA0B,GAAG,0BAA0B,CAAC;IAE5D,MAAM,wBAAwB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC,KAAK,EAAE,EAAE;QAClE,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YACtB,0BAA0B,GAAG,0BAA0B,CAAC;QAC1D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,KAAK,UAAU,kBAAkB;QAC/B,IAAI,CAAC,oBAAoB;YAAE,OAAO;QAElC,OAAO,CAAC,GAAG,CACT,iDAAiD;YAC/C,0BAA0B;YAC1B,IAAI,CACP,CAAC;QACF,0BAA0B,GAAG,IAAI,CAAC,GAAG,CACnC,0BAA0B,GAAG,CAAC,EAC9B,KAAK,CACN,CAAC;QAEF,MAAM,aAAa,CAAC,0BAA0B,CAAC,CAAC;QAEhD,IAAI,CAAC,oBAAoB;YAAE,OAAO;QAElC,OAAO,CACL,mBAAmB,CAAC;YAClB,SAAS,EAAE,IAAI,SAAS,CAAC,IAAI,CAAC;YAC9B,EAAE,EAAE,IAAI;YACR,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,kBAAkB;SAC5B,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,GAAG,EAAE;YACT,oBAAoB,GAAG,KAAK,CAAC;YAC7B,wBAAwB,EAAE,CAAC;QAC7B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IACpC,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACnC,MAAM,wBAAwB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC,KAAK,EAAE,EAAE;YAClE,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;gBACtB,qBAAqB,EAAE,CAAC;YAC1B,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,SAAS,qBAAqB;YAC5B,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,wBAAwB,EAAE,CAAC;YAC3B,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,qBAAqB,EAAE,OAAO,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/index.js CHANGED
@@ -1,74 +1,33 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  /* eslint-disable @typescript-eslint/no-unused-vars */
3
3
  import { cojsonInternals, createJazzContext, } from "jazz-tools";
4
- import NetInfo from "@react-native-community/netinfo";
5
- import { createWebSocketPeer } from "cojson-transport-ws";
6
4
  import * as Linking from "expo-linking";
7
5
  import { PureJSCrypto } from "jazz-tools/native";
8
6
  export { RNDemoAuth } from "./auth/DemoAuthMethod.js";
7
+ import { createWebSocketPeerWithReconnection } from "./createWebSocketPeerWithReconnection.js";
9
8
  import { KvStoreContext } from "./storage/kv-store-context.js";
10
9
  export async function createJazzRNContext(options) {
11
- const firstWsPeer = createWebSocketPeer({
12
- websocket: new WebSocket(options.peer),
13
- id: options.peer + "@" + new Date().toISOString(),
14
- role: "server",
15
- expectPings: true,
16
- });
17
- let shouldTryToReconnect = true;
18
- let currentReconnectionTimeout = options.reconnectionTimeout || 500;
19
- const unsubscribeNetworkChange = NetInfo.addEventListener((state) => {
20
- if (state.isConnected) {
21
- currentReconnectionTimeout = options.reconnectionTimeout || 500;
22
- }
10
+ const websocketPeer = createWebSocketPeerWithReconnection(options.peer, options.reconnectionTimeout, (peer) => {
11
+ node.syncManager.addPeer(peer);
23
12
  });
24
13
  const context = "auth" in options
25
14
  ? await createJazzContext({
26
15
  AccountSchema: options.AccountSchema,
27
16
  auth: options.auth,
28
17
  crypto: await PureJSCrypto.create(),
29
- peersToLoadFrom: [firstWsPeer],
18
+ peersToLoadFrom: [websocketPeer.peer],
30
19
  sessionProvider: provideLockSession,
31
20
  })
32
21
  : await createJazzContext({
33
22
  crypto: await PureJSCrypto.create(),
34
- peersToLoadFrom: [firstWsPeer],
23
+ peersToLoadFrom: [websocketPeer.peer],
35
24
  });
36
25
  const node = "account" in context ? context.account._raw.core.node : context.agent.node;
37
- async function websocketReconnectLoop() {
38
- while (shouldTryToReconnect) {
39
- if (Object.keys(node.syncManager.peers).some((peerId) => peerId.includes(options.peer))) {
40
- // TODO: this might drain battery, use listeners instead
41
- await new Promise((resolve) => setTimeout(resolve, 100));
42
- }
43
- else {
44
- console.log("Websocket disconnected, trying to reconnect in " +
45
- currentReconnectionTimeout +
46
- "ms");
47
- currentReconnectionTimeout = Math.min(currentReconnectionTimeout * 2, 30000);
48
- await new Promise((resolve) => {
49
- setTimeout(resolve, currentReconnectionTimeout);
50
- const _unsubscribeNetworkChange = NetInfo.addEventListener((state) => {
51
- if (state.isConnected) {
52
- resolve();
53
- _unsubscribeNetworkChange();
54
- }
55
- });
56
- });
57
- node.syncManager.addPeer(createWebSocketPeer({
58
- websocket: new WebSocket(options.peer),
59
- id: options.peer + "@" + new Date().toISOString(),
60
- role: "server",
61
- }));
62
- }
63
- }
64
- }
65
- void websocketReconnectLoop();
66
26
  return "account" in context
67
27
  ? {
68
28
  me: context.account,
69
29
  done: () => {
70
- shouldTryToReconnect = false;
71
- unsubscribeNetworkChange?.();
30
+ websocketPeer.done();
72
31
  context.done();
73
32
  },
74
33
  logOut: () => {
@@ -78,8 +37,7 @@ export async function createJazzRNContext(options) {
78
37
  : {
79
38
  guest: context.agent,
80
39
  done: () => {
81
- shouldTryToReconnect = false;
82
- unsubscribeNetworkChange?.();
40
+ websocketPeer.done();
83
41
  context.done();
84
42
  },
85
43
  logOut: () => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ // @vitest-environment happy-dom
2
+ import NetInfo from "@react-native-community/netinfo";
3
+ import { createWebSocketPeer } from "cojson-transport-ws";
4
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
5
+ import { createWebSocketPeerWithReconnection } from "../createWebSocketPeerWithReconnection.js";
6
+ // Mock WebSocket
7
+ class MockWebSocket {
8
+ addEventListener = vi.fn();
9
+ removeEventListener = vi.fn();
10
+ close = vi.fn();
11
+ readyState = 1;
12
+ }
13
+ vi.mock("@react-native-community/netinfo", async () => {
14
+ return {
15
+ default: {
16
+ addEventListener: vi.fn(),
17
+ },
18
+ };
19
+ });
20
+ vi.mock("cojson-transport-ws", () => ({
21
+ createWebSocketPeer: vi.fn().mockImplementation(({ onClose }) => ({
22
+ id: "test-peer",
23
+ incoming: { push: vi.fn() },
24
+ outgoing: { push: vi.fn(), close: vi.fn() },
25
+ onClose,
26
+ })),
27
+ }));
28
+ let listeners = new Set();
29
+ let unsubscribe = undefined;
30
+ function mockNetInfo() {
31
+ listeners.clear();
32
+ vi.mocked(NetInfo.addEventListener).mockImplementation((listener) => {
33
+ listeners.add(listener);
34
+ unsubscribe = vi.fn();
35
+ return unsubscribe;
36
+ });
37
+ }
38
+ function dispatchNetInfoChange(isConnected) {
39
+ listeners.forEach((listener) => listener({ isConnected }));
40
+ }
41
+ describe("createWebSocketPeerWithReconnection", () => {
42
+ beforeEach(() => {
43
+ vi.clearAllMocks();
44
+ vi.stubGlobal("WebSocket", MockWebSocket);
45
+ mockNetInfo();
46
+ });
47
+ afterEach(() => {
48
+ vi.useRealTimers();
49
+ });
50
+ test("should reset reconnection timeout when coming online", async () => {
51
+ vi.useFakeTimers();
52
+ const addPeerMock = vi.fn();
53
+ const { done } = createWebSocketPeerWithReconnection("ws://localhost:8080", 500, addPeerMock);
54
+ // Simulate multiple disconnections to increase timeout
55
+ const initialPeer = vi.mocked(createWebSocketPeer).mock.results[0].value;
56
+ initialPeer.onClose();
57
+ await vi.advanceTimersByTimeAsync(1000);
58
+ expect(addPeerMock).toHaveBeenCalledTimes(1);
59
+ vi.mocked(createWebSocketPeer).mock.results[1].value.onClose();
60
+ await vi.advanceTimersByTimeAsync(2000);
61
+ expect(addPeerMock).toHaveBeenCalledTimes(2);
62
+ // Resets the timeout to initial value
63
+ dispatchNetInfoChange(true);
64
+ // Next reconnection should use initial timeout
65
+ vi.mocked(createWebSocketPeer).mock.results[2].value.onClose();
66
+ await vi.advanceTimersByTimeAsync(1000);
67
+ expect(addPeerMock).toHaveBeenCalledTimes(3);
68
+ done();
69
+ });
70
+ test("should wait for online event or timeout before reconnecting", async () => {
71
+ vi.useFakeTimers();
72
+ const addPeerMock = vi.fn();
73
+ const { done } = createWebSocketPeerWithReconnection("ws://localhost:8080", 500, addPeerMock);
74
+ const initialPeer = vi.mocked(createWebSocketPeer).mock.results[0].value;
75
+ // Simulate offline state
76
+ vi.stubGlobal("navigator", { onLine: false });
77
+ initialPeer.onClose();
78
+ // Advance timer but not enough to trigger reconnection
79
+ await vi.advanceTimersByTimeAsync(500);
80
+ expect(createWebSocketPeer).toHaveBeenCalledTimes(1);
81
+ // Simulate coming back online
82
+ dispatchNetInfoChange(true);
83
+ // Wait for event loop to settle
84
+ await Promise.resolve().then();
85
+ // Should reconnect immediately after coming online
86
+ expect(createWebSocketPeer).toHaveBeenCalledTimes(2);
87
+ done();
88
+ });
89
+ test("should clean up event listeners when done", () => {
90
+ const addPeerMock = vi.fn();
91
+ const { done } = createWebSocketPeerWithReconnection("ws://localhost:8080", 1000, addPeerMock);
92
+ done();
93
+ expect(unsubscribe).toHaveBeenCalled();
94
+ });
95
+ test("should not attempt reconnection after done is called", async () => {
96
+ vi.useFakeTimers();
97
+ const addPeerMock = vi.fn();
98
+ const { done } = createWebSocketPeerWithReconnection("ws://localhost:8080", 500, addPeerMock);
99
+ const initialPeer = vi.mocked(createWebSocketPeer).mock.results[0].value;
100
+ done();
101
+ initialPeer.onClose();
102
+ await vi.advanceTimersByTimeAsync(1000);
103
+ expect(createWebSocketPeer).toHaveBeenCalledTimes(1);
104
+ });
105
+ });
106
+ //# sourceMappingURL=createWebSocketPeerWithReconnection.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createWebSocketPeerWithReconnection.test.js","sourceRoot":"","sources":["../../src/tests/createWebSocketPeerWithReconnection.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jazz-react-native",
3
- "version": "0.8.38",
3
+ "version": "0.8.41",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -16,9 +16,9 @@
16
16
  "license": "MIT",
17
17
  "dependencies": {
18
18
  "@scure/bip39": "^1.3.0",
19
- "cojson": "0.8.38",
20
- "cojson-transport-ws": "0.8.38",
21
- "jazz-tools": "0.8.38"
19
+ "cojson": "0.8.41",
20
+ "cojson-transport-ws": "0.8.41",
21
+ "jazz-tools": "0.8.41"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "@react-native-community/netinfo": "*",
@@ -27,11 +27,11 @@
27
27
  "react-native": "*"
28
28
  },
29
29
  "devDependencies": {
30
- "@react-native-community/netinfo": "^11.3.1",
31
- "expo-linking": "~6.3.1",
32
- "expo-secure-store": "~13.0.2",
33
- "react-native": "~0.74.5",
34
- "typescript": "^5.3.3"
30
+ "@react-native-community/netinfo": "^11.4.1",
31
+ "expo-linking": "~7.0.3",
32
+ "expo-secure-store": "~14.0.0",
33
+ "react-native": "~0.76.3",
34
+ "typescript": "~5.6.2"
35
35
  },
36
36
  "gitHead": "33c27053293b4801b968c61d5c4c989f93a67d13",
37
37
  "scripts": {
@@ -0,0 +1,79 @@
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
+ }
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ import { PureJSCrypto } from "jazz-tools/native";
23
23
 
24
24
  export { RNDemoAuth } from "./auth/DemoAuthMethod.js";
25
25
 
26
+ import { createWebSocketPeerWithReconnection } from "./createWebSocketPeerWithReconnection.js";
26
27
  import { KvStoreContext } from "./storage/kv-store-context.js";
27
28
 
28
29
  /** @category Context Creation */
@@ -66,21 +67,13 @@ export async function createJazzRNContext<Acc extends Account>(
66
67
  export async function createJazzRNContext<Acc extends Account>(
67
68
  options: ReactNativeContextOptions<Acc> | BaseReactNativeContextOptions,
68
69
  ): Promise<ReactNativeContext<Acc> | ReactNativeGuestContext> {
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
- });
70
+ const websocketPeer = createWebSocketPeerWithReconnection(
71
+ options.peer,
72
+ options.reconnectionTimeout,
73
+ (peer) => {
74
+ node.syncManager.addPeer(peer);
75
+ },
76
+ );
84
77
 
85
78
  const context =
86
79
  "auth" in options
@@ -88,67 +81,22 @@ export async function createJazzRNContext<Acc extends Account>(
88
81
  AccountSchema: options.AccountSchema,
89
82
  auth: options.auth,
90
83
  crypto: await PureJSCrypto.create(),
91
- peersToLoadFrom: [firstWsPeer],
84
+ peersToLoadFrom: [websocketPeer.peer],
92
85
  sessionProvider: provideLockSession,
93
86
  })
94
87
  : await createJazzContext({
95
88
  crypto: await PureJSCrypto.create(),
96
- peersToLoadFrom: [firstWsPeer],
89
+ peersToLoadFrom: [websocketPeer.peer],
97
90
  });
98
91
 
99
92
  const node =
100
93
  "account" in context ? context.account._raw.core.node : context.agent.node;
101
94
 
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
95
  return "account" in context
147
96
  ? {
148
97
  me: context.account,
149
98
  done: () => {
150
- shouldTryToReconnect = false;
151
- unsubscribeNetworkChange?.();
99
+ websocketPeer.done();
152
100
  context.done();
153
101
  },
154
102
  logOut: () => {
@@ -158,8 +106,7 @@ export async function createJazzRNContext<Acc extends Account>(
158
106
  : {
159
107
  guest: context.agent,
160
108
  done: () => {
161
- shouldTryToReconnect = false;
162
- unsubscribeNetworkChange?.();
109
+ websocketPeer.done();
163
110
  context.done();
164
111
  },
165
112
  logOut: () => {
@@ -0,0 +1,163 @@
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
+ });