@bitsocial/bitsocial-cli 0.19.39

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 (80) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +706 -0
  3. package/bin/dev +20 -0
  4. package/bin/dev.cmd +3 -0
  5. package/bin/postinstall.js +125 -0
  6. package/bin/run +13 -0
  7. package/bin/run.cmd +3 -0
  8. package/dist/challenge-packages/challenge-utils.d.ts +24 -0
  9. package/dist/challenge-packages/challenge-utils.js +304 -0
  10. package/dist/cli/base-command.d.ts +11 -0
  11. package/dist/cli/base-command.js +45 -0
  12. package/dist/cli/commands/challenge/install.d.ts +12 -0
  13. package/dist/cli/commands/challenge/install.js +131 -0
  14. package/dist/cli/commands/challenge/list.d.ts +10 -0
  15. package/dist/cli/commands/challenge/list.js +37 -0
  16. package/dist/cli/commands/challenge/remove.d.ts +12 -0
  17. package/dist/cli/commands/challenge/remove.js +60 -0
  18. package/dist/cli/commands/community/create.d.ts +12 -0
  19. package/dist/cli/commands/community/create.js +54 -0
  20. package/dist/cli/commands/community/delete.d.ts +10 -0
  21. package/dist/cli/commands/community/delete.js +44 -0
  22. package/dist/cli/commands/community/edit.d.ts +12 -0
  23. package/dist/cli/commands/community/edit.js +74 -0
  24. package/dist/cli/commands/community/get.d.ts +9 -0
  25. package/dist/cli/commands/community/get.js +32 -0
  26. package/dist/cli/commands/community/list.d.ts +9 -0
  27. package/dist/cli/commands/community/list.js +30 -0
  28. package/dist/cli/commands/community/start.d.ts +13 -0
  29. package/dist/cli/commands/community/start.js +46 -0
  30. package/dist/cli/commands/community/stop.d.ts +10 -0
  31. package/dist/cli/commands/community/stop.js +44 -0
  32. package/dist/cli/commands/daemon.d.ts +14 -0
  33. package/dist/cli/commands/daemon.js +484 -0
  34. package/dist/cli/commands/logs.d.ts +24 -0
  35. package/dist/cli/commands/logs.js +199 -0
  36. package/dist/cli/commands/subplebbit/create.d.ts +12 -0
  37. package/dist/cli/commands/subplebbit/create.js +54 -0
  38. package/dist/cli/commands/subplebbit/edit.d.ts +12 -0
  39. package/dist/cli/commands/subplebbit/edit.js +73 -0
  40. package/dist/cli/commands/subplebbit/get.d.ts +9 -0
  41. package/dist/cli/commands/subplebbit/get.js +32 -0
  42. package/dist/cli/commands/subplebbit/list.d.ts +9 -0
  43. package/dist/cli/commands/subplebbit/list.js +30 -0
  44. package/dist/cli/commands/subplebbit/start.d.ts +10 -0
  45. package/dist/cli/commands/subplebbit/start.js +41 -0
  46. package/dist/cli/commands/subplebbit/stop.d.ts +10 -0
  47. package/dist/cli/commands/subplebbit/stop.js +43 -0
  48. package/dist/cli/commands/update/check.d.ts +6 -0
  49. package/dist/cli/commands/update/check.js +28 -0
  50. package/dist/cli/commands/update/install.d.ts +12 -0
  51. package/dist/cli/commands/update/install.js +63 -0
  52. package/dist/cli/commands/update/versions.d.ts +9 -0
  53. package/dist/cli/commands/update/versions.js +29 -0
  54. package/dist/cli/hooks/init/version-hook.d.ts +3 -0
  55. package/dist/cli/hooks/init/version-hook.js +43 -0
  56. package/dist/cli/hooks/prerun/parse-dynamic-flags-hook.d.ts +3 -0
  57. package/dist/cli/hooks/prerun/parse-dynamic-flags-hook.js +94 -0
  58. package/dist/cli/types.d.ts +4 -0
  59. package/dist/cli/types.js +1 -0
  60. package/dist/common-utils/data-migration.d.ts +1 -0
  61. package/dist/common-utils/data-migration.js +27 -0
  62. package/dist/common-utils/defaults.d.ts +9 -0
  63. package/dist/common-utils/defaults.js +10 -0
  64. package/dist/common-utils/resolvers.d.ts +2 -0
  65. package/dist/common-utils/resolvers.js +6 -0
  66. package/dist/index.d.ts +1 -0
  67. package/dist/index.js +1 -0
  68. package/dist/ipfs/startIpfs.d.ts +3 -0
  69. package/dist/ipfs/startIpfs.js +304 -0
  70. package/dist/seeder.d.ts +1 -0
  71. package/dist/seeder.js +83 -0
  72. package/dist/update/npm-registry.d.ts +6 -0
  73. package/dist/update/npm-registry.js +66 -0
  74. package/dist/update/semver.d.ts +5 -0
  75. package/dist/update/semver.js +29 -0
  76. package/dist/util.d.ts +31 -0
  77. package/dist/util.js +157 -0
  78. package/dist/webui/daemon-server.d.ts +10 -0
  79. package/dist/webui/daemon-server.js +140 -0
  80. package/package.json +143 -0
