@peerbit/react 0.0.33 → 0.0.35

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