peerbit 5.3.6 → 5.3.7
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/bootstrap-recovery.d.ts +79 -0
- package/dist/src/bootstrap-recovery.d.ts.map +1 -0
- package/dist/src/bootstrap-recovery.js +216 -0
- package/dist/src/bootstrap-recovery.js.map +1 -0
- package/dist/src/bootstrap.d.ts +4 -1
- package/dist/src/bootstrap.d.ts.map +1 -1
- package/dist/src/bootstrap.js +201 -7
- package/dist/src/bootstrap.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/peer.d.ts +24 -1
- package/dist/src/peer.d.ts.map +1 -1
- package/dist/src/peer.js +116 -5
- package/dist/src/peer.js.map +1 -1
- package/package.json +21 -23
- package/src/bootstrap-recovery.ts +343 -0
- package/src/bootstrap.ts +286 -12
- package/src/index.ts +1 -0
- package/src/peer.ts +154 -5
package/src/peer.ts
CHANGED
|
@@ -42,6 +42,12 @@ import sodium from "libsodium-wrappers";
|
|
|
42
42
|
import path from "path-browserify";
|
|
43
43
|
import { concat } from "uint8arrays";
|
|
44
44
|
import { getBootstrapPeerId, resolveBootstrapAddresses } from "./bootstrap.js";
|
|
45
|
+
import {
|
|
46
|
+
BootstrapRecoveryController,
|
|
47
|
+
type BootstrapRecoveryEventTarget,
|
|
48
|
+
type BootstrapRecoveryOptions,
|
|
49
|
+
validateBootstrapRecoveryOptions,
|
|
50
|
+
} from "./bootstrap-recovery.js";
|
|
45
51
|
import {
|
|
46
52
|
type Libp2pCreateOptions as ClientCreateOptions,
|
|
47
53
|
type Libp2pExtended,
|
|
@@ -169,6 +175,12 @@ export type CreateInstanceOptions = (SimpleLibp2pOptions | Libp2pOptions) & {
|
|
|
169
175
|
indexer?: (directory?: string) => Promise<Indices> | Indices;
|
|
170
176
|
storage?: StorageCreateOptions;
|
|
171
177
|
network?: NativeNetworkCreateOptions;
|
|
178
|
+
/**
|
|
179
|
+
* Opt-in automatic bootstrap recovery. `true` uses bounded defaults; an
|
|
180
|
+
* options object customizes retry policy or fixed targets. It is disabled by
|
|
181
|
+
* default to avoid unexpected network access from `Peerbit.create()`.
|
|
182
|
+
*/
|
|
183
|
+
bootstrapRecovery?: boolean | BootstrapRecoveryOptions;
|
|
172
184
|
} & OptionalCreateOptions;
|
|
173
185
|
|
|
174
186
|
export type DialReadiness =
|
|
@@ -193,6 +205,11 @@ export type BootstrapResult = {
|
|
|
193
205
|
failures: BootstrapFailure[];
|
|
194
206
|
};
|
|
195
207
|
|
|
208
|
+
export type BootstrapDialOptions = {
|
|
209
|
+
dialTimeoutMs?: number;
|
|
210
|
+
signal?: AbortSignal;
|
|
211
|
+
};
|
|
212
|
+
|
|
196
213
|
const isLibp2pInstance = (libp2p: Libp2pExtended | ClientCreateOptions) =>
|
|
197
214
|
!!(libp2p as Libp2p).getMultiaddrs;
|
|
198
215
|
|
|
@@ -210,6 +227,17 @@ const createCache = async (
|
|
|
210
227
|
return cache;
|
|
211
228
|
};
|
|
212
229
|
|
|
230
|
+
const getOnlineEventTarget = (): BootstrapRecoveryEventTarget | undefined => {
|
|
231
|
+
const target = globalThis as unknown as Partial<BootstrapRecoveryEventTarget>;
|
|
232
|
+
return typeof target.addEventListener === "function" &&
|
|
233
|
+
typeof target.removeEventListener === "function"
|
|
234
|
+
? (target as BootstrapRecoveryEventTarget)
|
|
235
|
+
: undefined;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const isEnvironmentOnline = (): boolean =>
|
|
239
|
+
typeof navigator === "undefined" || navigator.onLine !== false;
|
|
240
|
+
|
|
213
241
|
const SELF_IDENTITY_KEY_ID = new Uint8Array([
|
|
214
242
|
95, 95, 115, 101, 108, 102, 95, 95,
|
|
215
243
|
]); // new TextEncoder().encode("__self__");
|
|
@@ -223,6 +251,11 @@ export class Peerbit implements ProgramClient {
|
|
|
223
251
|
private _indexer: Indices;
|
|
224
252
|
private _libp2pExternal?: boolean = false;
|
|
225
253
|
private _nativeNetwork?: NativeNetworkRuntime;
|
|
254
|
+
private _bootstrapRecoveryOptions?: BootstrapRecoveryOptions;
|
|
255
|
+
private _bootstrapRecovery?: BootstrapRecoveryController;
|
|
256
|
+
private _bootstrapRecoveryGeneration = 0;
|
|
257
|
+
private _bootstrapRecoveryTransition: Promise<void> = Promise.resolve();
|
|
258
|
+
private _bootstrapRecoveryPaused = false;
|
|
226
259
|
|
|
227
260
|
/**
|
|
228
261
|
* Native shared-log defaults advertised to programs opened on this
|
|
@@ -266,6 +299,14 @@ export class Peerbit implements ProgramClient {
|
|
|
266
299
|
}
|
|
267
300
|
|
|
268
301
|
static async create(options: CreateInstanceOptions = {}): Promise<Peerbit> {
|
|
302
|
+
const bootstrapRecoveryOptions = options.bootstrapRecovery;
|
|
303
|
+
if (
|
|
304
|
+
bootstrapRecoveryOptions &&
|
|
305
|
+
bootstrapRecoveryOptions !== true &&
|
|
306
|
+
bootstrapRecoveryOptions.enabled !== false
|
|
307
|
+
) {
|
|
308
|
+
validateBootstrapRecoveryOptions(bootstrapRecoveryOptions);
|
|
309
|
+
}
|
|
269
310
|
await sodium.ready; // Some of the modules depends on sodium to be readyy
|
|
270
311
|
|
|
271
312
|
let libp2pExtended: Libp2pExtended | undefined = (options as Libp2pOptions)
|
|
@@ -594,6 +635,14 @@ export class Peerbit implements ProgramClient {
|
|
|
594
635
|
nativeNetwork,
|
|
595
636
|
sharedLogNativeDefaults,
|
|
596
637
|
});
|
|
638
|
+
if (options.bootstrapRecovery === true) {
|
|
639
|
+
peer.enableBootstrapRecovery();
|
|
640
|
+
} else if (
|
|
641
|
+
options.bootstrapRecovery &&
|
|
642
|
+
options.bootstrapRecovery.enabled !== false
|
|
643
|
+
) {
|
|
644
|
+
peer.enableBootstrapRecovery(options.bootstrapRecovery);
|
|
645
|
+
}
|
|
597
646
|
return peer;
|
|
598
647
|
}
|
|
599
648
|
get libp2p(): Libp2pExtended {
|
|
@@ -734,16 +783,100 @@ export class Peerbit implements ProgramClient {
|
|
|
734
783
|
// TODO wait for pubsub and blocks to disconnect?
|
|
735
784
|
}
|
|
736
785
|
|
|
786
|
+
get bootstrapRecoveryEnabled(): boolean {
|
|
787
|
+
return this._bootstrapRecoveryOptions != null;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/** Enable automatic recovery and schedule an immediate attempt if disconnected. */
|
|
791
|
+
enableBootstrapRecovery(options: BootstrapRecoveryOptions = {}): void {
|
|
792
|
+
if (options.enabled === false) {
|
|
793
|
+
this.disableBootstrapRecovery();
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
validateBootstrapRecoveryOptions(options);
|
|
797
|
+
this._bootstrapRecoveryOptions = {
|
|
798
|
+
...options,
|
|
799
|
+
enabled: true,
|
|
800
|
+
addresses: options.addresses ? [...options.addresses] : undefined,
|
|
801
|
+
};
|
|
802
|
+
this.transitionBootstrapRecovery(true);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/** Disable recovery, remove environment listeners, and abort its active dial. */
|
|
806
|
+
disableBootstrapRecovery(): void {
|
|
807
|
+
this._bootstrapRecoveryOptions = undefined;
|
|
808
|
+
this.transitionBootstrapRecovery(false);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
private transitionBootstrapRecovery(startAfterDrain: boolean): Promise<void> {
|
|
812
|
+
const generation = ++this._bootstrapRecoveryGeneration;
|
|
813
|
+
const previous = this._bootstrapRecovery;
|
|
814
|
+
this._bootstrapRecovery = undefined;
|
|
815
|
+
const drain = previous?.stop() ?? Promise.resolve();
|
|
816
|
+
const transition = Promise.all([
|
|
817
|
+
this._bootstrapRecoveryTransition,
|
|
818
|
+
drain,
|
|
819
|
+
]).then(() => {
|
|
820
|
+
if (
|
|
821
|
+
startAfterDrain &&
|
|
822
|
+
generation === this._bootstrapRecoveryGeneration
|
|
823
|
+
) {
|
|
824
|
+
this.startBootstrapRecovery();
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
this._bootstrapRecoveryTransition = transition.catch((error) => {
|
|
828
|
+
logger.error(`Bootstrap recovery lifecycle transition failed: ${error}`);
|
|
829
|
+
});
|
|
830
|
+
return this._bootstrapRecoveryTransition;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
private startBootstrapRecovery(): void {
|
|
834
|
+
const options = this._bootstrapRecoveryOptions;
|
|
835
|
+
if (
|
|
836
|
+
!options ||
|
|
837
|
+
this._bootstrapRecovery ||
|
|
838
|
+
this._bootstrapRecoveryPaused ||
|
|
839
|
+
this.libp2p.status !== "started"
|
|
840
|
+
) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
const controller = new BootstrapRecoveryController(
|
|
844
|
+
{
|
|
845
|
+
bootstrap: (signal) => {
|
|
846
|
+
const addresses = this._bootstrapRecoveryOptions?.addresses;
|
|
847
|
+
return this.bootstrap(addresses ? [...addresses] : undefined, {
|
|
848
|
+
signal,
|
|
849
|
+
});
|
|
850
|
+
},
|
|
851
|
+
connectionEvents: this
|
|
852
|
+
.libp2p as unknown as BootstrapRecoveryEventTarget,
|
|
853
|
+
onlineEvents: getOnlineEventTarget(),
|
|
854
|
+
isConnected: () => this.libp2p.getConnections().length > 0,
|
|
855
|
+
isOnline: isEnvironmentOnline,
|
|
856
|
+
},
|
|
857
|
+
options,
|
|
858
|
+
);
|
|
859
|
+
this._bootstrapRecovery = controller;
|
|
860
|
+
controller.start();
|
|
861
|
+
}
|
|
862
|
+
|
|
737
863
|
async start() {
|
|
864
|
+
this._handler?.assertCanStart();
|
|
738
865
|
await this._storage.open();
|
|
739
866
|
await this.indexer.start();
|
|
740
867
|
|
|
741
868
|
if (this.libp2p.status === "stopped" || this.libp2p.status === "stopping") {
|
|
742
869
|
this._libp2pExternal = false; // this means we will also close libp2p client on close
|
|
743
|
-
|
|
870
|
+
await this.libp2p.start();
|
|
744
871
|
}
|
|
872
|
+
this._bootstrapRecoveryPaused = false;
|
|
873
|
+
await this._bootstrapRecoveryTransition;
|
|
874
|
+
this._handler?.start();
|
|
875
|
+
this.startBootstrapRecovery();
|
|
745
876
|
}
|
|
746
877
|
async stop() {
|
|
878
|
+
this._bootstrapRecoveryPaused = true;
|
|
879
|
+
await this.transitionBootstrapRecovery(false);
|
|
747
880
|
await this._handler?.stop();
|
|
748
881
|
await this._storage.close();
|
|
749
882
|
await this.indexer.stop();
|
|
@@ -755,8 +888,16 @@ export class Peerbit implements ProgramClient {
|
|
|
755
888
|
}
|
|
756
889
|
}
|
|
757
890
|
|
|
758
|
-
async bootstrap(
|
|
759
|
-
|
|
891
|
+
async bootstrap(
|
|
892
|
+
addresses?: Array<string | Multiaddr>,
|
|
893
|
+
options: BootstrapDialOptions = {},
|
|
894
|
+
) {
|
|
895
|
+
if (options.signal?.aborted) {
|
|
896
|
+
throw options.signal.reason ?? new Error("Bootstrap aborted");
|
|
897
|
+
}
|
|
898
|
+
const _addresses =
|
|
899
|
+
addresses ??
|
|
900
|
+
(await resolveBootstrapAddresses("5", { signal: options.signal }));
|
|
760
901
|
if (_addresses.length === 0) {
|
|
761
902
|
throw new Error("Failed to find any addresses to dial");
|
|
762
903
|
}
|
|
@@ -785,7 +926,7 @@ export class Peerbit implements ProgramClient {
|
|
|
785
926
|
byPeerId.set(pid, list);
|
|
786
927
|
}
|
|
787
928
|
|
|
788
|
-
const dialTimeoutMs = 30_000;
|
|
929
|
+
const dialTimeoutMs = options.dialTimeoutMs ?? 30_000;
|
|
789
930
|
const scoreBootstrapAddr = (a: Multiaddr) => {
|
|
790
931
|
const s = a.toString();
|
|
791
932
|
const isCircuit = s.includes("p2p-circuit");
|
|
@@ -803,7 +944,11 @@ export class Peerbit implements ProgramClient {
|
|
|
803
944
|
// Bootstrap nodes are rendezvous points; they may not immediately satisfy
|
|
804
945
|
// "services" readiness (pubsub/blocks/fanout neighbor checks), especially in
|
|
805
946
|
// browser runtimes. A successful transport-level dial is enough here.
|
|
806
|
-
await this.dial(ma, {
|
|
947
|
+
await this.dial(ma, {
|
|
948
|
+
dialTimeoutMs,
|
|
949
|
+
readiness: "connection",
|
|
950
|
+
signal: options.signal,
|
|
951
|
+
});
|
|
807
952
|
return true;
|
|
808
953
|
} catch (e) {
|
|
809
954
|
lastError = e;
|
|
@@ -832,11 +977,15 @@ export class Peerbit implements ProgramClient {
|
|
|
832
977
|
promise: this.dial(typeof a === "string" ? multiaddr(a) : a, {
|
|
833
978
|
dialTimeoutMs,
|
|
834
979
|
readiness: "connection",
|
|
980
|
+
signal: options.signal,
|
|
835
981
|
}),
|
|
836
982
|
});
|
|
837
983
|
}
|
|
838
984
|
|
|
839
985
|
const settled = await Promise.allSettled(dialTasks.map((t) => t.promise));
|
|
986
|
+
if (options.signal?.aborted) {
|
|
987
|
+
throw options.signal.reason ?? new Error("Bootstrap aborted");
|
|
988
|
+
}
|
|
840
989
|
let once = false;
|
|
841
990
|
const connectedPeerIds = new Set<string>();
|
|
842
991
|
const failures: BootstrapFailure[] = [];
|