gnutella 1.0.0

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 (45) hide show
  1. package/CLI.md +189 -0
  2. package/DEVELOPER.md +193 -0
  3. package/LICENSE +674 -0
  4. package/QUICKSTART.md +133 -0
  5. package/README.md +74 -0
  6. package/bin/gnutella.ts +15 -0
  7. package/gnutella.json.example +18 -0
  8. package/package.json +72 -0
  9. package/src/cli.ts +692 -0
  10. package/src/cli_shared.ts +359 -0
  11. package/src/const.ts +138 -0
  12. package/src/gwebcache/bootstrap.ts +491 -0
  13. package/src/gwebcache/response.ts +391 -0
  14. package/src/gwebcache/shared.ts +116 -0
  15. package/src/gwebcache/types.ts +187 -0
  16. package/src/gwebcache_client.ts +13 -0
  17. package/src/protocol/browse_host.ts +552 -0
  18. package/src/protocol/client_blocking.ts +29 -0
  19. package/src/protocol/codec.ts +715 -0
  20. package/src/protocol/content_urn.ts +170 -0
  21. package/src/protocol/core_utils.ts +43 -0
  22. package/src/protocol/file_server.ts +245 -0
  23. package/src/protocol/ggep.ts +168 -0
  24. package/src/protocol/handshake.ts +199 -0
  25. package/src/protocol/http_download_reader.ts +112 -0
  26. package/src/protocol/magnet.ts +176 -0
  27. package/src/protocol/node.ts +416 -0
  28. package/src/protocol/node_handshake.ts +992 -0
  29. package/src/protocol/node_lifecycle.ts +210 -0
  30. package/src/protocol/node_protocol_runtime.ts +949 -0
  31. package/src/protocol/node_qrp_runtime.ts +97 -0
  32. package/src/protocol/node_query_routing.ts +208 -0
  33. package/src/protocol/node_state.ts +745 -0
  34. package/src/protocol/node_tls.ts +257 -0
  35. package/src/protocol/node_topology.ts +141 -0
  36. package/src/protocol/node_transfer.ts +455 -0
  37. package/src/protocol/node_types.ts +106 -0
  38. package/src/protocol/peer_state.ts +675 -0
  39. package/src/protocol/qrp.ts +549 -0
  40. package/src/protocol/query_search.ts +29 -0
  41. package/src/protocol/share_index.ts +131 -0
  42. package/src/protocol/share_library.ts +246 -0
  43. package/src/protocol.ts +36 -0
  44. package/src/shared.ts +236 -0
  45. package/src/types.ts +452 -0