package/dist/seeder.js ADDED
@@ -0,0 +1,83 @@
1
+ // import lodash from "lodash";
2
+ // import Logger from "@plebbit/plebbit-logger";
3
+ // import { Plebbit } from "@plebbit/plebbit-js/dist/node/plebbit";
4
+ // import { BasePages } from "@plebbit/plebbit-js/dist/node/pages";
5
+ // import { Comment } from "@plebbit/plebbit-js/dist/node/comment";
6
+ // import assert from "assert";
7
+ // //@ts-expect-error
8
+ // import { CID } from "ipfs-http-client";
9
+ // import { Subplebbit } from "@plebbit/plebbit-js/dist/node/subplebbit/subplebbit";
10
+ export {};
11
+ // async function _loadAllPages(pageCid: string, pagesInstance: BasePages): Promise<Comment[]> {
12
+ // const log = Logger("plebbit-cli:server:seed:_loadAllPages");
13
+ // try {
14
+ // let sortedCommentsPage = await pagesInstance.getPage(pageCid);
15
+ // let sortedComments: Comment[] = sortedCommentsPage.comments;
16
+ // while (sortedCommentsPage.nextCid) {
17
+ // sortedCommentsPage = await pagesInstance.getPage(sortedCommentsPage.nextCid);
18
+ // sortedComments = sortedComments.concat(sortedCommentsPage.comments);
19
+ // }
20
+ // return sortedComments;
21
+ // } catch (e) {
22
+ // log.error(`Failed to load page (${pageCid}) of sub (${pagesInstance._subplebbitAddress}) due to error:`, e);
23
+ // return [];
24
+ // }
25
+ // }
26
+ // const seededIpns: Record<string, { lastSeededAt?: number }> = {};
27
+ // async function _seedSub(sub: Subplebbit, pinnedCids: string[]) {
28
+ // const log = Logger("plebbit-cli:server:seed");
29
+ // if (sub.statsCid) await sub.plebbit.fetchCid(sub.statsCid); // Seed stats
30
+ // await sub.plebbit.pubsubSubscribe(sub.pubsubTopic || sub.address);
31
+ // // Load all pages
32
+ // if (sub.posts.pageCids) {
33
+ // const pagesLoaded = await Promise.all(Object.values(sub.posts.pageCids).map((pageCid) => _loadAllPages(pageCid, sub.posts)));
34
+ // // What if one of pages fail to load
35
+ // log.trace(`Loaded the newest pages of sub (${sub.address}) to seed`);
36
+ // const pageNames = Object.keys(sub.posts.pageCids);
37
+ // const loadedPagesWithNames = lodash.zipObject(pageNames, pagesLoaded);
38
+ // const allCidsToPin: string[] = [];
39
+ // if (loadedPagesWithNames["new"]) {
40
+ // // Fetch all comments CID
41
+ // allCidsToPin.push(...loadedPagesWithNames["new"].map((comment) => <string>comment.cid));
42
+ // // Seed IPNS
43
+ // for (const comment of loadedPagesWithNames["new"]) {
44
+ // assert(comment.ipnsName);
45
+ // if (seededIpns[comment.ipnsName]?.lastSeededAt !== comment.updatedAt) {
46
+ // try {
47
+ // await comment._clientsManager.fetchCommentUpdate(comment.ipnsName);
48
+ // seededIpns[comment.ipnsName] = { lastSeededAt: comment.updatedAt };
49
+ // log.trace(`Seeded comment (${comment.cid}) IPNS (${comment.ipnsName})`);
50
+ // } catch (e) {
51
+ // log.error(`Failed to seed comment (${comment.cid}) IPNS (${comment.ipnsName}) due to error`, e);
52
+ // }
53
+ // }
54
+ // }
55
+ // }
56
+ // // Pin cids that are not already pinned
57
+ // const newCidsToPin = lodash.difference(lodash.uniq(allCidsToPin), pinnedCids).map((cidString) => CID.parse(cidString));
58
+ // if (newCidsToPin.length > 0) {
59
+ // log.trace(`Attempting to pin ${newCidsToPin.length} comments' cids from sub (${sub.address}): `, newCidsToPin);
60
+ // const defaultIpfsClient = Object.values(sub.plebbit.clients.ipfsClients)[0];
61
+ // assert(defaultIpfsClient);
62
+ // await defaultIpfsClient._client.pin.addAll(newCidsToPin);
63
+ // log.trace(`Pinned ${newCidsToPin.length} cids from sub (${sub.address})`);
64
+ // } else log.trace(`All ${lodash.uniq(allCidsToPin).length} cids from sub (${sub.address}) are pinned`);
65
+ // }
66
+ // }
67
+ // export async function seedSubplebbits(subAddresses: string[], plebbit: Plebbit) {
68
+ // const log = Logger("plebbit-cli:server:seed");
69
+ // log.trace("test"); // remove this line later
70
+ // for (const subAddress of subAddresses) {
71
+ // try {
72
+ // const sub = await plebbit.getSubplebbit(subAddress);
73
+ // log.trace(`Loaded the newest record of sub (${subAddress}) for seeding`);
74
+ // const defaultIpfsClient = Object.values(sub.plebbit.clients.ipfsClients)[0];
75
+ // assert(defaultIpfsClient);
76
+ // const pinnedCids: string[] = (await defaultIpfsClient._client.pin.ls()).map((pin) => pin.cid.toString());
77
+ // await _seedSub(sub, pinnedCids);
78
+ // } catch (e) {
79
+ // log.error(`Failed to load and seed sub (${subAddress}):`, String(e));
80
+ // }
81
+ // }
82
+ // log(`Finished this round of seeding. Will seed again later`);
83
+ // }
@@ -0,0 +1,6 @@
1
+ /** Query npm registry for the latest published version. */
2
+ export declare function fetchLatestVersion(): Promise<string>;
3
+ /** Query npm registry for all published versions (oldest-first). */
4
+ export declare function fetchAllVersions(): Promise<string[]>;
5
+ /** Install a specific version globally via npm install -g. Streams output to terminal. */
6
+ export declare function installGlobal(version: string): Promise<void>;
@@ -0,0 +1,66 @@
1
+ import { spawn } from "child_process";
2
+ import { getNpmCliPath, getNpmEnv, ensureNpmAvailable } from "../challenge-packages/challenge-utils.js";
3
+ const PACKAGE_NAME = "@bitsocial/bitsocial-cli";
4
+ function runNpmView(args) {
5
+ return new Promise(async (resolve, reject) => {
6
+ const npmCliPath = await getNpmCliPath();
7
+ const proc = spawn(process.execPath, [npmCliPath, "view", PACKAGE_NAME, ...args, "--json"], {
8
+ stdio: ["ignore", "pipe", "pipe"],
9
+ env: getNpmEnv()
10
+ });
11
+ let stdout = "";
12
+ let stderr = "";
13
+ proc.stdout.on("data", (data) => {
14
+ stdout += data.toString();
15
+ });
16
+ proc.stderr.on("data", (data) => {
17
+ stderr += data.toString();
18
+ });
19
+ proc.on("error", (err) => {
20
+ reject(new Error(`Failed to run npm view: ${err.message}`));
21
+ });
22
+ proc.on("close", (code) => {
23
+ if (code === 0) {
24
+ resolve(stdout.trim());
25
+ }
26
+ else {
27
+ reject(new Error(`npm view exited with code ${code}: ${stderr.trim()}`));
28
+ }
29
+ });
30
+ });
31
+ }
32
+ /** Query npm registry for the latest published version. */
33
+ export async function fetchLatestVersion() {
34
+ await ensureNpmAvailable();
35
+ const raw = await runNpmView(["version"]);
36
+ // npm view <pkg> version --json returns a quoted string like "0.19.40"
37
+ return JSON.parse(raw);
38
+ }
39
+ /** Query npm registry for all published versions (oldest-first). */
40
+ export async function fetchAllVersions() {
41
+ await ensureNpmAvailable();
42
+ const raw = await runNpmView(["versions"]);
43
+ return JSON.parse(raw);
44
+ }
45
+ /** Install a specific version globally via npm install -g. Streams output to terminal. */
46
+ export async function installGlobal(version) {
47
+ await ensureNpmAvailable();
48
+ const npmCliPath = await getNpmCliPath();
49
+ return new Promise((resolve, reject) => {
50
+ const proc = spawn(process.execPath, [npmCliPath, "install", "-g", `${PACKAGE_NAME}@${version}`], {
51
+ stdio: ["ignore", "pipe", "pipe"],
52
+ env: getNpmEnv()
53
+ });
54
+ proc.stdout?.pipe(process.stdout);
55
+ proc.stderr?.pipe(process.stderr);
56
+ proc.on("error", (err) => {
57
+ reject(new Error(`Failed to run npm install: ${err.message}`));
58
+ });
59
+ proc.on("close", (code) => {
60
+ if (code === 0)
61
+ resolve();
62
+ else
63
+ reject(new Error(`npm install -g exited with code ${code}`));
64
+ });
65
+ });
66
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Minimal semver comparison. Strips leading "v", splits on ".",
3
+ * compares each segment numerically left-to-right.
4
+ */
5
+ export declare function compareVersions(a: string, b: string): -1 | 0 | 1;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Minimal semver comparison. Strips leading "v", splits on ".",
3
+ * compares each segment numerically left-to-right.
4
+ */
5
+ export function compareVersions(a, b) {
6
+ const normalize = (v) => v.replace(/^v/i, "");
7
+ const partsA = normalize(a).split(".").map(Number);
8
+ const partsB = normalize(b).split(".").map(Number);
9
+ const len = Math.max(partsA.length, partsB.length);
10
+ for (let i = 0; i < len; i++) {
11
+ const na = partsA[i] ?? 0;
12
+ const nb = partsB[i] ?? 0;
13
+ if (Number.isNaN(na) || Number.isNaN(nb)) {
14
+ // Fall back to string compare for non-numeric segments
15
+ const sa = String(partsA[i] ?? "");
16
+ const sb = String(partsB[i] ?? "");
17
+ if (sa < sb)
18
+ return -1;
19
+ if (sa > sb)
20
+ return 1;
21
+ continue;
22
+ }
23
+ if (na < nb)
24
+ return -1;
25
+ if (na > nb)
26
+ return 1;
27
+ }
28
+ return 0;
29
+ }
package/dist/util.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ export type PKCLogger = Awaited<ReturnType<typeof getPKCLogger>> & {
2
+ inspectOpts?: {
3
+ depth?: number;
4
+ colors?: boolean;
5
+ [key: string]: any;
6
+ };
7
+ };
8
+ export declare function getPKCLogger(): Promise<typeof import("@pkcprotocol/pkc-logger").default>;
9
+ /**
10
+ * Read _PKC_DEBUG / DEBUG env vars and configure the Logger instance.
11
+ * Does NOT redirect output — debug logs go to stderr (the default for the debug module).
12
+ *
13
+ * @param options.enableDefaultNamespace - If true, enable "bitsocial*,pkc*,-pkc*trace"
14
+ * when no DEBUG env is set (used by daemon). If false, only enable if user
15
+ * explicitly set DEBUG or _PKC_DEBUG (used by non-daemon commands).
16
+ */
17
+ export declare function setupDebugLogger(Logger: PKCLogger, options?: {
18
+ enableDefaultNamespace?: boolean;
19
+ }): {
20
+ debugNamespace: string | undefined;
21
+ debugDepth: number;
22
+ };
23
+ export declare function getLanIpV4Address(): string | undefined;
24
+ export declare function loadKuboConfigFile(pkcDataPath: string): Promise<any | undefined>;
25
+ export declare function parseMultiAddrKuboRpcToUrl(kuboMultiAddrString: string): Promise<import("url").URL>;
26
+ export declare function parseMultiAddrIpfsGatewayToUrl(ipfsGatewaymultiAddrString: string): Promise<import("url").URL>;
27
+ /**
28
+ * Custom merge function that implements CLI-specific merge behavior.
29
+ * This matches the expected behavior from the test suite.
30
+ */
31
+ export declare function mergeDeep(target: any, source: any): any;
package/dist/util.js ADDED
@@ -0,0 +1,157 @@
1
+ import os from "os";
2
+ import path from "path";
3
+ import fs from "fs";
4
+ import * as fsPromises from "fs/promises";
5
+ export async function getPKCLogger() {
6
+ const Logger = await import("@pkcprotocol/pkc-logger");
7
+ return Logger.default;
8
+ }
9
+ /**
10
+ * Read _PKC_DEBUG / DEBUG env vars and configure the Logger instance.
11
+ * Does NOT redirect output — debug logs go to stderr (the default for the debug module).
12
+ *
13
+ * @param options.enableDefaultNamespace - If true, enable "bitsocial*,pkc*,-pkc*trace"
14
+ * when no DEBUG env is set (used by daemon). If false, only enable if user
15
+ * explicitly set DEBUG or _PKC_DEBUG (used by non-daemon commands).
16
+ */
17
+ export function setupDebugLogger(Logger, options = {}) {
18
+ const envDebug = process.env["_PKC_DEBUG"] || process.env["DEBUG"];
19
+ const debugNamespace = envDebug === "0" || envDebug === "" ? undefined : envDebug;
20
+ const debugDepth = process.env["DEBUG_DEPTH"] ? parseInt(process.env["DEBUG_DEPTH"]) : 10;
21
+ Logger.inspectOpts = Logger.inspectOpts || {};
22
+ Logger.inspectOpts.depth = debugDepth;
23
+ const defaultNamespace = "bitsocial*,pkc*,-pkc*trace";
24
+ if (debugNamespace) {
25
+ Logger.enable(debugNamespace);
26
+ }
27
+ else if (options.enableDefaultNamespace) {
28
+ Logger.enable(defaultNamespace);
29
+ }
30
+ return { debugNamespace, debugDepth };
31
+ }
32
+ export function getLanIpV4Address() {
33
+ const allInterfaces = os.networkInterfaces();
34
+ for (const k in allInterfaces) {
35
+ const specificInterfaceInfos = allInterfaces[k];
36
+ if (!specificInterfaceInfos)
37
+ continue;
38
+ const lanAddress = specificInterfaceInfos.filter((info) => info.family === "IPv4" && !info.internal)[0]
39
+ ?.address;
40
+ if (lanAddress)
41
+ return lanAddress;
42
+ }
43
+ return undefined;
44
+ }
45
+ export async function loadKuboConfigFile(pkcDataPath) {
46
+ const kuboConfigPath = path.join(pkcDataPath, ".ipfs-bitsocial-cli", "config");
47
+ if (!fs.existsSync(kuboConfigPath))
48
+ return undefined;
49
+ const kuboConfig = JSON.parse((await fsPromises.readFile(kuboConfigPath)).toString());
50
+ return kuboConfig;
51
+ }
52
+ async function parseMultiAddr(multiAddrString) {
53
+ const module = await import("@multiformats/multiaddr");
54
+ return module.multiaddr(multiAddrString);
55
+ }
56
+ function multiAddrToHostPort(multiAddrObj) {
57
+ const components = multiAddrObj.getComponents();
58
+ const hostComponent = components.find((component) => ["ip4", "ip6", "dns", "dns4", "dns6", "dnsaddr"].includes(component.name));
59
+ const tcpComponent = components.find((component) => component.name === "tcp");
60
+ const host = hostComponent?.value;
61
+ const port = tcpComponent?.value ? Number(tcpComponent.value) : undefined;
62
+ if (!host || !port || !Number.isFinite(port) || port <= 0)
63
+ return undefined;
64
+ return { host, port };
65
+ }
66
+ export async function parseMultiAddrKuboRpcToUrl(kuboMultiAddrString) {
67
+ const multiAddrObj = await parseMultiAddr(kuboMultiAddrString);
68
+ const parsed = multiAddrToHostPort(multiAddrObj);
69
+ if (!parsed)
70
+ throw new Error(`Unable to parse kubo RPC multiaddr: ${kuboMultiAddrString}`);
71
+ return new URL(`http://${parsed.host}:${parsed.port}/api/v0`);
72
+ }
73
+ export async function parseMultiAddrIpfsGatewayToUrl(ipfsGatewaymultiAddrString) {
74
+ const multiAddrObj = await parseMultiAddr(ipfsGatewaymultiAddrString);
75
+ const parsed = multiAddrToHostPort(multiAddrObj);
76
+ if (!parsed)
77
+ throw new Error(`Unable to parse IPFS gateway multiaddr: ${ipfsGatewaymultiAddrString}`);
78
+ return new URL(`http://${parsed.host}:${parsed.port}`);
79
+ }
80
+ /**
81
+ * Custom merge function that implements CLI-specific merge behavior.
82
+ * This matches the expected behavior from the test suite.
83
+ */
84
+ export function mergeDeep(target, source) {
85
+ function isObject(item) {
86
+ return item && typeof item === "object" && !Array.isArray(item);
87
+ }
88
+ function isPlainObject(item) {
89
+ return isObject(item) && item.constructor === Object;
90
+ }
91
+ // Handle arrays with CLI-specific behavior
92
+ if (Array.isArray(target) && Array.isArray(source)) {
93
+ // Check if source is sparse (has holes/empty items) - indicates indexed assignment like --rules[2]
94
+ const sourceHasHoles = source.length !== Object.keys(source).length;
95
+ if (sourceHasHoles) {
96
+ // Sparse array: merge by index, extending to accommodate both arrays
97
+ const maxLength = Math.max(target.length, source.length);
98
+ const result = new Array(maxLength);
99
+ for (let i = 0; i < maxLength; i++) {
100
+ if (i in source) {
101
+ if (i in target && isPlainObject(target[i]) && isPlainObject(source[i])) {
102
+ result[i] = mergeDeep(target[i], source[i]);
103
+ }
104
+ else {
105
+ result[i] = source[i];
106
+ }
107
+ }
108
+ else if (i in target) {
109
+ result[i] = target[i];
110
+ }
111
+ // If neither has this index, it remains undefined
112
+ }
113
+ return result;
114
+ }
115
+ else {
116
+ // Dense array: CLI behavior is to extend the array to include both original and new values
117
+ // This creates: [source[0], source[1], target[2], target[3], ...]
118
+ const maxLength = target.length + source.length;
119
+ const result = new Array(maxLength);
120
+ // First, place source values at the beginning
121
+ for (let i = 0; i < source.length; i++) {
122
+ result[i] = source[i];
123
+ }
124
+ // Then, place target values at their original indices (beyond source length)
125
+ for (let i = source.length; i < maxLength; i++) {
126
+ const targetIndex = i; // Use the same index, not shifted
127
+ if (targetIndex < target.length) {
128
+ result[i] = target[targetIndex];
129
+ }
130
+ else {
131
+ result[i] = undefined;
132
+ }
133
+ }
134
+ return result;
135
+ }
136
+ }
137
+ // Handle plain objects
138
+ if (isPlainObject(target) && isPlainObject(source)) {
139
+ const result = { ...target };
140
+ for (const key in source) {
141
+ if (source.hasOwnProperty(key)) {
142
+ if (Array.isArray(target[key]) && Array.isArray(source[key])) {
143
+ result[key] = mergeDeep(target[key], source[key]);
144
+ }
145
+ else if (isPlainObject(target[key]) && isPlainObject(source[key])) {
146
+ result[key] = mergeDeep(target[key], source[key]);
147
+ }
148
+ else {
149
+ result[key] = source[key];
150
+ }
151
+ }
152
+ }
153
+ return result;
154
+ }
155
+ // If not both objects/arrays, source takes precedence
156
+ return source;
157
+ }
@@ -0,0 +1,10 @@
1
+ export declare function startDaemonServer(rpcUrl: URL, ipfsGatewayUrl: URL, pkcOptions: any): Promise<{
2
+ rpcAuthKey: string;
3
+ listedSub: string[];
4
+ webuis: {
5
+ name: string;
6
+ endpointLocal: string;
7
+ endpointRemote: string;
8
+ }[];
9
+ destroy: () => Promise<void>;
10
+ }>;
@@ -0,0 +1,140 @@
1
+ import path from "path";
2
+ import { fileURLToPath } from "url";
3
+ const __filename = fileURLToPath(import.meta.url);
4
+ const __dirname = path.dirname(__filename);
5
+ import fs from "fs/promises";
6
+ import { getPKCLogger } from "../util.js";
7
+ import { randomBytes } from "crypto";
8
+ import express from "express";
9
+ import { loadChallengesIntoPKC } from "../challenge-packages/challenge-utils.js";
10
+ async function _generateModifiedIndexHtmlWithRpcSettings(webuiPath, webuiName, ipfsGatewayPort) {
11
+ const indexHtmlString = (await fs.readFile(path.join(webuiPath, "index_backup_no_rpc.html")))
12
+ .toString()
13
+ .replace(/<script>\s*\/\/\s*Redirect non-hash URLs[\s\S]*?<\/script>/, "");
14
+ const defaultRpcOptionString = `[window.location.origin.replace("https://", "wss://").replace("http://", "ws://") + window.location.pathname.split('/' + '${webuiName}')[0]]`;
15
+ // Ipfs media only locally because ipfs gateway doesn't allow remote connections
16
+ const defaultIpfsMedia = `if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname === "0.0.0.0")window.defaultMediaIpfsGatewayUrl = 'http://' + window.location.hostname + ':' + ${ipfsGatewayPort}`;
17
+ const defaultOptionsString = `<script>window.defaultPkcOptions = {pkcRpcClientsOptions: ${defaultRpcOptionString}};${defaultIpfsMedia};console.log(window.defaultPkcOptions, window.defaultMediaIpfsGatewayUrl)</script>`;
18
+ const modifiedIndexHtmlContent = "<!DOCTYPE html>" + defaultOptionsString + indexHtmlString.replace("<!DOCTYPE html>", "");
19
+ return modifiedIndexHtmlContent;
20
+ }
21
+ async function _generateRpcAuthKeyIfNotExisting(pkcDataPath) {
22
+ const pkcRpcAuthKeyPath = path.join(pkcDataPath, "auth-key");
23
+ const envAuthKey = process.env["PKC_RPC_AUTH_KEY"];
24
+ let pkcRpcAuthKey;
25
+ if (envAuthKey) {
26
+ pkcRpcAuthKey = envAuthKey;
27
+ await fs.writeFile(pkcRpcAuthKeyPath, pkcRpcAuthKey);
28
+ }
29
+ else {
30
+ try {
31
+ pkcRpcAuthKey = await fs.readFile(pkcRpcAuthKeyPath, "utf-8");
32
+ }
33
+ catch (e) {
34
+ pkcRpcAuthKey = randomBytes(32).toString("base64").replace(/[/+=]/g, "").substring(0, 40);
35
+ await fs.writeFile(pkcRpcAuthKeyPath, pkcRpcAuthKey, { flag: "wx" });
36
+ }
37
+ }
38
+ return pkcRpcAuthKey;
39
+ }
40
+ // The daemon server will host both RPC and webui on the same port
41
+ export async function startDaemonServer(rpcUrl, ipfsGatewayUrl, pkcOptions) {
42
+ // Start pkc-js RPC
43
+ const log = (await getPKCLogger())("bitsocial-cli:daemon:startDaemonServer");
44
+ const webuiExpressApp = express();
45
+ const httpServer = webuiExpressApp.listen(Number(rpcUrl.port));
46
+ log("HTTP server is running on", "0.0.0.0" + ":" + rpcUrl.port);
47
+ const rpcAuthKey = await _generateRpcAuthKeyIfNotExisting(pkcOptions.dataPath);
48
+ const PKCRpc = await import("@pkcprotocol/pkc-js/rpc");
49
+ // Will add ability to edit later, but it's hard coded for now
50
+ log("Will be passing pkc options to RPC server", pkcOptions);
51
+ const rpcServer = await PKCRpc.default.PKCWsServer({
52
+ server: httpServer,
53
+ pkcOptions: pkcOptions,
54
+ authKey: rpcAuthKey
55
+ });
56
+ const webuisDir = path.join(__dirname, "..", "..", "dist", "webuis");
57
+ const webUiNames = (await fs.readdir(webuisDir, { withFileTypes: true })).filter((file) => file.isDirectory()).map((file) => file.name);
58
+ const webuis = [];
59
+ log("Discovered webuis", webUiNames);
60
+ for (const webuiNameWithVersion of webUiNames) {
61
+ const webuiDirPath = path.join(webuisDir, webuiNameWithVersion);
62
+ const webuiName = webuiNameWithVersion.split("-")[0]; // should be "seedit", "plebones"
63
+ const modifiedIndexHtmlString = await _generateModifiedIndexHtmlWithRpcSettings(webuiDirPath, webuiName, Number(ipfsGatewayUrl.port));
64
+ const endpointLocal = `/${webuiName}`;
65
+ webuiExpressApp.use(endpointLocal, express.static(webuiDirPath, { index: false }));
66
+ webuiExpressApp.get(endpointLocal, (req, res, next) => {
67
+ const isLocal = req.socket.localAddress && req.socket.localAddress === req.socket.remoteAddress;
68
+ log("Received local connection request for webui", endpointLocal, "with socket.localAddress", req.socket.localAddress, "and socket.remoteAddress", req.socket.remoteAddress);
69
+ if (!isLocal)
70
+ res.status(403).send("This endpoint does not exist for remote connections");
71
+ else {
72
+ res.set("Cache-Control", "no-store, no-cache, must-revalidate, private");
73
+ res.set("Expires", "-1");
74
+ res.set("Pragma", "no-cache");
75
+ res.type("html").send(modifiedIndexHtmlString);
76
+ }
77
+ });
78
+ const endpointRemote = `/${rpcAuthKey}/${webuiName}`;
79
+ webuiExpressApp.use(endpointRemote, express.static(webuiDirPath, { index: false }));
80
+ webuiExpressApp.get(endpointRemote, (req, res, next) => {
81
+ const isLocal = req.socket.localAddress && req.socket.localAddress === req.socket.remoteAddress;
82
+ log("Received remote connection request for webui", endpointLocal, "with socket.localAddress", req.socket.localAddress, "and socket.remoteAddress", req.socket.remoteAddress, "with req.url", req.url);
83
+ if (isLocal) {
84
+ res.redirect(`http://localhost:${rpcUrl.port}/${webuiName}`);
85
+ }
86
+ else {
87
+ res.set("Cache-Control", "public, max-age=600"); // 600 seconds = 10 minutes
88
+ res.type("html").send(modifiedIndexHtmlString);
89
+ }
90
+ });
91
+ webuis.push({ name: webuiName, endpointLocal, endpointRemote });
92
+ }
93
+ // Challenge reload endpoints
94
+ const handleChallengeReload = async (_req, res) => {
95
+ try {
96
+ const loadedNames = await loadChallengesIntoPKC(pkcOptions.dataPath);
97
+ // Notify all connected RPC clients about the updated challenges
98
+ const onSettingsChange = rpcServer._onSettingsChange;
99
+ if (onSettingsChange) {
100
+ for (const connectionId of Object.keys(onSettingsChange)) {
101
+ const handlers = onSettingsChange[connectionId];
102
+ if (!handlers)
103
+ continue;
104
+ for (const subscriptionId of Object.keys(handlers)) {
105
+ const handler = handlers[subscriptionId];
106
+ if (handler)
107
+ await handler({ newPKC: rpcServer.pkc });
108
+ }
109
+ }
110
+ }
111
+ res.json({ ok: true, challenges: loadedNames });
112
+ }
113
+ catch (err) {
114
+ log.error("Failed to reload challenges", err);
115
+ res.status(500).json({ ok: false, error: String(err) });
116
+ }
117
+ };
118
+ // Local-only endpoint (same isLocal check as webui routes)
119
+ webuiExpressApp.post("/api/challenges/reload", (req, res) => {
120
+ const isLocal = req.socket.localAddress && req.socket.localAddress === req.socket.remoteAddress;
121
+ if (!isLocal) {
122
+ res.status(403).send("This endpoint does not exist for remote connections");
123
+ return;
124
+ }
125
+ handleChallengeReload(req, res);
126
+ });
127
+ // Remote endpoint with auth key
128
+ webuiExpressApp.post(`/${rpcAuthKey}/api/challenges/reload`, (req, res) => {
129
+ handleChallengeReload(req, res);
130
+ });
131
+ let daemonServerDestroyed = false;
132
+ const cleanupDaemonServer = async () => {
133
+ if (daemonServerDestroyed)
134
+ return;
135
+ await rpcServer.destroy();
136
+ httpServer.close();
137
+ daemonServerDestroyed = true;
138
+ };
139
+ return { rpcAuthKey, listedSub: rpcServer.pkc.communities, webuis, destroy: cleanupDaemonServer };
140
+ }