@peerbit/react 0.0.33 → 0.0.34
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/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -0
- package/{lib/esm → dist/src}/lockstorage.d.ts +1 -0
- package/dist/src/lockstorage.d.ts.map +1 -0
- package/{lib/esm → dist/src}/lockstorage.js +2 -5
- package/dist/src/lockstorage.js.map +1 -0
- package/{lib/esm → dist/src}/useMount.d.ts +1 -0
- package/dist/src/useMount.d.ts.map +1 -0
- package/dist/src/useMount.js.map +1 -0
- package/{lib/esm → dist/src}/usePeer.d.ts +7 -3
- package/dist/src/usePeer.d.ts.map +1 -0
- package/{lib/esm → dist/src}/usePeer.js +25 -20
- package/dist/src/usePeer.js.map +1 -0
- package/{lib/esm → dist/src}/utils.d.ts +2 -4
- package/dist/src/utils.d.ts.map +1 -0
- package/{lib/esm → dist/src}/utils.js +17 -69
- package/dist/src/utils.js.map +1 -0
- package/package.json +59 -56
- package/src/index.ts +4 -14
- package/src/lockstorage.ts +224 -233
- package/src/useMount.ts +15 -0
- package/src/usePeer.tsx +406 -419
- package/src/utils.ts +99 -168
- package/README.md +0 -24
- package/lib/esm/__tests__/lockstorage.test.d.ts +0 -1
- package/lib/esm/__tests__/lockstorage.test.js +0 -237
- package/lib/esm/__tests__/lockstorage.test.js.map +0 -1
- package/lib/esm/__tests__/singletonLock.test.d.ts +0 -1
- package/lib/esm/__tests__/singletonLock.test.js +0 -71
- package/lib/esm/__tests__/singletonLock.test.js.map +0 -1
- package/lib/esm/__tests__/useQuery.dom.test.d.ts +0 -1
- package/lib/esm/__tests__/useQuery.dom.test.js +0 -433
- package/lib/esm/__tests__/useQuery.dom.test.js.map +0 -1
- package/lib/esm/__tests__/utils.test.d.ts +0 -1
- package/lib/esm/__tests__/utils.test.js +0 -66
- package/lib/esm/__tests__/utils.test.js.map +0 -1
- package/lib/esm/index.d.ts +0 -8
- package/lib/esm/index.js +0 -9
- package/lib/esm/index.js.map +0 -1
- package/lib/esm/lockstorage.js.map +0 -1
- package/lib/esm/useCount.d.ts +0 -11
- package/lib/esm/useCount.js +0 -43
- package/lib/esm/useCount.js.map +0 -1
- package/lib/esm/useLocal.d.ts +0 -20
- package/lib/esm/useLocal.js +0 -73
- package/lib/esm/useLocal.js.map +0 -1
- package/lib/esm/useMount.js.map +0 -1
- package/lib/esm/useOnline.d.ts +0 -11
- package/lib/esm/useOnline.js +0 -65
- package/lib/esm/useOnline.js.map +0 -1
- package/lib/esm/usePeer.js.map +0 -1
- package/lib/esm/useProgram.d.ts +0 -16
- package/lib/esm/useProgram.js +0 -114
- package/lib/esm/useProgram.js.map +0 -1
- package/lib/esm/useQuery.d.ts +0 -49
- package/lib/esm/useQuery.js +0 -418
- package/lib/esm/useQuery.js.map +0 -1
- package/lib/esm/utils.js.map +0 -1
- package/src/__tests__/lockstorage.test.ts +0 -285
- package/src/__tests__/singletonLock.test.ts +0 -85
- package/src/__tests__/useQuery.dom.test.ts +0 -518
- package/src/__tests__/utils.test.ts +0 -90
- package/src/useCount.tsx +0 -63
- package/src/useLocal.tsx +0 -125
- package/src/useMount.tsx +0 -15
- package/src/useOnline.tsx +0 -85
- package/src/useProgram.tsx +0 -148
- package/src/useQuery.tsx +0 -548
- /package/{lib/esm → dist/src}/useMount.js +0 -0
package/src/usePeer.tsx
CHANGED
|
@@ -1,79 +1,82 @@
|
|
|
1
|
-
import React, { JSX, useContext } from "react";
|
|
2
|
-
import { Multiaddr } from "@multiformats/multiaddr";
|
|
3
|
-
import { Peerbit } from "peerbit";
|
|
4
|
-
import {
|
|
5
|
-
getFreeKeypair,
|
|
6
|
-
getClientId,
|
|
7
|
-
inIframe,
|
|
8
|
-
cookiesWhereClearedJustNow,
|
|
9
|
-
} from "./utils.js";
|
|
10
1
|
import { noise } from "@chainsafe/libp2p-noise";
|
|
11
|
-
import {
|
|
2
|
+
import { yamux } from "@chainsafe/libp2p-yamux";
|
|
3
|
+
import { webSockets } from "@libp2p/websockets";
|
|
4
|
+
import type { Multiaddr } from "@multiformats/multiaddr";
|
|
12
5
|
import { Ed25519Keypair } from "@peerbit/crypto";
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
import { useMount } from "./useMount.js";
|
|
6
|
+
import type { Indices } from "@peerbit/indexer-interface";
|
|
7
|
+
import type { ProgramClient } from "@peerbit/program";
|
|
17
8
|
import { createClient, createHost } from "@peerbit/proxy-window";
|
|
18
|
-
import {
|
|
19
|
-
import { webSockets } from "@libp2p/websockets";
|
|
9
|
+
import { waitFor } from "@peerbit/time";
|
|
20
10
|
import { detectIncognito } from "detectincognitojs";
|
|
21
|
-
import
|
|
22
|
-
import {
|
|
11
|
+
import sodium from "libsodium-wrappers";
|
|
12
|
+
import { Peerbit } from "peerbit";
|
|
13
|
+
import * as React from "react";
|
|
14
|
+
import type { JSX } from "react";
|
|
15
|
+
import { v4 as uuid } from "uuid";
|
|
16
|
+
import { FastMutex } from "./lockstorage.ts";
|
|
17
|
+
import { useMount } from "./useMount.ts";
|
|
18
|
+
import {
|
|
19
|
+
cookiesWhereClearedJustNow,
|
|
20
|
+
getClientId,
|
|
21
|
+
getFreeKeypair,
|
|
22
|
+
inIframe,
|
|
23
|
+
} from "./utils.ts";
|
|
24
|
+
|
|
23
25
|
const isInStandaloneMode = () =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
window.matchMedia("(display-mode: standalone)").matches ||
|
|
27
|
+
((window.navigator as unknown as Record<string, unknown>)["standalone"] ??
|
|
28
|
+
false) ||
|
|
29
|
+
document.referrer.includes("android-app://");
|
|
27
30
|
|
|
28
31
|
export class ClientBusyError extends Error {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
constructor(message: string) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "CreateClientError";
|
|
35
|
+
}
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export type ConnectionStatus =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
| "disconnected"
|
|
40
|
+
| "connected"
|
|
41
|
+
| "connecting"
|
|
42
|
+
| "failed";
|
|
40
43
|
|
|
41
44
|
/** Discriminated union for PeerContext */
|
|
42
45
|
export type IPeerContext = (ProxyPeerContext | NodePeerContext) & {
|
|
43
|
-
|
|
46
|
+
error?: Error;
|
|
44
47
|
};
|
|
45
48
|
|
|
46
49
|
export interface ProxyPeerContext {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
type: "proxy";
|
|
51
|
+
peer: ProgramClient | undefined;
|
|
52
|
+
promise: Promise<void> | undefined;
|
|
53
|
+
loading: boolean;
|
|
54
|
+
status: ConnectionStatus;
|
|
55
|
+
persisted: boolean | undefined;
|
|
56
|
+
/** Present only in proxy (iframe) mode */
|
|
57
|
+
targetOrigin: string;
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
export interface NodePeerContext {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
type: "node";
|
|
62
|
+
peer: ProgramClient | undefined;
|
|
63
|
+
promise: Promise<void> | undefined;
|
|
64
|
+
loading: boolean;
|
|
65
|
+
status: ConnectionStatus;
|
|
66
|
+
persisted: boolean | undefined;
|
|
67
|
+
tabIndex: number;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
if (!window.name) {
|
|
68
|
-
|
|
71
|
+
window.name = uuid();
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
export const PeerContext = React.createContext<IPeerContext>({} as any);
|
|
72
|
-
export const usePeer = () => useContext(PeerContext);
|
|
75
|
+
export const usePeer = () => React.useContext(PeerContext);
|
|
73
76
|
|
|
74
77
|
type IFrameOptions = {
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
type: "proxy";
|
|
79
|
+
targetOrigin: string;
|
|
77
80
|
};
|
|
78
81
|
|
|
79
82
|
/**
|
|
@@ -83,396 +86,380 @@ type IFrameOptions = {
|
|
|
83
86
|
* If bootstrap is provided, it takes precedence over any implicit defaults.
|
|
84
87
|
*/
|
|
85
88
|
export type NetworkOption =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
| { type: "local" }
|
|
90
|
+
| { type: "remote" }
|
|
91
|
+
| { type?: "explicit"; bootstrap: (Multiaddr | string)[] };
|
|
89
92
|
|
|
90
93
|
type NodeOptions = {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
type?: "node";
|
|
95
|
+
network: "local" | "remote" | NetworkOption;
|
|
96
|
+
waitForConnnected?: boolean | "in-flight";
|
|
97
|
+
keypair?: Ed25519Keypair;
|
|
98
|
+
host?: boolean;
|
|
99
|
+
singleton?: boolean;
|
|
100
|
+
indexer?: (directory?: string) => Promise<Indices> | Indices;
|
|
97
101
|
};
|
|
98
102
|
|
|
99
103
|
type TopOptions = NodeOptions & { inMemory?: boolean };
|
|
100
104
|
type TopAndIframeOptions = {
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
iframe: IFrameOptions | NodeOptions;
|
|
106
|
+
top: TopOptions;
|
|
103
107
|
};
|
|
104
108
|
type WithChildren = {
|
|
105
|
-
|
|
109
|
+
children: JSX.Element;
|
|
106
110
|
};
|
|
107
111
|
type PeerOptions = (TopAndIframeOptions | TopOptions) & WithChildren;
|
|
108
112
|
|
|
109
113
|
const subscribeToUnload = (fn: () => any) => {
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
window.addEventListener("pagehide", fn);
|
|
115
|
+
window.addEventListener("beforeunload", fn);
|
|
112
116
|
};
|
|
113
117
|
|
|
114
118
|
export const PeerProvider = (options: PeerOptions) => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
? {
|
|
292
|
-
connectionGater: {
|
|
293
|
-
denyDialMultiaddr: () => false,
|
|
294
|
-
},
|
|
295
|
-
transports: [
|
|
296
|
-
webSockets({}) /* ,
|
|
119
|
+
const [peer, setPeer] = React.useState<ProgramClient | undefined>(undefined);
|
|
120
|
+
const [promise, setPromise] = React.useState<Promise<void> | undefined>(
|
|
121
|
+
undefined,
|
|
122
|
+
);
|
|
123
|
+
const [persisted, setPersisted] = React.useState<boolean>(false);
|
|
124
|
+
const [loading, setLoading] = React.useState<boolean>(true);
|
|
125
|
+
const [connectionState, setConnectionState] =
|
|
126
|
+
React.useState<ConnectionStatus>("disconnected");
|
|
127
|
+
|
|
128
|
+
const [tabIndex, setTabIndex] = React.useState<number>(-1);
|
|
129
|
+
|
|
130
|
+
const [error, setError] = React.useState<Error | undefined>(undefined); // <-- error state
|
|
131
|
+
|
|
132
|
+
// Decide which options to use based on whether we're in an iframe.
|
|
133
|
+
// If options.top is defined, assume we have separate settings for iframe vs. host.
|
|
134
|
+
const nodeOptions: IFrameOptions | TopOptions = (
|
|
135
|
+
options as TopAndIframeOptions
|
|
136
|
+
).top
|
|
137
|
+
? inIframe()
|
|
138
|
+
? (options as TopAndIframeOptions).iframe
|
|
139
|
+
: { ...options, ...(options as TopAndIframeOptions).top } // we merge root and top options, TODO should this be made in a different way to prevent confusion about top props?
|
|
140
|
+
: (options as TopOptions);
|
|
141
|
+
|
|
142
|
+
// If running as a proxy (iframe), expect a targetOrigin.
|
|
143
|
+
const computedTargetOrigin =
|
|
144
|
+
nodeOptions.type === "proxy"
|
|
145
|
+
? (nodeOptions as IFrameOptions).targetOrigin
|
|
146
|
+
: undefined;
|
|
147
|
+
|
|
148
|
+
const memo = React.useMemo<IPeerContext>(() => {
|
|
149
|
+
if (nodeOptions.type === "proxy") {
|
|
150
|
+
return {
|
|
151
|
+
type: "proxy",
|
|
152
|
+
peer,
|
|
153
|
+
promise,
|
|
154
|
+
loading,
|
|
155
|
+
status: connectionState,
|
|
156
|
+
persisted,
|
|
157
|
+
targetOrigin: computedTargetOrigin as string,
|
|
158
|
+
error,
|
|
159
|
+
};
|
|
160
|
+
} else {
|
|
161
|
+
return {
|
|
162
|
+
type: "node",
|
|
163
|
+
peer,
|
|
164
|
+
promise,
|
|
165
|
+
loading,
|
|
166
|
+
status: connectionState,
|
|
167
|
+
persisted,
|
|
168
|
+
tabIndex,
|
|
169
|
+
error,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}, [
|
|
173
|
+
loading,
|
|
174
|
+
promise,
|
|
175
|
+
connectionState,
|
|
176
|
+
peer,
|
|
177
|
+
persisted,
|
|
178
|
+
tabIndex,
|
|
179
|
+
computedTargetOrigin,
|
|
180
|
+
error,
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
useMount(() => {
|
|
184
|
+
setLoading(true);
|
|
185
|
+
const fn = async () => {
|
|
186
|
+
await sodium.ready;
|
|
187
|
+
let newPeer: ProgramClient;
|
|
188
|
+
// Track resolved persistence status during client creation
|
|
189
|
+
let persistedResolved = false;
|
|
190
|
+
// Controls how long we keep locks alive; flipped to false on close/hidden
|
|
191
|
+
const keepAliveRef = { current: true } as { current: boolean };
|
|
192
|
+
|
|
193
|
+
if (nodeOptions.type !== "proxy") {
|
|
194
|
+
const releaseFirstLock = cookiesWhereClearedJustNow();
|
|
195
|
+
|
|
196
|
+
const sessionId = getClientId("session");
|
|
197
|
+
const mutex = new FastMutex({
|
|
198
|
+
clientId: sessionId,
|
|
199
|
+
timeout: 1e3,
|
|
200
|
+
});
|
|
201
|
+
if (nodeOptions.singleton) {
|
|
202
|
+
const localId = getClientId("local");
|
|
203
|
+
try {
|
|
204
|
+
const lockKey = localId + "-singleton";
|
|
205
|
+
subscribeToUnload(function () {
|
|
206
|
+
// Immediate release on page close
|
|
207
|
+
keepAliveRef.current = false;
|
|
208
|
+
mutex.release(lockKey);
|
|
209
|
+
});
|
|
210
|
+
// Also release when page is hidden to reduce flakiness between sequential tests
|
|
211
|
+
const onVisibility = () => {
|
|
212
|
+
if (document.visibilityState === "hidden") {
|
|
213
|
+
keepAliveRef.current = false;
|
|
214
|
+
// Mark expired and remove proactively
|
|
215
|
+
try {
|
|
216
|
+
mutex.release(lockKey);
|
|
217
|
+
} catch {}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
221
|
+
if (isInStandaloneMode()) {
|
|
222
|
+
// PWA issue fix (? TODO is this needed ?
|
|
223
|
+
keepAliveRef.current = false;
|
|
224
|
+
mutex.release(lockKey);
|
|
225
|
+
}
|
|
226
|
+
await mutex.lock(lockKey, () => keepAliveRef.current, {
|
|
227
|
+
replaceIfSameClient: true,
|
|
228
|
+
});
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error("Failed to lock singleton client", error);
|
|
231
|
+
throw new ClientBusyError("Failed to lock single client");
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let nodeId: Ed25519Keypair;
|
|
236
|
+
if (nodeOptions.keypair) {
|
|
237
|
+
nodeId = nodeOptions.keypair;
|
|
238
|
+
} else {
|
|
239
|
+
const kp = await getFreeKeypair(
|
|
240
|
+
"",
|
|
241
|
+
mutex,
|
|
242
|
+
() => keepAliveRef.current,
|
|
243
|
+
{
|
|
244
|
+
releaseFirstLock,
|
|
245
|
+
releaseLockIfSameId: true,
|
|
246
|
+
},
|
|
247
|
+
);
|
|
248
|
+
subscribeToUnload(function () {
|
|
249
|
+
keepAliveRef.current = false;
|
|
250
|
+
mutex.release(kp.path);
|
|
251
|
+
});
|
|
252
|
+
nodeId = kp.key;
|
|
253
|
+
setTabIndex(kp.index);
|
|
254
|
+
}
|
|
255
|
+
const peerId = nodeId.toPeerId();
|
|
256
|
+
|
|
257
|
+
let directory: string | undefined = undefined;
|
|
258
|
+
if (
|
|
259
|
+
!(nodeOptions as TopOptions).inMemory &&
|
|
260
|
+
!(await detectIncognito()).isPrivate
|
|
261
|
+
) {
|
|
262
|
+
const persisted = await navigator.storage.persist();
|
|
263
|
+
setPersisted(persisted);
|
|
264
|
+
persistedResolved = persisted;
|
|
265
|
+
if (!persisted) {
|
|
266
|
+
setPersisted(false);
|
|
267
|
+
console.error(
|
|
268
|
+
"Request persistence but permission was not granted by browser.",
|
|
269
|
+
);
|
|
270
|
+
} else {
|
|
271
|
+
directory = `./repo/${peerId.toString()}/`;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
console.log("Create client");
|
|
276
|
+
newPeer = await Peerbit.create({
|
|
277
|
+
libp2p: {
|
|
278
|
+
addresses: {
|
|
279
|
+
listen: [
|
|
280
|
+
/* "/p2p-circuit" */
|
|
281
|
+
],
|
|
282
|
+
},
|
|
283
|
+
streamMuxers: [yamux()],
|
|
284
|
+
connectionEncrypters: [noise()],
|
|
285
|
+
peerId,
|
|
286
|
+
connectionManager: { maxConnections: 100 },
|
|
287
|
+
connectionMonitor: { enabled: false },
|
|
288
|
+
...(nodeOptions.network === "local"
|
|
289
|
+
? {
|
|
290
|
+
connectionGater: {
|
|
291
|
+
denyDialMultiaddr: () => false,
|
|
292
|
+
},
|
|
293
|
+
transports: [
|
|
294
|
+
webSockets({}) /* ,
|
|
297
295
|
circuitRelayTransport(), */,
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
296
|
+
],
|
|
297
|
+
}
|
|
298
|
+
: {
|
|
299
|
+
connectionGater: {
|
|
300
|
+
denyDialMultiaddr: () => false, // TODO do right here, dont allow local dials except bootstrap
|
|
301
|
+
},
|
|
302
|
+
transports: [
|
|
303
|
+
webSockets() /* ,
|
|
306
304
|
circuitRelayTransport(), */,
|
|
307
|
-
|
|
308
|
-
|
|
305
|
+
],
|
|
306
|
+
}) /*
|
|
309
307
|
services: {
|
|
310
308
|
pubsub: (c) =>
|
|
311
309
|
new DirectSub(c, { canRelayMessage: true }),
|
|
312
310
|
identify: identify(),
|
|
313
311
|
}, */,
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
setLoading(false);
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
setPromise(fnWithErrorHandling());
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
return (
|
|
474
|
-
<PeerContext.Provider value={memo}>
|
|
475
|
-
{options.children}
|
|
476
|
-
</PeerContext.Provider>
|
|
477
|
-
);
|
|
312
|
+
},
|
|
313
|
+
directory,
|
|
314
|
+
indexer: nodeOptions.indexer,
|
|
315
|
+
});
|
|
316
|
+
console.log("Client created", {
|
|
317
|
+
directory,
|
|
318
|
+
peerHash: newPeer?.identity.publicKey.hashcode(),
|
|
319
|
+
network: nodeOptions.network === "local" ? "local" : "remote",
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
(window as any).__peerInfo = {
|
|
323
|
+
peerHash: newPeer?.identity.publicKey.hashcode(),
|
|
324
|
+
persisted: persistedResolved,
|
|
325
|
+
};
|
|
326
|
+
window.dispatchEvent(
|
|
327
|
+
new CustomEvent("peer:ready", {
|
|
328
|
+
detail: (window as any).__peerInfo,
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
setConnectionState("connecting");
|
|
333
|
+
|
|
334
|
+
const connectFn = async () => {
|
|
335
|
+
try {
|
|
336
|
+
const network = nodeOptions.network;
|
|
337
|
+
|
|
338
|
+
// 1) Explicit bootstrap addresses take precedence
|
|
339
|
+
if (
|
|
340
|
+
typeof network !== "string" &&
|
|
341
|
+
(network as any)?.bootstrap !== undefined
|
|
342
|
+
) {
|
|
343
|
+
const list = (network as any).bootstrap as (Multiaddr | string)[];
|
|
344
|
+
if (list.length === 0) {
|
|
345
|
+
// Explicit offline mode: skip dialing and mark as connected (no relays)
|
|
346
|
+
console.log("Offline bootstrap: skipping relay dialing");
|
|
347
|
+
} else {
|
|
348
|
+
for (const addr of list) {
|
|
349
|
+
await newPeer.dial(addr);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// 2) Local development: dial local relay service
|
|
354
|
+
else if (
|
|
355
|
+
network === "local" ||
|
|
356
|
+
(typeof network !== "string" &&
|
|
357
|
+
(network as any)?.type === "local")
|
|
358
|
+
) {
|
|
359
|
+
const localAddress =
|
|
360
|
+
"/ip4/127.0.0.1/tcp/8002/ws/p2p/" +
|
|
361
|
+
(await (await fetch("http://localhost:8082/peer/id")).text());
|
|
362
|
+
console.log("Dialing local address", localAddress);
|
|
363
|
+
await newPeer.dial(localAddress);
|
|
364
|
+
}
|
|
365
|
+
// 3) Remote default: use bootstrap service (no explicit bootstrap provided)
|
|
366
|
+
else {
|
|
367
|
+
await (newPeer as Peerbit).bootstrap?.();
|
|
368
|
+
}
|
|
369
|
+
setConnectionState("connected");
|
|
370
|
+
} catch (err: any) {
|
|
371
|
+
console.error("Failed to bootstrap:", err);
|
|
372
|
+
setConnectionState("failed");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (nodeOptions.host) {
|
|
376
|
+
newPeer = await createHost(newPeer as Peerbit);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const perfEnabled = new URLSearchParams(window.location.search).get(
|
|
381
|
+
"perf",
|
|
382
|
+
);
|
|
383
|
+
const t0 = performance.now();
|
|
384
|
+
const marks: Record<string, number> = {};
|
|
385
|
+
const perfMark = (label: string) => {
|
|
386
|
+
marks[label] = performance.now() - t0;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
console.log("Bootstrap start...");
|
|
390
|
+
const promise = connectFn().then(() => {
|
|
391
|
+
perfMark("dialComplete");
|
|
392
|
+
});
|
|
393
|
+
promise.then(() => {
|
|
394
|
+
console.log("Bootstrap done");
|
|
395
|
+
try {
|
|
396
|
+
if (perfEnabled) {
|
|
397
|
+
const payload = { ...marks } as any;
|
|
398
|
+
console.info("[Perf] peer bootstrap", payload);
|
|
399
|
+
window.dispatchEvent(
|
|
400
|
+
new CustomEvent("perf:peer", {
|
|
401
|
+
detail: payload,
|
|
402
|
+
}),
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
} catch {}
|
|
406
|
+
});
|
|
407
|
+
if (nodeOptions.waitForConnnected === true) {
|
|
408
|
+
await promise;
|
|
409
|
+
} else if (nodeOptions.waitForConnnected === "in-flight") {
|
|
410
|
+
// wait for dialQueue to not be empty or connections to contains the peerId
|
|
411
|
+
// or done
|
|
412
|
+
let isDone = false;
|
|
413
|
+
promise.finally(() => {
|
|
414
|
+
isDone = true;
|
|
415
|
+
});
|
|
416
|
+
await waitFor(() => {
|
|
417
|
+
if (isDone) {
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
const libp2p = newPeer as Peerbit;
|
|
421
|
+
if (libp2p.libp2p.getDialQueue().length > 0) {
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
if (libp2p.libp2p.getConnections().length > 0) {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
return false;
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
} else {
|
|
431
|
+
// When in proxy mode (iframe), use the provided targetOrigin.
|
|
432
|
+
newPeer = await createClient(
|
|
433
|
+
(nodeOptions as IFrameOptions).targetOrigin,
|
|
434
|
+
);
|
|
435
|
+
try {
|
|
436
|
+
(window as any).__peerInfo = {
|
|
437
|
+
peerHash: newPeer?.identity.publicKey.hashcode(),
|
|
438
|
+
persisted: false,
|
|
439
|
+
};
|
|
440
|
+
window.dispatchEvent(
|
|
441
|
+
new CustomEvent("peer:ready", {
|
|
442
|
+
detail: (window as any).__peerInfo,
|
|
443
|
+
}),
|
|
444
|
+
);
|
|
445
|
+
} catch {}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
setPeer(newPeer);
|
|
449
|
+
setLoading(false);
|
|
450
|
+
};
|
|
451
|
+
const fnWithErrorHandling = async () => {
|
|
452
|
+
try {
|
|
453
|
+
await fn();
|
|
454
|
+
} catch (error: any) {
|
|
455
|
+
setError(error);
|
|
456
|
+
setLoading(false);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
setPromise(fnWithErrorHandling());
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
return (
|
|
463
|
+
<PeerContext.Provider value={memo}>{options.children}</PeerContext.Provider>
|
|
464
|
+
);
|
|
478
465
|
};
|