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 +22 -0
- package/dist/createWebSocketPeerWithReconnection.d.ts +5 -0
- package/dist/createWebSocketPeerWithReconnection.js +58 -0
- package/dist/createWebSocketPeerWithReconnection.js.map +1 -0
- package/dist/index.js +7 -49
- package/dist/index.js.map +1 -1
- package/dist/tests/createWebSocketPeerWithReconnection.test.d.ts +1 -0
- package/dist/tests/createWebSocketPeerWithReconnection.test.js +106 -0
- package/dist/tests/createWebSocketPeerWithReconnection.test.js.map +1 -0
- package/package.json +9 -9
- package/src/createWebSocketPeerWithReconnection.ts +79 -0
- package/src/index.ts +12 -65
- package/src/tests/createWebSocketPeerWithReconnection.test.ts +163 -0
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,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
|
12
|
-
|
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: [
|
18
|
+
peersToLoadFrom: [websocketPeer.peer],
|
30
19
|
sessionProvider: provideLockSession,
|
31
20
|
})
|
32
21
|
: await createJazzContext({
|
33
22
|
crypto: await PureJSCrypto.create(),
|
34
|
-
peersToLoadFrom: [
|
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
|
-
|
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
|
-
|
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":"AAAA,uDAAuD;AACvD,sDAAsD;AACtD,OAAO,EAWL,eAAe,EACf,iBAAiB,GAClB,MAAM,YAAY,CAAC;
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -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.
|
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.
|
20
|
-
"cojson-transport-ws": "0.8.
|
21
|
-
"jazz-tools": "0.8.
|
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.
|
31
|
-
"expo-linking": "~
|
32
|
-
"expo-secure-store": "~
|
33
|
-
"react-native": "~0.
|
34
|
-
"typescript": "
|
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
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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: [
|
84
|
+
peersToLoadFrom: [websocketPeer.peer],
|
92
85
|
sessionProvider: provideLockSession,
|
93
86
|
})
|
94
87
|
: await createJazzContext({
|
95
88
|
crypto: await PureJSCrypto.create(),
|
96
|
-
peersToLoadFrom: [
|
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
|
-
|
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
|
-
|
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
|
+
});
|