@@ -0,0 +1,391 @@
1
+ import { DEFAULT_USER_AGENT } from "../const";
2
+ import type {
3
+ GWebCacheCacheEntry,
4
+ GWebCacheHostEntry,
5
+ GWebCacheHttpResponse,
6
+ GWebCacheInfoLine,
7
+ GWebCachePong,
8
+ GWebCacheRequestOptions,
9
+ GWebCacheResponse,
10
+ GWebCacheUpdate,
11
+ } from "./types";
12
+ import {
13
+ DEFAULT_TIMEOUT_MS,
14
+ normalizeCacheUrl,
15
+ normalizeGWebCachePeer,
16
+ normalizeNetwork,
17
+ parseAge,
18
+ parseNetworks,
19
+ parseWarning,
20
+ sanitizeClient,
21
+ sanitizeVersion,
22
+ splitBodyLines,
23
+ } from "./shared";
24
+
25
+ type GWebCacheResponseState = {
26
+ peers: string[];
27
+ caches: string[];
28
+ warnings: string[];
29
+ info: GWebCacheInfoLine[];
30
+ hostEntries: GWebCacheHostEntry[];
31
+ cacheEntries: GWebCacheCacheEntry[];
32
+ seenPeers: Set<string>;
33
+ seenCaches: Set<string>;
34
+ pong?: GWebCachePong;
35
+ update?: GWebCacheUpdate;
36
+ recognized: boolean;
37
+ };
38
+
39
+ function buildUpdate(values: string[]): GWebCacheUpdate {
40
+ const head = values[0]?.trim().toUpperCase() || "";
41
+ if (head === "OK") {
42
+ return { ok: true, warning: parseWarning(values.slice(1)), values };
43
+ }
44
+ if (head === "WARNING") {
45
+ return {
46
+ ok: false,
47
+ warning: parseWarning(values.slice(1)) || "WARNING",
48
+ values,
49
+ };
50
+ }
51
+ return {
52
+ ok: values.some((value) => value.trim().toUpperCase() === "OK"),
53
+ warning: parseWarning(values),
54
+ values,
55
+ };
56
+ }
57
+
58
+ export function describeHttpError(result: GWebCacheHttpResponse): string {
59
+ const summary =
60
+ result.rawLines[0] || result.statusText || "request failed";
61
+ return `HTTP ${result.status}: ${summary}`;
62
+ }
63
+
64
+ export function describeUpdateError(
65
+ result: GWebCacheHttpResponse,
66
+ ): string {
67
+ if (!result.ok) return describeHttpError(result);
68
+ if (!result.spec) return "unexpected non-spec2 gwebcache response";
69
+ if (!result.update) return "missing spec2 gwebcache update response";
70
+ return result.update.warning || "gwebcache update rejected";
71
+ }
72
+
73
+ function setGWebCacheFlag(
74
+ url: URL,
75
+ key: string,
76
+ enabled: boolean | undefined,
77
+ ): void {
78
+ if (!enabled) return;
79
+ url.searchParams.set(key, "1");
80
+ }
81
+
82
+ function applyRequestMode(
83
+ url: URL,
84
+ options: GWebCacheRequestOptions,
85
+ ): void {
86
+ if (options.mode === "update") {
87
+ url.searchParams.set("update", "1");
88
+ return;
89
+ }
90
+ url.searchParams.set("get", "1");
91
+ url.searchParams.set("net", normalizeNetwork(options.network));
92
+ }
93
+
94
+ function applyRequestMetadata(
95
+ url: URL,
96
+ options: GWebCacheRequestOptions,
97
+ ): void {
98
+ if (options.spec) url.searchParams.set("spec", String(options.spec));
99
+ if (options.ping !== false) url.searchParams.set("ping", "1");
100
+ url.searchParams.set("client", sanitizeClient(options.client));
101
+ url.searchParams.set("version", sanitizeVersion(options.version));
102
+ }
103
+
104
+ function applyPeerUpdate(url: URL, peerSpec: string | undefined): void {
105
+ if (!peerSpec) return;
106
+ const peer = normalizeGWebCachePeer(peerSpec);
107
+ if (!peer) throw new Error(`invalid gwebcache peer update: ${peerSpec}`);
108
+ url.searchParams.set("ip", peer);
109
+ url.searchParams.set("update", "1");
110
+ }
111
+
112
+ function applyCacheUpdate(url: URL, cacheSpec: string | undefined): void {
113
+ if (!cacheSpec) return;
114
+ const cacheUrl = normalizeCacheUrl(cacheSpec);
115
+ if (!cacheUrl)
116
+ throw new Error(`invalid gwebcache cache update: ${cacheSpec}`);
117
+ url.searchParams.set("url", cacheUrl);
118
+ url.searchParams.set("update", "1");
119
+ }
120
+
121
+ function setOptionalCount(
122
+ url: URL,
123
+ key: string,
124
+ value: number | undefined,
125
+ ): void {
126
+ if (!Number.isInteger(value) || (value || 0) < 0) return;
127
+ url.searchParams.set(key, String(value));
128
+ }
129
+
130
+ function applyUpdateStats(
131
+ url: URL,
132
+ options: GWebCacheRequestOptions,
133
+ ): void {
134
+ setOptionalCount(url, "x_leaves", options.leafCount);
135
+ setOptionalCount(url, "x_max", options.maxLeaves);
136
+ setOptionalCount(url, "uptime", options.uptimeSec);
137
+ }
138
+
139
+ function applyLookupFlags(
140
+ url: URL,
141
+ options: GWebCacheRequestOptions,
142
+ ): void {
143
+ if (options.cluster?.trim()) {
144
+ url.searchParams.set("cluster", options.cluster.trim());
145
+ }
146
+ setGWebCacheFlag(url, "getleaves", options.getLeaves);
147
+ setGWebCacheFlag(url, "getclusters", options.getClusters);
148
+ setGWebCacheFlag(url, "getvendors", options.getVendors);
149
+ setGWebCacheFlag(url, "getuptime", options.getUptime);
150
+ }
151
+
152
+ export function buildGWebCacheUrl(
153
+ baseUrl: string,
154
+ options: GWebCacheRequestOptions = {},
155
+ ): string {
156
+ const normalizedBaseUrl = normalizeCacheUrl(baseUrl);
157
+ if (!normalizedBaseUrl)
158
+ throw new Error(`invalid gwebcache url: ${baseUrl}`);
159
+
160
+ const url = new URL(normalizedBaseUrl);
161
+ applyRequestMode(url, options);
162
+ applyRequestMetadata(url, options);
163
+ applyPeerUpdate(url, options.ip);
164
+ applyCacheUpdate(url, options.url);
165
+ applyUpdateStats(url, options);
166
+ applyLookupFlags(url, options);
167
+ return url.toString();
168
+ }
169
+
170
+ function createGWebCacheResponseState(): GWebCacheResponseState {
171
+ return {
172
+ peers: [],
173
+ caches: [],
174
+ warnings: [],
175
+ info: [],
176
+ hostEntries: [],
177
+ cacheEntries: [],
178
+ seenPeers: new Set<string>(),
179
+ seenCaches: new Set<string>(),
180
+ recognized: false,
181
+ };
182
+ }
183
+
184
+ function addResponsePeer(
185
+ state: GWebCacheResponseState,
186
+ value: string,
187
+ ageSec?: number,
188
+ cluster?: string,
189
+ leafCount?: number,
190
+ vendor?: string,
191
+ uptimeSec?: number,
192
+ extraFields: string[] = [],
193
+ ): void {
194
+ const peer = normalizeGWebCachePeer(value);
195
+ if (!peer || state.seenPeers.has(peer)) return;
196
+ state.seenPeers.add(peer);
197
+ state.peers.push(peer);
198
+ state.hostEntries.push({
199
+ peer,
200
+ ageSec,
201
+ cluster,
202
+ leafCount,
203
+ vendor,
204
+ uptimeSec,
205
+ extraFields,
206
+ });
207
+ }
208
+
209
+ function addResponseCache(
210
+ state: GWebCacheResponseState,
211
+ value: string,
212
+ ageSec?: number,
213
+ ): void {
214
+ const cacheUrl = normalizeCacheUrl(value);
215
+ if (!cacheUrl || state.seenCaches.has(cacheUrl)) return;
216
+ state.seenCaches.add(cacheUrl);
217
+ state.caches.push(cacheUrl);
218
+ state.cacheEntries.push({ url: cacheUrl, ageSec });
219
+ }
220
+
221
+ function applyPongInfo(
222
+ state: GWebCacheResponseState,
223
+ values: string[],
224
+ ): void {
225
+ state.pong = {
226
+ name: values[0] || "",
227
+ networks: parseNetworks(values[1]),
228
+ };
229
+ }
230
+
231
+ function applyUpdateInfo(
232
+ state: GWebCacheResponseState,
233
+ values: string[],
234
+ ): void {
235
+ state.update = buildUpdate(values);
236
+ if (state.update.warning) state.warnings.push(state.update.warning);
237
+ }
238
+
239
+ function applyWarningInfo(
240
+ state: GWebCacheResponseState,
241
+ values: string[],
242
+ ): void {
243
+ const warning = parseWarning(values);
244
+ if (warning) state.warnings.push(warning);
245
+ }
246
+
247
+ function parseInfoLineIntoState(
248
+ state: GWebCacheResponseState,
249
+ parts: string[],
250
+ ): void {
251
+ const key = parts[1]?.trim().toLowerCase() || "";
252
+ const values = parts.slice(2).map((value) => value.trim());
253
+ if (key === "pong") return applyPongInfo(state, values);
254
+ if (key === "update") return applyUpdateInfo(state, values);
255
+ if (key === "warning") return applyWarningInfo(state, values);
256
+ if (key) state.info.push({ key, values });
257
+ }
258
+
259
+ function parseHostLineIntoState(
260
+ state: GWebCacheResponseState,
261
+ parts: string[],
262
+ ): void {
263
+ const values = parts.slice(1).map((value) => value.trim());
264
+ addResponsePeer(
265
+ state,
266
+ values[0] || "",
267
+ parseAge(values[1]),
268
+ values[2] || undefined,
269
+ parseAge(values[3]),
270
+ values[4] || undefined,
271
+ parseAge(values[5]),
272
+ values.slice(6).filter(Boolean),
273
+ );
274
+ }
275
+
276
+ function parseCacheLineIntoState(
277
+ state: GWebCacheResponseState,
278
+ parts: string[],
279
+ ): void {
280
+ const values = parts.slice(1).map((value) => value.trim());
281
+ addResponseCache(state, values[0] || "", parseAge(values[1]));
282
+ }
283
+
284
+ function parseResponseLineIntoState(
285
+ state: GWebCacheResponseState,
286
+ line: string,
287
+ ): void {
288
+ const parts = line.split("|");
289
+ const code = parts[0]?.trim().toUpperCase();
290
+ if (code === "I") {
291
+ state.recognized = true;
292
+ parseInfoLineIntoState(state, parts);
293
+ return;
294
+ }
295
+ if (code === "H") {
296
+ state.recognized = true;
297
+ parseHostLineIntoState(state, parts);
298
+ return;
299
+ }
300
+ if (code === "U") {
301
+ state.recognized = true;
302
+ parseCacheLineIntoState(state, parts);
303
+ }
304
+ }
305
+
306
+ export function parseGWebCacheResponse(body: string): GWebCacheResponse {
307
+ const rawLines = splitBodyLines(body);
308
+ const state = createGWebCacheResponseState();
309
+ for (const line of rawLines) parseResponseLineIntoState(state, line);
310
+
311
+ return {
312
+ spec: state.recognized ? 2 : undefined,
313
+ rawLines,
314
+ peers: state.peers,
315
+ caches: state.caches,
316
+ warnings: [...new Set(state.warnings)],
317
+ info: state.info,
318
+ hostEntries: state.hostEntries,
319
+ cacheEntries: state.cacheEntries,
320
+ pong: state.pong,
321
+ update: state.update,
322
+ };
323
+ }
324
+
325
+ function combineSignals(
326
+ signal: AbortSignal | undefined,
327
+ timeoutMs: number,
328
+ ): {
329
+ signal?: AbortSignal;
330
+ cleanup: () => void;
331
+ } {
332
+ if (!signal && !(timeoutMs > 0)) return { cleanup: () => void 0 };
333
+
334
+ const controller = new AbortController();
335
+ let timeout: ReturnType<typeof setTimeout> | undefined;
336
+ const onAbort = () => controller.abort(signal?.reason);
337
+
338
+ if (signal) {
339
+ if (signal.aborted) controller.abort(signal.reason);
340
+ else signal.addEventListener("abort", onAbort, { once: true });
341
+ }
342
+
343
+ if (timeoutMs > 0) {
344
+ timeout = setTimeout(() => {
345
+ controller.abort(
346
+ new Error(`gwebcache request timed out after ${timeoutMs}ms`),
347
+ );
348
+ }, timeoutMs);
349
+ }
350
+
351
+ return {
352
+ signal: controller.signal,
353
+ cleanup: () => {
354
+ if (timeout) clearTimeout(timeout);
355
+ if (signal) signal.removeEventListener("abort", onAbort);
356
+ },
357
+ };
358
+ }
359
+
360
+ export async function requestGWebCache(
361
+ baseUrl: string,
362
+ options: GWebCacheRequestOptions = {},
363
+ ): Promise<GWebCacheHttpResponse> {
364
+ const requestUrl = buildGWebCacheUrl(baseUrl, options);
365
+ const timeoutMs = Math.max(0, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
366
+ const fetchImpl = options.fetchImpl || fetch;
367
+ const { signal, cleanup } = combineSignals(options.signal, timeoutMs);
368
+
369
+ try {
370
+ const response = await fetchImpl(requestUrl, {
371
+ method: "GET",
372
+ headers: {
373
+ Accept: "text/plain, text/*;q=0.9, */*;q=0.1",
374
+ "Cache-Control": "no-cache",
375
+ "User-Agent": DEFAULT_USER_AGENT,
376
+ },
377
+ signal,
378
+ });
379
+ const body = await response.text();
380
+ return {
381
+ requestUrl,
382
+ body,
383
+ status: response.status,
384
+ statusText: response.statusText,
385
+ ok: response.ok,
386
+ ...parseGWebCacheResponse(body),
387
+ };
388
+ } finally {
389
+ cleanup();
390
+ }
391
+ }
@@ -0,0 +1,116 @@
1
+ import { DEFAULT_USER_AGENT, DEFAULT_VENDOR_CODE } from "../const";
2
+ import {
3
+ isRoutableIpv4,
4
+ normalizePeer,
5
+ parsePeer,
6
+ unique,
7
+ } from "../shared";
8
+ import { KNOWN_CACHES, type GWebCacheBootstrapState } from "./types";
9
+
10
+ export const DEFAULT_TIMEOUT_MS = 5000;
11
+ export const DEFAULT_MAX_PEERS = 20;
12
+ export const DEFAULT_MAX_CACHES = 20;
13
+ export const DEFAULT_MAX_BOOTSTRAP_PEERS = 4096;
14
+ export const DEFAULT_MAX_BOOTSTRAP_CACHES = 256;
15
+
16
+ export function normalizeCacheUrl(value: string): string | undefined {
17
+ const trimmed = String(value || "").trim();
18
+ if (!trimmed) return undefined;
19
+ try {
20
+ const url = new URL(trimmed);
21
+ if (url.protocol !== "http:" && url.protocol !== "https:")
22
+ return undefined;
23
+ url.hash = "";
24
+ return url.toString();
25
+ } catch {
26
+ return undefined;
27
+ }
28
+ }
29
+
30
+ function normalizeCacheList(caches: readonly string[] = []): string[] {
31
+ return unique(
32
+ [...caches]
33
+ .map((cache) => normalizeCacheUrl(cache))
34
+ .filter((cache): cache is string => !!cache),
35
+ );
36
+ }
37
+
38
+ export function seedCacheList(caches?: readonly string[]): string[] {
39
+ return normalizeCacheList(caches?.length ? [...caches] : KNOWN_CACHES);
40
+ }
41
+
42
+ export function aliveCachesForState(
43
+ state: GWebCacheBootstrapState | undefined,
44
+ ): string[] {
45
+ return normalizeCacheList(state?.aliveCaches);
46
+ }
47
+
48
+ export function rememberAliveCaches(
49
+ state: GWebCacheBootstrapState | undefined,
50
+ caches: readonly string[],
51
+ ): void {
52
+ if (!state) return;
53
+ state.aliveCaches = normalizeCacheList([
54
+ ...aliveCachesForState(state),
55
+ ...caches,
56
+ ]);
57
+ }
58
+
59
+ export function sanitizeClient(value: string | undefined): string {
60
+ const client = String(value || DEFAULT_VENDOR_CODE)
61
+ .trim()
62
+ .toUpperCase();
63
+ return client.slice(0, 4) || DEFAULT_VENDOR_CODE;
64
+ }
65
+
66
+ export function sanitizeVersion(value: string | undefined): string {
67
+ const version = String(value || DEFAULT_USER_AGENT).trim();
68
+ return version || DEFAULT_USER_AGENT;
69
+ }
70
+
71
+ export function normalizeNetwork(
72
+ value: string | undefined,
73
+ ): "gnutella" | "gnutella2" {
74
+ const network = String(value || "gnutella")
75
+ .trim()
76
+ .toLowerCase();
77
+ if (network === "gnutella" || network === "gnutella2") return network;
78
+ throw new Error(`unsupported gwebcache network: ${value}`);
79
+ }
80
+
81
+ export function normalizeGWebCachePeer(value: string): string | undefined {
82
+ const parsed = parsePeer(value);
83
+ if (!parsed) return undefined;
84
+ if (!isRoutableIpv4(parsed.host)) return undefined;
85
+ return normalizePeer(parsed.host, parsed.port);
86
+ }
87
+
88
+ export function parseAge(value: string | undefined): number | undefined {
89
+ if (value == null || value === "") return undefined;
90
+ const age = Number(value);
91
+ return Number.isInteger(age) && age >= 0 ? age : undefined;
92
+ }
93
+
94
+ export function parseWarning(values: string[]): string | undefined {
95
+ const warning = values.join("|").trim();
96
+ return warning || undefined;
97
+ }
98
+
99
+ export function parseNetworks(value: string | undefined): string[] {
100
+ if (!value) return [];
101
+ return unique(
102
+ value
103
+ .split("-")
104
+ .map((part) => part.trim())
105
+ .filter(Boolean),
106
+ );
107
+ }
108
+
109
+ export function splitBodyLines(body: string): string[] {
110
+ return body
111
+ .replace(/^\uFEFF/, "")
112
+ .replace(/\r\n/g, "\n")
113
+ .split("\n")
114
+ .map((line) => line.trim())
115
+ .filter(Boolean);
116
+ }
@@ -0,0 +1,187 @@
1
+ type GWebCacheMode = "get" | "update";
2
+
3
+ type FetchLike = (
4
+ input: string | URL | Request,
5
+ init?: RequestInit,
6
+ ) => Promise<Response>;
7
+
8
+ export type GWebCacheRequestOptions = {
9
+ mode?: GWebCacheMode;
10
+ network?: "gnutella" | "gnutella2";
11
+ client?: string;
12
+ version?: string;
13
+ ping?: boolean;
14
+ spec?: 2;
15
+ ip?: string;
16
+ url?: string;
17
+ cluster?: string;
18
+ leafCount?: number;
19
+ maxLeaves?: number;
20
+ uptimeSec?: number;
21
+ getLeaves?: boolean;
22
+ getClusters?: boolean;
23
+ getVendors?: boolean;
24
+ getUptime?: boolean;
25
+ timeoutMs?: number;
26
+ signal?: AbortSignal;
27
+ fetchImpl?: FetchLike;
28
+ };
29
+
30
+ export type GWebCacheInfoLine = {
31
+ key: string;
32
+ values: string[];
33
+ };
34
+
35
+ export type GWebCachePong = {
36
+ name: string;
37
+ networks: string[];
38
+ };
39
+
40
+ export type GWebCacheUpdate = {
41
+ ok: boolean;
42
+ warning?: string;
43
+ values: string[];
44
+ };
45
+
46
+ export type GWebCacheHostEntry = {
47
+ peer: string;
48
+ ageSec?: number;
49
+ cluster?: string;
50
+ leafCount?: number;
51
+ vendor?: string;
52
+ uptimeSec?: number;
53
+ extraFields: string[];
54
+ };
55
+
56
+ export type GWebCacheCacheEntry = {
57
+ url: string;
58
+ ageSec?: number;
59
+ };
60
+
61
+ export type GWebCacheResponse = {
62
+ spec?: 2;
63
+ rawLines: string[];
64
+ peers: string[];
65
+ caches: string[];
66
+ warnings: string[];
67
+ info: GWebCacheInfoLine[];
68
+ hostEntries: GWebCacheHostEntry[];
69
+ cacheEntries: GWebCacheCacheEntry[];
70
+ pong?: GWebCachePong;
71
+ update?: GWebCacheUpdate;
72
+ };
73
+
74
+ export type GWebCacheHttpResponse = GWebCacheResponse & {
75
+ requestUrl: string;
76
+ body: string;
77
+ status: number;
78
+ statusText: string;
79
+ ok: boolean;
80
+ };
81
+
82
+ export type BootstrapOptions = {
83
+ caches?: readonly string[];
84
+ client?: string;
85
+ version?: string;
86
+ network?: "gnutella" | "gnutella2";
87
+ timeoutMs?: number;
88
+ maxPeers?: number;
89
+ maxCaches?: number;
90
+ queryAll?: boolean;
91
+ signal?: AbortSignal;
92
+ fetchImpl?: FetchLike;
93
+ };
94
+
95
+ export type BootstrapResult = {
96
+ peers: string[];
97
+ caches: string[];
98
+ queriedCaches: string[];
99
+ successfulCaches: string[];
100
+ errors: Array<{
101
+ cache: string;
102
+ message: string;
103
+ }>;
104
+ };
105
+
106
+ export type BootstrapPeer = {
107
+ host: string;
108
+ port: number;
109
+ peer: string;
110
+ };
111
+
112
+ export type GWebCacheBootstrapState = {
113
+ active?: boolean;
114
+ lastExhaustedPeerSet?: string;
115
+ aliveCaches?: string[];
116
+ };
117
+
118
+ export type ConnectBootstrapOptions = BootstrapOptions & {
119
+ peers: readonly string[];
120
+ connectTimeoutMs: number;
121
+ connectConcurrency: number;
122
+ connectedCount: () => number;
123
+ availableSlots: () => number;
124
+ connectPeer: (
125
+ host: string,
126
+ port: number,
127
+ timeoutMs: number,
128
+ ) => Promise<void>;
129
+ addPeer?: (peer: string) => void;
130
+ isSelfPeer?: (host: string, port: number) => boolean;
131
+ maxBootstrapPeers?: number;
132
+ maxBootstrapCaches?: number;
133
+ state?: GWebCacheBootstrapState;
134
+ };
135
+
136
+ export type ConnectBootstrapResult = {
137
+ attemptedPeers: string[];
138
+ fetchedFromCaches: boolean;
139
+ addedPeers: string[];
140
+ queriedCaches: string[];
141
+ errors: BootstrapResult["errors"];
142
+ };
143
+
144
+ export type ReportSelfOptions = {
145
+ caches?: readonly string[];
146
+ client?: string;
147
+ version?: string;
148
+ timeoutMs?: number;
149
+ ip: string;
150
+ cluster?: string;
151
+ leafCount?: number;
152
+ maxLeaves?: number;
153
+ uptimeSec?: number;
154
+ state?: GWebCacheBootstrapState;
155
+ signal?: AbortSignal;
156
+ fetchImpl?: FetchLike;
157
+ };
158
+
159
+ export type ReportSelfResult = {
160
+ referenceCache?: string;
161
+ attemptedCaches: string[];
162
+ reportedCaches: string[];
163
+ errors: BootstrapResult["errors"];
164
+ };
165
+
166
+ export const KNOWN_CACHES = [
167
+ "http://bj.ddns.net/beacon/gwc.php",
168
+ "http://bj.ddns.net/cachechu/",
169
+ "http://bj.ddns.net/gnucache/gcache.php",
170
+ "http://bj.ddns.net/gwebcache/gcache.php",
171
+ "http://bj.ddns.net/perlcache/perlgcache.cgi",
172
+ "http://bj.ddns.net/phpgnucacheii/gwcii.php",
173
+ "http://bj.ddns.net/skulls/skulls.php",
174
+ "http://cache.jayl.de/g2/gwc.php/",
175
+ "http://cache.trillinux.org/g2/bazooka.php",
176
+ "http://dkac.trillinux.org/dkac/dkac.php/",
177
+ "http://fascination77.free.fr/cachechu/",
178
+ "http://gweb3.4octets.co.uk/gwc.php",
179
+ "http://gweb4.4octets.co.uk/index.php",
180
+ "http://midian.jayl.de/g2/bazooka.php",
181
+ "http://p2p.findclan.net/skulls.php",
182
+ "http://skulls.gwc.dyslexicfish.net/skulls.php",
183
+ "http://www.paper.gwc.dyslexicfish.net/",
184
+ "http://www.rock.gwc.dyslexicfish.net/",
185
+ "http://www.scissors.gwc.dyslexicfish.net/",
186
+ "https://www.paper.gwc.dyslexicfish.net/",
187
+ ] as const;
@@ -0,0 +1,13 @@
1
+ export {
2
+ connectBootstrapPeers,
3
+ fetchBootstrapData,
4
+ getMorePeers,
5
+ reportSelfToGWebCaches,
6
+ } from "./gwebcache/bootstrap";
7
+ export {
8
+ buildGWebCacheUrl,
9
+ parseGWebCacheResponse,
10
+ requestGWebCache,
11
+ } from "./gwebcache/response";
12
+ export { KNOWN_CACHES } from "./gwebcache/types";
13
+ export type { GWebCacheBootstrapState } from "./gwebcache/types";