@pkcprotocol/pkc-js 0.0.45 → 0.0.46
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/browser/clients/rpc-client/schema.d.ts +2 -2
- package/dist/browser/community/schema.d.ts +18 -18
- package/dist/browser/generated-version.d.ts +1 -1
- package/dist/browser/generated-version.js +1 -1
- package/dist/browser/index-with-compile-cache.d.ts +254 -0
- package/dist/browser/index-with-compile-cache.js +25 -0
- package/dist/browser/index-with-compile-cache.js.map +1 -0
- package/dist/browser/pages/schema.d.ts +5 -5
- package/dist/browser/pkc/pkc.d.ts +3 -4
- package/dist/browser/pkc/pkc.js +16 -19
- package/dist/browser/pkc/pkc.js.map +1 -1
- package/dist/browser/publications/comment/schema.d.ts +2 -2
- package/dist/browser/publications/comment/schema.js +7 -1
- package/dist/browser/publications/comment/schema.js.map +1 -1
- package/dist/browser/rpc/src/index.js +10 -1
- package/dist/browser/rpc/src/index.js.map +1 -1
- package/dist/browser/runtime/browser/compile-cache.d.ts +1 -0
- package/dist/browser/runtime/browser/compile-cache.js +7 -0
- package/dist/browser/runtime/browser/compile-cache.js.map +1 -0
- package/dist/browser/runtime/browser/setup-kubo-http-routers.d.ts +1 -0
- package/dist/browser/runtime/browser/setup-kubo-http-routers.js +4 -0
- package/dist/browser/runtime/browser/setup-kubo-http-routers.js.map +1 -0
- package/dist/browser/runtime/node/compile-cache.d.ts +1 -0
- package/dist/browser/runtime/node/compile-cache.js +29 -0
- package/dist/browser/runtime/node/compile-cache.js.map +1 -0
- package/dist/browser/runtime/node/setup-kubo-http-routers.d.ts +2 -0
- package/dist/browser/runtime/node/{setup-kubo-address-rewriter-and-http-router.js → setup-kubo-http-routers.js} +10 -79
- package/dist/browser/runtime/node/setup-kubo-http-routers.js.map +1 -0
- package/dist/bundled/bundle-manifest.json +1288 -0
- package/dist/bundled/challenges.js +1 -0
- package/dist/bundled/chunks/helia-for-pkc-uRINWDzG.js +281 -0
- package/dist/bundled/chunks/local-community-CCSP8e6_.js +2 -0
- package/dist/bundled/chunks/local-community-iVvgerOY.js +7218 -0
- package/dist/bundled/chunks/node-DMNVMYGv.js +7567 -0
- package/dist/bundled/chunks/rpc-local-community-DG6G0xL6.js +128945 -0
- package/dist/bundled/chunks/schema-Bo3XK8Oe.js +34342 -0
- package/dist/bundled/chunks/util-I3gVOp6w.js +200 -0
- package/dist/bundled/index-with-compile-cache.js +19 -0
- package/dist/bundled/index.js +2 -0
- package/dist/bundled/rpc/src/index.js +1473 -0
- package/dist/node/clients/rpc-client/schema.d.ts +2 -2
- package/dist/node/community/schema.d.ts +18 -18
- package/dist/node/generated-version.d.ts +1 -1
- package/dist/node/generated-version.js +1 -1
- package/dist/node/index-with-compile-cache.d.ts +254 -0
- package/dist/node/index-with-compile-cache.js +25 -0
- package/dist/node/index-with-compile-cache.js.map +1 -0
- package/dist/node/pages/schema.d.ts +5 -5
- package/dist/node/pkc/pkc.d.ts +3 -4
- package/dist/node/pkc/pkc.js +16 -19
- package/dist/node/pkc/pkc.js.map +1 -1
- package/dist/node/publications/comment/schema.d.ts +2 -2
- package/dist/node/publications/comment/schema.js +7 -1
- package/dist/node/publications/comment/schema.js.map +1 -1
- package/dist/node/rpc/src/index.js +10 -1
- package/dist/node/rpc/src/index.js.map +1 -1
- package/dist/node/runtime/browser/compile-cache.d.ts +1 -0
- package/dist/node/runtime/browser/compile-cache.js +7 -0
- package/dist/node/runtime/browser/compile-cache.js.map +1 -0
- package/dist/node/runtime/browser/setup-kubo-http-routers.d.ts +1 -0
- package/dist/node/runtime/browser/setup-kubo-http-routers.js +4 -0
- package/dist/node/runtime/browser/setup-kubo-http-routers.js.map +1 -0
- package/dist/node/runtime/node/compile-cache.d.ts +1 -0
- package/dist/node/runtime/node/compile-cache.js +29 -0
- package/dist/node/runtime/node/compile-cache.js.map +1 -0
- package/dist/node/runtime/node/setup-kubo-http-routers.d.ts +2 -0
- package/dist/node/runtime/node/{setup-kubo-address-rewriter-and-http-router.js → setup-kubo-http-routers.js} +10 -79
- package/dist/node/runtime/node/setup-kubo-http-routers.js.map +1 -0
- package/package.json +18 -9
- package/dist/browser/runtime/browser/setup-kubo-address-rewriter-and-http-router.d.ts +0 -1
- package/dist/browser/runtime/browser/setup-kubo-address-rewriter-and-http-router.js +0 -4
- package/dist/browser/runtime/browser/setup-kubo-address-rewriter-and-http-router.js.map +0 -1
- package/dist/browser/runtime/node/address-rewriter-db.d.ts +0 -31
- package/dist/browser/runtime/node/address-rewriter-db.js +0 -156
- package/dist/browser/runtime/node/address-rewriter-db.js.map +0 -1
- package/dist/browser/runtime/node/addresses-rewriter-proxy-server.d.ts +0 -45
- package/dist/browser/runtime/node/addresses-rewriter-proxy-server.js +0 -493
- package/dist/browser/runtime/node/addresses-rewriter-proxy-server.js.map +0 -1
- package/dist/browser/runtime/node/setup-kubo-address-rewriter-and-http-router.d.ts +0 -4
- package/dist/browser/runtime/node/setup-kubo-address-rewriter-and-http-router.js.map +0 -1
- package/dist/node/runtime/browser/setup-kubo-address-rewriter-and-http-router.d.ts +0 -1
- package/dist/node/runtime/browser/setup-kubo-address-rewriter-and-http-router.js +0 -4
- package/dist/node/runtime/browser/setup-kubo-address-rewriter-and-http-router.js.map +0 -1
- package/dist/node/runtime/node/address-rewriter-db.d.ts +0 -31
- package/dist/node/runtime/node/address-rewriter-db.js +0 -156
- package/dist/node/runtime/node/address-rewriter-db.js.map +0 -1
- package/dist/node/runtime/node/addresses-rewriter-proxy-server.d.ts +0 -45
- package/dist/node/runtime/node/addresses-rewriter-proxy-server.js +0 -493
- package/dist/node/runtime/node/addresses-rewriter-proxy-server.js.map +0 -1
- package/dist/node/runtime/node/setup-kubo-address-rewriter-and-http-router.d.ts +0 -4
- package/dist/node/runtime/node/setup-kubo-address-rewriter-and-http-router.js.map +0 -1
|
@@ -0,0 +1,1473 @@
|
|
|
1
|
+
import { Er as __toESM, Jn as require_commonjs, L as hideClassPrivateProps, ir as PKCError, or as logger_default, ot as replaceXWithY, yt as findStartedCommunity } from "../../chunks/schema-Bo3XK8Oe.js";
|
|
2
|
+
import { _ as parseRpcUnsubscribeParam, c as parseRpcCancelExportParam, d as parseRpcCommunityIdentifierParam, f as parseRpcCommunityPageParam, g as parseRpcPublishChallengeAnswersParam, h as parseRpcExportCommunityParam, l as parseRpcCidParam, m as parseRpcExportCommunityModLogsParam, p as parseRpcEditCommunityParam, s as parseRpcAuthorNameParam, t as PKC, u as parseRpcCommentRepliesPageParam } from "../../chunks/node-DMNVMYGv.js";
|
|
3
|
+
import { Cn as parseVoteChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, G as buildPageRuntimeFields, Jt as parseCommentChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, K as buildPagesRuntimeFields, Qt as parseCommentModerationChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, Sn as parseSetNewSettingsPKCWsServerSchemaWithPKCErrorIfItFails, Un as stringify, Yt as parseCommentEditChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, dn as parseCreatePKCWsServerOptionsSchemaWithPKCErrorIfItFails, hn as parseDecryptedChallengeAnswerWithPKCErrorIfItFails, nn as parseCommunityEditChallengeRequestToEncryptSchemaWithPKCErrorIfItFails, rn as parseCommunityEditOptionsSchemaWithPKCErrorIfItFails, rt as require_lib, tt as pLimit, un as parseCreateNewLocalCommunityUserOptionsSchemaWithPKCErrorIfItFails } from "../../chunks/rpc-local-community-DG6G0xL6.js";
|
|
4
|
+
import { t as LocalCommunity } from "../../chunks/local-community-iVvgerOY.js";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import assert from "assert";
|
|
8
|
+
import { createReadStream, existsSync, mkdirSync, promises, statSync } from "fs";
|
|
9
|
+
import http from "http";
|
|
10
|
+
import { randomInt } from "crypto";
|
|
11
|
+
import { toString } from "uint8arrays/to-string";
|
|
12
|
+
import Database from "better-sqlite3";
|
|
13
|
+
import { Server } from "rpc-websockets";
|
|
14
|
+
//#region dist/node/rpc/src/lib/pkc-js/index.js
|
|
15
|
+
const log$1 = logger_default("pkc-react-hooks:pkc-js");
|
|
16
|
+
const PKCJs = { PKC };
|
|
17
|
+
/**
|
|
18
|
+
* replace PKCJs with a different implementation, for
|
|
19
|
+
* example to mock it during unit tests, to add mock content
|
|
20
|
+
* for developing the front-end or to add a PKCJs with
|
|
21
|
+
* desktop privileges in the Electron build.
|
|
22
|
+
*/
|
|
23
|
+
function setPKCJs(_PKC) {
|
|
24
|
+
assert(typeof _PKC === "function", `setPKCJs invalid PKC argument '${_PKC}' not a function`);
|
|
25
|
+
if (_PKC.challenges === void 0) _PKC.challenges = PKC.challenges;
|
|
26
|
+
PKCJs.PKC = _PKC;
|
|
27
|
+
log$1("setPKCJs", _PKC?.constructor?.name);
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region dist/node/rpc/src/utils.js
|
|
31
|
+
const maxRandomInt = 0xffffffffffff;
|
|
32
|
+
const generateSubscriptionId = () => randomInt(1, maxRandomInt);
|
|
33
|
+
function _encodeChallengeRequestId(id) {
|
|
34
|
+
return toString(id, "base58btc");
|
|
35
|
+
}
|
|
36
|
+
function _encodeEncrypted(encrypted) {
|
|
37
|
+
return {
|
|
38
|
+
tag: toString(encrypted.tag, "base64"),
|
|
39
|
+
iv: toString(encrypted.iv, "base64"),
|
|
40
|
+
ciphertext: toString(encrypted.ciphertext, "base64"),
|
|
41
|
+
type: encrypted.type
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function _encodeSignature(signature) {
|
|
45
|
+
return {
|
|
46
|
+
...signature,
|
|
47
|
+
publicKey: toString(signature.publicKey, "base64"),
|
|
48
|
+
signature: toString(signature.signature, "base64")
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function encodeChallengeRequest(msg) {
|
|
52
|
+
return {
|
|
53
|
+
...msg,
|
|
54
|
+
challengeRequestId: _encodeChallengeRequestId(msg.challengeRequestId),
|
|
55
|
+
encrypted: _encodeEncrypted(msg.encrypted),
|
|
56
|
+
signature: _encodeSignature(msg.signature)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function encodeChallengeMessage(msg) {
|
|
60
|
+
return {
|
|
61
|
+
...msg,
|
|
62
|
+
challengeRequestId: _encodeChallengeRequestId(msg.challengeRequestId),
|
|
63
|
+
encrypted: _encodeEncrypted(msg.encrypted),
|
|
64
|
+
signature: _encodeSignature(msg.signature)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function encodeChallengeAnswerMessage(msg) {
|
|
68
|
+
return {
|
|
69
|
+
...msg,
|
|
70
|
+
challengeRequestId: _encodeChallengeRequestId(msg.challengeRequestId),
|
|
71
|
+
encrypted: _encodeEncrypted(msg.encrypted),
|
|
72
|
+
signature: _encodeSignature(msg.signature)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function encodeChallengeVerificationMessage(msg) {
|
|
76
|
+
const encrypted = msg.encrypted ? _encodeEncrypted(msg.encrypted) : void 0;
|
|
77
|
+
return {
|
|
78
|
+
...msg,
|
|
79
|
+
challengeRequestId: _encodeChallengeRequestId(msg.challengeRequestId),
|
|
80
|
+
encrypted,
|
|
81
|
+
signature: _encodeSignature(msg.signature)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region dist/node/rpc/src/json-rpc-util.js
|
|
86
|
+
var import_commonjs = /* @__PURE__ */ __toESM(require_commonjs(), 1);
|
|
87
|
+
var import_lib = require_lib();
|
|
88
|
+
function sanitizeRpcNotificationResult(event, result) {
|
|
89
|
+
if (event !== "error" || !result || typeof result !== "object") return result;
|
|
90
|
+
const sanitizedResult = { ...result };
|
|
91
|
+
if ("stack" in sanitizedResult) delete sanitizedResult.stack;
|
|
92
|
+
if ("details" in sanitizedResult && sanitizedResult.details && typeof sanitizedResult.details === "object") {
|
|
93
|
+
const details = { ...sanitizedResult.details };
|
|
94
|
+
const nestedError = details.error;
|
|
95
|
+
if (nestedError && typeof nestedError === "object") {
|
|
96
|
+
details.error = { ...nestedError };
|
|
97
|
+
if ("stack" in details.error) delete details.error.stack;
|
|
98
|
+
}
|
|
99
|
+
sanitizedResult.details = details;
|
|
100
|
+
}
|
|
101
|
+
return sanitizedResult;
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region dist/node/rpc/src/index.js
|
|
105
|
+
const log = logger_default("pkc-js-rpc:pkc-ws-server");
|
|
106
|
+
var PKCWsServer = class extends import_lib.TypedEmitter {
|
|
107
|
+
constructor({ port, server, pkc, authKey, startStartedCommunitiesOnStartup, autoStartConcurrency, allowPrivateKeyExport, exportFileMaxAgeMs }) {
|
|
108
|
+
super();
|
|
109
|
+
this.connections = {};
|
|
110
|
+
this.subscriptionCleanups = {};
|
|
111
|
+
this.publishing = {};
|
|
112
|
+
this._setSettingsQueue = Promise.resolve();
|
|
113
|
+
this._trackedCommunityListeners = /* @__PURE__ */ new WeakMap();
|
|
114
|
+
this._getIpFromConnectionRequest = (req) => req.socket.remoteAddress;
|
|
115
|
+
this._onSettingsChange = {};
|
|
116
|
+
this._startedCommunities = {};
|
|
117
|
+
this._autoStartOnBoot = false;
|
|
118
|
+
this._exportCommunityInstances = /* @__PURE__ */ new Map();
|
|
119
|
+
this._ownsHttpServer = false;
|
|
120
|
+
const log = logger_default("pkc-js:PKCWsServer");
|
|
121
|
+
this.authKey = authKey;
|
|
122
|
+
this._autoStartOnBoot = startStartedCommunitiesOnStartup ?? true;
|
|
123
|
+
this._autoStartConcurrency = Math.max(1, autoStartConcurrency ?? 5);
|
|
124
|
+
this._allowPrivateKeyExport = allowPrivateKeyExport ?? true;
|
|
125
|
+
this._exportFileMaxAgeMs = exportFileMaxAgeMs ?? 1440 * 60 * 1e3;
|
|
126
|
+
this._initPKC(pkc);
|
|
127
|
+
if (server) this._httpServer = server;
|
|
128
|
+
else {
|
|
129
|
+
this._httpServer = http.createServer();
|
|
130
|
+
this._ownsHttpServer = true;
|
|
131
|
+
if (typeof port === "number") this._httpServer.listen(port);
|
|
132
|
+
}
|
|
133
|
+
this._httpServer.on("request", (req, res) => {
|
|
134
|
+
this._handleExportsHttpRequest(req, res).catch((e) => {
|
|
135
|
+
log.error("Unhandled error in /exports HTTP handler", e);
|
|
136
|
+
if (!res.headersSent) {
|
|
137
|
+
res.statusCode = 500;
|
|
138
|
+
res.end();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
this.rpcWebsockets = new Server({
|
|
143
|
+
server: this._httpServer,
|
|
144
|
+
verifyClient: ({ req }, callback) => {
|
|
145
|
+
const requestOriginatorIp = this._getIpFromConnectionRequest(req);
|
|
146
|
+
log.trace("Received new connection request from", requestOriginatorIp, "with url", req.url);
|
|
147
|
+
const xForwardedFor = Boolean(req.rawHeaders.find((item, i) => item.toLowerCase() === "x-forwarded-for" && i % 2 === 0));
|
|
148
|
+
const isLocalhost = req.socket.localAddress && req.socket.localAddress === requestOriginatorIp && !xForwardedFor;
|
|
149
|
+
const hasAuth = this.authKey && `/${this.authKey}` === req.url;
|
|
150
|
+
if (!isLocalhost && !hasAuth) {
|
|
151
|
+
log(`Rejecting RPC connection request from`, requestOriginatorIp, `rejected because there is no auth key, url:`, req.url);
|
|
152
|
+
callback(false, 403, "You need to set the auth key to connect remotely");
|
|
153
|
+
} else callback(true);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
this.ws = this.rpcWebsockets.wss;
|
|
157
|
+
this.rpcWebsockets.on("error", (error) => {
|
|
158
|
+
log.error("RPC server", "Received an error on rpc-websockets", error);
|
|
159
|
+
this._emitError(error);
|
|
160
|
+
});
|
|
161
|
+
this.ws.on("connection", (ws) => {
|
|
162
|
+
this.connections[ws._id] = ws;
|
|
163
|
+
this.subscriptionCleanups[ws._id] = {};
|
|
164
|
+
this._onSettingsChange[ws._id] = {};
|
|
165
|
+
log("Established connection with new RPC client", ws._id);
|
|
166
|
+
});
|
|
167
|
+
this.rpcWebsockets.on("disconnection", async (ws) => {
|
|
168
|
+
log("RPC client disconnected", ws._id, "number of rpc clients connected", this.rpcWebsockets.wss.clients.size);
|
|
169
|
+
const subscriptionCleanups = this.subscriptionCleanups[ws._id];
|
|
170
|
+
if (!subscriptionCleanups) {
|
|
171
|
+
delete this.subscriptionCleanups[ws._id];
|
|
172
|
+
delete this.connections[ws._id];
|
|
173
|
+
delete this._onSettingsChange[ws._id];
|
|
174
|
+
log("Disconnected from RPC client (no subscriptions to clean)", ws._id);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
for (const subscriptionId in subscriptionCleanups) {
|
|
178
|
+
await subscriptionCleanups[subscriptionId]();
|
|
179
|
+
delete subscriptionCleanups[subscriptionId];
|
|
180
|
+
}
|
|
181
|
+
delete this.subscriptionCleanups[ws._id];
|
|
182
|
+
delete this.connections[ws._id];
|
|
183
|
+
delete this._onSettingsChange[ws._id];
|
|
184
|
+
log("Disconnected from RPC client", ws._id);
|
|
185
|
+
});
|
|
186
|
+
this.rpcWebsocketsRegister("getComment", this.getComment.bind(this));
|
|
187
|
+
this.rpcWebsocketsRegister("getCommunityPage", this.getCommunityPage.bind(this));
|
|
188
|
+
this.rpcWebsocketsRegister("getCommentPage", this.getCommentPage.bind(this));
|
|
189
|
+
this.rpcWebsocketsRegister("createCommunity", this.createCommunity.bind(this));
|
|
190
|
+
this.rpcWebsocketsRegister("startCommunity", this.startCommunity.bind(this));
|
|
191
|
+
this.rpcWebsocketsRegister("stopCommunity", this.stopCommunity.bind(this));
|
|
192
|
+
this.rpcWebsocketsRegister("editCommunity", this.editCommunity.bind(this));
|
|
193
|
+
this.rpcWebsocketsRegister("deleteCommunity", this.deleteCommunity.bind(this));
|
|
194
|
+
this.rpcWebsocketsRegister("communitiesSubscribe", this.communitiesSubscribe.bind(this));
|
|
195
|
+
this.rpcWebsocketsRegister("settingsSubscribe", this.settingsSubscribe.bind(this));
|
|
196
|
+
this.rpcWebsocketsRegister("fetchCid", this.fetchCid.bind(this));
|
|
197
|
+
this.rpcWebsocketsRegister("resolveAuthorName", this.resolveAuthorName.bind(this));
|
|
198
|
+
this.rpcWebsocketsRegister("setSettings", this.setSettings.bind(this));
|
|
199
|
+
this.rpcWebsocketsRegister("commentUpdateSubscribe", this.commentUpdateSubscribe.bind(this));
|
|
200
|
+
this.rpcWebsocketsRegister("communityUpdateSubscribe", this.communityUpdateSubscribe.bind(this));
|
|
201
|
+
this.rpcWebsocketsRegister("publishComment", this.publishComment.bind(this));
|
|
202
|
+
this.rpcWebsocketsRegister("publishCommunityEdit", this.publishCommunityEdit.bind(this));
|
|
203
|
+
this.rpcWebsocketsRegister("publishVote", this.publishVote.bind(this));
|
|
204
|
+
this.rpcWebsocketsRegister("publishCommentEdit", this.publishCommentEdit.bind(this));
|
|
205
|
+
this.rpcWebsocketsRegister("publishCommentModeration", this.publishCommentModeration.bind(this));
|
|
206
|
+
this.rpcWebsocketsRegister("publishChallengeAnswers", this.publishChallengeAnswers.bind(this));
|
|
207
|
+
this.rpcWebsocketsRegister("unsubscribe", this.unsubscribe.bind(this));
|
|
208
|
+
this.rpcWebsocketsRegister("exportCommunity", this.exportCommunity.bind(this));
|
|
209
|
+
this.rpcWebsocketsRegister("exportsSubscribe", this.exportsSubscribe.bind(this));
|
|
210
|
+
this.rpcWebsocketsRegister("cancelExport", this.cancelExport.bind(this));
|
|
211
|
+
this.rpcWebsocketsRegister("exportCommunityModLogs", this.exportCommunityModLogs.bind(this));
|
|
212
|
+
hideClassPrivateProps(this);
|
|
213
|
+
}
|
|
214
|
+
async getStartedCommunity(address) {
|
|
215
|
+
if (!(address in this._startedCommunities)) throw Error("Can't call getStartedCommunity when the community hasn't been started");
|
|
216
|
+
while (this._startedCommunities[address] === "pending") await new Promise((r) => setTimeout(r, 20));
|
|
217
|
+
return this._startedCommunities[address];
|
|
218
|
+
}
|
|
219
|
+
_findCommunityAddress(identifier) {
|
|
220
|
+
const { name, publicKey } = identifier;
|
|
221
|
+
if (!name && !publicKey) throw new Error("At least one of name or publicKey must be provided");
|
|
222
|
+
if (name && name in this._startedCommunities) return name;
|
|
223
|
+
if (name && this.pkc.communities.includes(name)) return name;
|
|
224
|
+
if (publicKey && publicKey in this._startedCommunities) return publicKey;
|
|
225
|
+
if (publicKey && this.pkc.communities.includes(publicKey)) return publicKey;
|
|
226
|
+
}
|
|
227
|
+
_emitError(error) {
|
|
228
|
+
if (this.listeners("error").length === 0) log.error("Unhandled error. This may crash your process, you need to listen for error event on PKCRpcWsServer", error);
|
|
229
|
+
this.emit("error", error);
|
|
230
|
+
}
|
|
231
|
+
_getRpcStateDb() {
|
|
232
|
+
if (this._rpcStateDb) return this._rpcStateDb;
|
|
233
|
+
const dataPath = this.pkc.dataPath;
|
|
234
|
+
if (!dataPath) return void 0;
|
|
235
|
+
const rpcServerDir = path.join(dataPath, "rpc-server");
|
|
236
|
+
mkdirSync(rpcServerDir, { recursive: true });
|
|
237
|
+
const dbPath = path.join(rpcServerDir, "rpc-state.db");
|
|
238
|
+
this._rpcStateDb = new Database(dbPath);
|
|
239
|
+
this._rpcStateDb.pragma("journal_mode = WAL");
|
|
240
|
+
try {
|
|
241
|
+
this._rpcStateDb.exec("ALTER TABLE community_states RENAME TO community_states");
|
|
242
|
+
} catch (_) {}
|
|
243
|
+
this._rpcStateDb.exec(`
|
|
244
|
+
CREATE TABLE IF NOT EXISTS community_states (
|
|
245
|
+
address TEXT PRIMARY KEY,
|
|
246
|
+
wasStarted INTEGER NOT NULL DEFAULT 0,
|
|
247
|
+
wasExplicitlyStopped INTEGER NOT NULL DEFAULT 0
|
|
248
|
+
)
|
|
249
|
+
`);
|
|
250
|
+
return this._rpcStateDb;
|
|
251
|
+
}
|
|
252
|
+
_updateCommunityState(address, update) {
|
|
253
|
+
const db = this._getRpcStateDb();
|
|
254
|
+
if (!db) return;
|
|
255
|
+
db.prepare("INSERT OR IGNORE INTO community_states (address) VALUES (?)").run(address);
|
|
256
|
+
if (update.wasStarted !== void 0) db.prepare("UPDATE community_states SET wasStarted = ? WHERE address = ?").run(update.wasStarted ? 1 : 0, address);
|
|
257
|
+
if (update.wasExplicitlyStopped !== void 0) db.prepare("UPDATE community_states SET wasExplicitlyStopped = ? WHERE address = ?").run(update.wasExplicitlyStopped ? 1 : 0, address);
|
|
258
|
+
}
|
|
259
|
+
_removeCommunityState(address) {
|
|
260
|
+
const db = this._getRpcStateDb();
|
|
261
|
+
if (!db) return;
|
|
262
|
+
db.prepare("DELETE FROM community_states WHERE address = ?").run(address);
|
|
263
|
+
}
|
|
264
|
+
async _autoStartPreviousCommunities() {
|
|
265
|
+
if (!this._autoStartOnBoot) return;
|
|
266
|
+
const autoStartLog = logger_default("pkc-js-rpc:pkc-ws-server:auto-start");
|
|
267
|
+
autoStartLog("Checking for previously started communities to auto-start");
|
|
268
|
+
const db = this._getRpcStateDb();
|
|
269
|
+
if (!db) return;
|
|
270
|
+
const rows = db.prepare("SELECT address FROM community_states WHERE wasStarted = 1 AND wasExplicitlyStopped = 0").all();
|
|
271
|
+
const localCommunities = (await this._getPKCInstance()).communities;
|
|
272
|
+
const communitiesToStart = [];
|
|
273
|
+
for (const row of rows) {
|
|
274
|
+
if (!localCommunities.includes(row.address)) {
|
|
275
|
+
autoStartLog(`Skipping auto-start for ${row.address} - community no longer exists`);
|
|
276
|
+
this._removeCommunityState(row.address);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (row.address in this._startedCommunities) {
|
|
280
|
+
autoStartLog(`Skipping auto-start for ${row.address} - already started`);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
communitiesToStart.push(row.address);
|
|
284
|
+
}
|
|
285
|
+
if (communitiesToStart.length === 0) {
|
|
286
|
+
autoStartLog("No communities to auto-start");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
autoStartLog(`Auto-starting ${communitiesToStart.length} communities with concurrency limit of ${this._autoStartConcurrency}`);
|
|
290
|
+
const limit = pLimit(this._autoStartConcurrency);
|
|
291
|
+
await Promise.allSettled(communitiesToStart.map((address) => limit(async () => {
|
|
292
|
+
autoStartLog(`Auto-starting community: ${address}`);
|
|
293
|
+
try {
|
|
294
|
+
await this._internalStartCommunity(address);
|
|
295
|
+
autoStartLog(`Successfully auto-started: ${address}`);
|
|
296
|
+
} catch (e) {
|
|
297
|
+
autoStartLog.error(`Failed to auto-start community ${address}`, e);
|
|
298
|
+
this._emitError(e instanceof Error ? e : /* @__PURE__ */ new Error(`Failed to auto-start ${address}: ${String(e)}`));
|
|
299
|
+
}
|
|
300
|
+
})));
|
|
301
|
+
}
|
|
302
|
+
async _internalStartCommunity(address) {
|
|
303
|
+
const pkc = await this._getPKCInstance();
|
|
304
|
+
this._startedCommunities[address] = "pending";
|
|
305
|
+
try {
|
|
306
|
+
const community = await pkc.createCommunity({ address });
|
|
307
|
+
community.started = true;
|
|
308
|
+
await community.start();
|
|
309
|
+
this._startedCommunities[address] = community;
|
|
310
|
+
this._updateCommunityState(address, {
|
|
311
|
+
wasStarted: true,
|
|
312
|
+
wasExplicitlyStopped: false
|
|
313
|
+
});
|
|
314
|
+
return community;
|
|
315
|
+
} catch (e) {
|
|
316
|
+
delete this._startedCommunities[address];
|
|
317
|
+
throw e;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
rpcWebsocketsRegister(method, callback) {
|
|
321
|
+
const callbackWithErrorHandled = async (params, connectionId) => {
|
|
322
|
+
try {
|
|
323
|
+
return await callback(params, connectionId);
|
|
324
|
+
} catch (e) {
|
|
325
|
+
const typedError = e;
|
|
326
|
+
log.error(`${callback.name} error`, {
|
|
327
|
+
params,
|
|
328
|
+
error: typedError
|
|
329
|
+
});
|
|
330
|
+
if (typedError instanceof PKCError) {
|
|
331
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
332
|
+
const errorJson = JSON.parse(JSON.stringify(typedError, (_key, value) => {
|
|
333
|
+
if (typeof value === "object" && value !== null) {
|
|
334
|
+
if (seen.has(value)) return void 0;
|
|
335
|
+
seen.add(value);
|
|
336
|
+
}
|
|
337
|
+
return value;
|
|
338
|
+
}));
|
|
339
|
+
delete errorJson["stack"];
|
|
340
|
+
throw errorJson;
|
|
341
|
+
} else {
|
|
342
|
+
const errorJson = {
|
|
343
|
+
message: typedError.message,
|
|
344
|
+
name: typedError.name
|
|
345
|
+
};
|
|
346
|
+
if ("details" in typedError) errorJson.details = typedError.details;
|
|
347
|
+
if ("code" in typedError) errorJson.code = typedError.code;
|
|
348
|
+
throw errorJson;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
this.rpcWebsockets.register(method, callbackWithErrorHandled);
|
|
353
|
+
if (this.authKey) this.rpcWebsockets.register(method, callbackWithErrorHandled, `/${this.authKey}`);
|
|
354
|
+
}
|
|
355
|
+
jsonRpcSendNotification({ method, result, subscription, event, connectionId }) {
|
|
356
|
+
const message = {
|
|
357
|
+
jsonrpc: "2.0",
|
|
358
|
+
method,
|
|
359
|
+
params: {
|
|
360
|
+
result: sanitizeRpcNotificationResult(event, result),
|
|
361
|
+
subscription,
|
|
362
|
+
event
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
this.connections[connectionId]?.send?.(JSON.stringify(message));
|
|
366
|
+
}
|
|
367
|
+
_registerPublishing(subscriptionId, publication, pkc, connectionId) {
|
|
368
|
+
this.publishing[subscriptionId] = {
|
|
369
|
+
publication,
|
|
370
|
+
pkc,
|
|
371
|
+
connectionId
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
_clearPublishing(subscriptionId) {
|
|
375
|
+
const record = this.publishing[subscriptionId];
|
|
376
|
+
if (record?.timeout) clearTimeout(record.timeout);
|
|
377
|
+
delete this.publishing[subscriptionId];
|
|
378
|
+
}
|
|
379
|
+
async _forceCleanupPublication(subscriptionId, reason) {
|
|
380
|
+
const record = this.publishing[subscriptionId];
|
|
381
|
+
if (!record) return;
|
|
382
|
+
const cleanup = await this.subscriptionCleanups?.[record.connectionId]?.[subscriptionId];
|
|
383
|
+
log(`Force-cleaning publication ${subscriptionId} after ${reason}`);
|
|
384
|
+
if (cleanup) {
|
|
385
|
+
await cleanup();
|
|
386
|
+
if (this.subscriptionCleanups?.[record.connectionId]) delete this.subscriptionCleanups[record.connectionId][subscriptionId];
|
|
387
|
+
}
|
|
388
|
+
this._clearPublishing(subscriptionId);
|
|
389
|
+
await this._retirePKCIfNeeded(record.pkc);
|
|
390
|
+
}
|
|
391
|
+
async _retirePKCIfNeeded(pkc) {
|
|
392
|
+
if (Object.values(this.publishing).filter(({ pkc: p }) => p === pkc).length === 0 && !pkc.destroyed) {
|
|
393
|
+
await pkc.destroy().catch((error) => log.error("Failed destroying old pkc immediately after setSettings", { error }));
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async _getPKCInstance() {
|
|
398
|
+
await this._setSettingsQueue;
|
|
399
|
+
return this.pkc;
|
|
400
|
+
}
|
|
401
|
+
async getComment(params) {
|
|
402
|
+
const getCommentArgs = parseRpcCidParam(params[0]);
|
|
403
|
+
return (await (await this._getPKCInstance()).getComment(getCommentArgs)).raw.comment;
|
|
404
|
+
}
|
|
405
|
+
async getCommunityPage(params) {
|
|
406
|
+
const { cid: pageCid, communityPublicKey, communityName, type, pageMaxSize } = parseRpcCommunityPageParam(params[0]);
|
|
407
|
+
if (!communityPublicKey && !communityName) throw Error("At least one of communityPublicKey or communityName must be provided");
|
|
408
|
+
const communityAddress = communityName || communityPublicKey;
|
|
409
|
+
const pkc = await this._getPKCInstance();
|
|
410
|
+
const community = communityAddress in this._startedCommunities ? await this.getStartedCommunity(communityAddress) : await pkc.createCommunity({
|
|
411
|
+
name: communityName,
|
|
412
|
+
publicKey: communityPublicKey
|
|
413
|
+
});
|
|
414
|
+
const { page } = type === "posts" ? await community.posts._fetchAndVerifyPage({
|
|
415
|
+
pageCid,
|
|
416
|
+
pageMaxSize
|
|
417
|
+
}) : await community.modQueue._fetchAndVerifyPage({
|
|
418
|
+
pageCid,
|
|
419
|
+
pageMaxSize
|
|
420
|
+
});
|
|
421
|
+
return {
|
|
422
|
+
page,
|
|
423
|
+
runtimeFields: buildPageRuntimeFields(page, pkc._memCaches.nameResolvedCache)
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
async getCommentPage(params) {
|
|
427
|
+
const { cid: pageCid, commentCid, communityPublicKey, communityName, pageMaxSize } = parseRpcCommentRepliesPageParam(params[0]);
|
|
428
|
+
const pkc = await this._getPKCInstance();
|
|
429
|
+
const { page } = await (await pkc.createComment({
|
|
430
|
+
cid: commentCid,
|
|
431
|
+
communityPublicKey,
|
|
432
|
+
communityName
|
|
433
|
+
})).replies._fetchAndVerifyPage({
|
|
434
|
+
pageCid,
|
|
435
|
+
pageMaxSize
|
|
436
|
+
});
|
|
437
|
+
return {
|
|
438
|
+
page,
|
|
439
|
+
runtimeFields: buildPageRuntimeFields(page, pkc._memCaches.nameResolvedCache)
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
async createCommunity(params) {
|
|
443
|
+
const createCommunityOptions = parseCreateNewLocalCommunityUserOptionsSchemaWithPKCErrorIfItFails(params[0]);
|
|
444
|
+
const community = await (await this._getPKCInstance()).createCommunity(createCommunityOptions);
|
|
445
|
+
if (!(community instanceof LocalCommunity)) throw Error("Failed to create a local community. This is a critical error");
|
|
446
|
+
return community.toJSONInternalRpcBeforeFirstUpdate();
|
|
447
|
+
}
|
|
448
|
+
_trackCommunityListener(community, event, listener) {
|
|
449
|
+
let listenersByEvent = this._trackedCommunityListeners.get(community);
|
|
450
|
+
if (!listenersByEvent) {
|
|
451
|
+
listenersByEvent = /* @__PURE__ */ new Map();
|
|
452
|
+
this._trackedCommunityListeners.set(community, listenersByEvent);
|
|
453
|
+
}
|
|
454
|
+
let listeners = listenersByEvent.get(event);
|
|
455
|
+
if (!listeners) {
|
|
456
|
+
listeners = /* @__PURE__ */ new Set();
|
|
457
|
+
listenersByEvent.set(event, listeners);
|
|
458
|
+
}
|
|
459
|
+
listeners.add(listener);
|
|
460
|
+
}
|
|
461
|
+
_untrackCommunityListener(community, event, listener) {
|
|
462
|
+
const listenersByEvent = this._trackedCommunityListeners.get(community);
|
|
463
|
+
if (!listenersByEvent) return;
|
|
464
|
+
const listeners = listenersByEvent.get(event);
|
|
465
|
+
if (!listeners) return;
|
|
466
|
+
listeners.delete(listener);
|
|
467
|
+
if (listeners.size === 0) listenersByEvent.delete(event);
|
|
468
|
+
if (listenersByEvent.size === 0) this._trackedCommunityListeners.delete(community);
|
|
469
|
+
}
|
|
470
|
+
_setupStartedEvents(community, connectionId, subscriptionId) {
|
|
471
|
+
const sendEvent = (event, result) => this.jsonRpcSendNotification({
|
|
472
|
+
method: "startCommunity",
|
|
473
|
+
subscription: subscriptionId,
|
|
474
|
+
event,
|
|
475
|
+
result,
|
|
476
|
+
connectionId
|
|
477
|
+
});
|
|
478
|
+
const getUpdateJson = () => typeof community.updatedAt === "number" ? community.toJSONInternalRpcAfterFirstUpdate() : community.toJSONInternalRpcBeforeFirstUpdate();
|
|
479
|
+
const updateListener = () => {
|
|
480
|
+
const json = getUpdateJson();
|
|
481
|
+
const communityIpfsRecord = community.raw.communityIpfs;
|
|
482
|
+
if (communityIpfsRecord?.posts?.pages && "runtimeFields" in json && json.runtimeFields) Object.assign(json.runtimeFields, { posts: { pages: buildPagesRuntimeFields(communityIpfsRecord.posts.pages, community._pkc._memCaches.nameResolvedCache) } });
|
|
483
|
+
sendEvent("update", json);
|
|
484
|
+
};
|
|
485
|
+
community.on("update", updateListener);
|
|
486
|
+
this._trackCommunityListener(community, "update", updateListener);
|
|
487
|
+
const startedStateListener = () => sendEvent("startedstatechange", { state: community.startedState });
|
|
488
|
+
community.on("startedstatechange", startedStateListener);
|
|
489
|
+
this._trackCommunityListener(community, "startedstatechange", startedStateListener);
|
|
490
|
+
const requestListener = (request) => sendEvent("challengerequest", encodeChallengeRequest(request));
|
|
491
|
+
community.on("challengerequest", requestListener);
|
|
492
|
+
this._trackCommunityListener(community, "challengerequest", requestListener);
|
|
493
|
+
const challengeListener = (challenge) => sendEvent("challenge", encodeChallengeMessage(challenge));
|
|
494
|
+
community.on("challenge", challengeListener);
|
|
495
|
+
this._trackCommunityListener(community, "challenge", challengeListener);
|
|
496
|
+
const challengeAnswerListener = (answer) => sendEvent("challengeanswer", encodeChallengeAnswerMessage(answer));
|
|
497
|
+
community.on("challengeanswer", challengeAnswerListener);
|
|
498
|
+
this._trackCommunityListener(community, "challengeanswer", challengeAnswerListener);
|
|
499
|
+
const challengeVerificationListener = (challengeVerification) => sendEvent("challengeverification", { challengeVerification: encodeChallengeVerificationMessage(challengeVerification) });
|
|
500
|
+
community.on("challengeverification", challengeVerificationListener);
|
|
501
|
+
this._trackCommunityListener(community, "challengeverification", challengeVerificationListener);
|
|
502
|
+
const errorListener = (error) => {
|
|
503
|
+
const rpcError = error;
|
|
504
|
+
if (community.state === "started") rpcError.details = {
|
|
505
|
+
...rpcError.details,
|
|
506
|
+
newStartedState: community.startedState
|
|
507
|
+
};
|
|
508
|
+
else if (community.state === "updating") rpcError.details = {
|
|
509
|
+
...rpcError.details,
|
|
510
|
+
newUpdatingState: community.updatingState
|
|
511
|
+
};
|
|
512
|
+
log("community rpc error", rpcError);
|
|
513
|
+
sendEvent("error", rpcError);
|
|
514
|
+
};
|
|
515
|
+
community.on("error", errorListener);
|
|
516
|
+
this._trackCommunityListener(community, "error", errorListener);
|
|
517
|
+
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {
|
|
518
|
+
community.removeListener("update", updateListener);
|
|
519
|
+
this._untrackCommunityListener(community, "update", updateListener);
|
|
520
|
+
community.removeListener("startedstatechange", startedStateListener);
|
|
521
|
+
this._untrackCommunityListener(community, "startedstatechange", startedStateListener);
|
|
522
|
+
community.removeListener("challengerequest", requestListener);
|
|
523
|
+
this._untrackCommunityListener(community, "challengerequest", requestListener);
|
|
524
|
+
community.removeListener("challenge", challengeListener);
|
|
525
|
+
this._untrackCommunityListener(community, "challenge", challengeListener);
|
|
526
|
+
community.removeListener("challengeanswer", challengeAnswerListener);
|
|
527
|
+
this._untrackCommunityListener(community, "challengeanswer", challengeAnswerListener);
|
|
528
|
+
community.removeListener("challengeverification", challengeVerificationListener);
|
|
529
|
+
this._untrackCommunityListener(community, "challengeverification", challengeVerificationListener);
|
|
530
|
+
community.removeListener("error", errorListener);
|
|
531
|
+
this._untrackCommunityListener(community, "error", errorListener);
|
|
532
|
+
if (this._onSettingsChange[connectionId]) delete this._onSettingsChange[connectionId][subscriptionId];
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
async startCommunity(params, connectionId) {
|
|
536
|
+
const { name, publicKey } = parseRpcCommunityIdentifierParam(params[0]);
|
|
537
|
+
await this._getPKCInstance();
|
|
538
|
+
const address = this._findCommunityAddress({
|
|
539
|
+
name,
|
|
540
|
+
publicKey
|
|
541
|
+
});
|
|
542
|
+
if (!address) throw new PKCError("ERR_RPC_CLIENT_ATTEMPTING_TO_START_A_REMOTE_COMMUNITY", { communityAddress: name || publicKey });
|
|
543
|
+
const subscriptionId = generateSubscriptionId();
|
|
544
|
+
const startCommunityImpl = async () => {
|
|
545
|
+
const pkc = await this._getPKCInstance();
|
|
546
|
+
if (address in this._startedCommunities) {
|
|
547
|
+
const community = await this.getStartedCommunity(address);
|
|
548
|
+
this._setupStartedEvents(community, connectionId, subscriptionId);
|
|
549
|
+
} else try {
|
|
550
|
+
this._startedCommunities[address] = "pending";
|
|
551
|
+
const community = await pkc.createCommunity({ address });
|
|
552
|
+
this._setupStartedEvents(community, connectionId, subscriptionId);
|
|
553
|
+
community.started = true;
|
|
554
|
+
community.emit("update", community);
|
|
555
|
+
await community.start();
|
|
556
|
+
this._startedCommunities[address] = community;
|
|
557
|
+
this._updateCommunityState(address, {
|
|
558
|
+
wasStarted: true,
|
|
559
|
+
wasExplicitlyStopped: false
|
|
560
|
+
});
|
|
561
|
+
} catch (e) {
|
|
562
|
+
const cleanup = this.subscriptionCleanups?.[connectionId]?.[subscriptionId];
|
|
563
|
+
if (cleanup) await cleanup();
|
|
564
|
+
delete this._startedCommunities[address];
|
|
565
|
+
throw e;
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
this._onSettingsChange[connectionId][subscriptionId] = async ({ newPKC }) => {
|
|
569
|
+
const current = this._startedCommunities[address];
|
|
570
|
+
if (!current || current === "pending") return;
|
|
571
|
+
const community = await this.getStartedCommunity(address);
|
|
572
|
+
this._startedCommunities[address] = "pending";
|
|
573
|
+
try {
|
|
574
|
+
await community.stop();
|
|
575
|
+
community._pkc = newPKC;
|
|
576
|
+
await community.start();
|
|
577
|
+
this._startedCommunities[address] = community;
|
|
578
|
+
} catch (error) {
|
|
579
|
+
delete this._startedCommunities[address];
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
await startCommunityImpl();
|
|
584
|
+
return { subscriptionId };
|
|
585
|
+
}
|
|
586
|
+
async stopCommunity(params) {
|
|
587
|
+
const { name, publicKey } = parseRpcCommunityIdentifierParam(params[0]);
|
|
588
|
+
await this._getPKCInstance();
|
|
589
|
+
const address = this._findCommunityAddress({
|
|
590
|
+
name,
|
|
591
|
+
publicKey
|
|
592
|
+
});
|
|
593
|
+
if (!address) throw new PKCError("ERR_RPC_CLIENT_TRYING_TO_STOP_REMOTE_COMMUNITY", { communityAddress: name || publicKey });
|
|
594
|
+
if (!(address in this._startedCommunities)) throw new PKCError("ERR_RPC_CLIENT_TRYING_TO_STOP_COMMUNITY_THAT_IS_NOT_RUNNING", { communityAddress: address });
|
|
595
|
+
const startedCommunity = await this.getStartedCommunity(address);
|
|
596
|
+
await startedCommunity.stop();
|
|
597
|
+
await this._postStoppingOrDeleting(startedCommunity);
|
|
598
|
+
delete this._startedCommunities[address];
|
|
599
|
+
this._updateCommunityState(address, { wasExplicitlyStopped: true });
|
|
600
|
+
return { success: true };
|
|
601
|
+
}
|
|
602
|
+
async _postStoppingOrDeleting(community) {
|
|
603
|
+
community.emit("update", community);
|
|
604
|
+
community.emit("startedstatechange", community.startedState);
|
|
605
|
+
const trackedListeners = this._trackedCommunityListeners.get(community);
|
|
606
|
+
if (trackedListeners) {
|
|
607
|
+
for (const [event, listeners] of trackedListeners) for (const listener of listeners) community.removeListener(event, listener);
|
|
608
|
+
this._trackedCommunityListeners.delete(community);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async editCommunity(params) {
|
|
612
|
+
const rawParam = params[0];
|
|
613
|
+
rawParam.editOptions = replaceXWithY(rawParam.editOptions, null, void 0);
|
|
614
|
+
const { name, publicKey, editOptions } = parseRpcEditCommunityParam(rawParam);
|
|
615
|
+
const editCommunityOptions = parseCommunityEditOptionsSchemaWithPKCErrorIfItFails(editOptions);
|
|
616
|
+
const pkc = await this._getPKCInstance();
|
|
617
|
+
const address = this._findCommunityAddress({
|
|
618
|
+
name,
|
|
619
|
+
publicKey
|
|
620
|
+
});
|
|
621
|
+
if (!address) throw new PKCError("ERR_RPC_CLIENT_TRYING_TO_EDIT_REMOTE_COMMUNITY", { communityAddress: name || publicKey });
|
|
622
|
+
let community;
|
|
623
|
+
if (this._startedCommunities[address] instanceof LocalCommunity) community = this._startedCommunities[address];
|
|
624
|
+
else {
|
|
625
|
+
community = await pkc.createCommunity({ address });
|
|
626
|
+
community.once("error", (error) => {
|
|
627
|
+
log.error("RPC server Received an error on community", community.address, "edit", error);
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
await community.edit(editCommunityOptions);
|
|
631
|
+
if (editCommunityOptions.address && this._startedCommunities[address]) {
|
|
632
|
+
this._startedCommunities[editCommunityOptions.address] = this._startedCommunities[address];
|
|
633
|
+
delete this._startedCommunities[address];
|
|
634
|
+
const db = this._getRpcStateDb();
|
|
635
|
+
if (db) db.prepare("UPDATE community_states SET address = @newAddress WHERE address = @oldAddress").run({
|
|
636
|
+
newAddress: editCommunityOptions.address,
|
|
637
|
+
oldAddress: address
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
if (typeof community.updatedAt === "number") return community.toJSONInternalRpcAfterFirstUpdate();
|
|
641
|
+
else return community.toJSONInternalRpcBeforeFirstUpdate();
|
|
642
|
+
}
|
|
643
|
+
async deleteCommunity(params) {
|
|
644
|
+
const { name, publicKey } = parseRpcCommunityIdentifierParam(params[0]);
|
|
645
|
+
const pkc = await this._getPKCInstance();
|
|
646
|
+
const address = this._findCommunityAddress({
|
|
647
|
+
name,
|
|
648
|
+
publicKey
|
|
649
|
+
});
|
|
650
|
+
if (!address) throw new PKCError("ERR_RPC_CLIENT_TRYING_TO_DELETE_REMOTE_COMMUNITY", { communityAddress: name || publicKey });
|
|
651
|
+
const community = address in this._startedCommunities ? await this.getStartedCommunity(address) : await pkc.createCommunity({ address });
|
|
652
|
+
await community.delete();
|
|
653
|
+
await this._postStoppingOrDeleting(community);
|
|
654
|
+
delete this._startedCommunities[address];
|
|
655
|
+
this._removeCommunityState(address);
|
|
656
|
+
return { success: true };
|
|
657
|
+
}
|
|
658
|
+
async communitiesSubscribe(params, connectionId) {
|
|
659
|
+
const subscriptionId = generateSubscriptionId();
|
|
660
|
+
const sendEvent = (event, result) => {
|
|
661
|
+
this.jsonRpcSendNotification({
|
|
662
|
+
method: "communitiesNotification",
|
|
663
|
+
subscription: Number(subscriptionId),
|
|
664
|
+
event,
|
|
665
|
+
result,
|
|
666
|
+
connectionId
|
|
667
|
+
});
|
|
668
|
+
};
|
|
669
|
+
const pkcSubscribeEvent = (newCommunities) => sendEvent("communitieschange", { communities: newCommunities });
|
|
670
|
+
const pkc = await this._getPKCInstance();
|
|
671
|
+
pkc.on("communitieschange", pkcSubscribeEvent);
|
|
672
|
+
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {
|
|
673
|
+
pkc.removeListener("communitieschange", pkcSubscribeEvent);
|
|
674
|
+
};
|
|
675
|
+
sendEvent("communitieschange", { communities: pkc.communities });
|
|
676
|
+
return { subscriptionId };
|
|
677
|
+
}
|
|
678
|
+
async fetchCid(params) {
|
|
679
|
+
const parsedArgs = parseRpcCidParam(params[0]);
|
|
680
|
+
return (await this._getPKCInstance()).fetchCid(parsedArgs);
|
|
681
|
+
}
|
|
682
|
+
_serializeSettingsFromPKC(pkc) {
|
|
683
|
+
const pkcOptions = pkc.parsedPKCOptions;
|
|
684
|
+
const allChallengeFactories = {
|
|
685
|
+
...PKCJs.PKC.challenges || {},
|
|
686
|
+
...pkc.settings?.challenges || {}
|
|
687
|
+
};
|
|
688
|
+
return {
|
|
689
|
+
pkcOptions,
|
|
690
|
+
challenges: import_commonjs.mapValues(allChallengeFactories, (challengeFactory) => import_commonjs.omit(challengeFactory({ challengeSettings: {} }), ["getChallenge"]))
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
async settingsSubscribe(params, connectionId) {
|
|
694
|
+
const subscriptionId = generateSubscriptionId();
|
|
695
|
+
const sendEvent = (event, result) => {
|
|
696
|
+
this.jsonRpcSendNotification({
|
|
697
|
+
method: "settingsNotification",
|
|
698
|
+
subscription: Number(subscriptionId),
|
|
699
|
+
event,
|
|
700
|
+
result,
|
|
701
|
+
connectionId
|
|
702
|
+
});
|
|
703
|
+
};
|
|
704
|
+
const sendRpcSettings = async ({ newPKC }) => {
|
|
705
|
+
sendEvent("settingschange", this._serializeSettingsFromPKC(newPKC));
|
|
706
|
+
};
|
|
707
|
+
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {
|
|
708
|
+
if (this._onSettingsChange[connectionId]) delete this._onSettingsChange[connectionId][subscriptionId];
|
|
709
|
+
};
|
|
710
|
+
this._onSettingsChange[connectionId][subscriptionId] = sendRpcSettings;
|
|
711
|
+
await sendRpcSettings({ newPKC: this.pkc });
|
|
712
|
+
return { subscriptionId };
|
|
713
|
+
}
|
|
714
|
+
_initPKC(pkc) {
|
|
715
|
+
this.pkc = pkc;
|
|
716
|
+
pkc.on("error", (error) => log.error("RPC server", "Received an error on pkc instance", error));
|
|
717
|
+
}
|
|
718
|
+
async _createPKCInstanceFromSetSettings(newOptions) {
|
|
719
|
+
return PKCJs.PKC(newOptions);
|
|
720
|
+
}
|
|
721
|
+
async setSettings(params) {
|
|
722
|
+
const runSetSettings = async () => {
|
|
723
|
+
const settings = parseSetNewSettingsPKCWsServerSchemaWithPKCErrorIfItFails(params[0]);
|
|
724
|
+
const currentSettings = this._serializeSettingsFromPKC(this.pkc);
|
|
725
|
+
if (stringify(settings.pkcOptions) === stringify(currentSettings.pkcOptions)) {
|
|
726
|
+
log("RPC client called setSettings with the same settings as the current one, aborting");
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
log(`RPC client called setSettings, the clients need to call all subscription methods again`);
|
|
730
|
+
const oldPKC = this.pkc;
|
|
731
|
+
const { nameResolvers: _stripNr, ...pkcOptionsWithoutNameResolvers } = settings.pkcOptions;
|
|
732
|
+
const newPKC = await this._createPKCInstanceFromSetSettings({
|
|
733
|
+
...pkcOptionsWithoutNameResolvers,
|
|
734
|
+
nameResolvers: this.pkc.parsedPKCOptions.nameResolvers
|
|
735
|
+
});
|
|
736
|
+
this._initPKC(newPKC);
|
|
737
|
+
for (const connectionId of import_commonjs.keys.strict(this._onSettingsChange)) {
|
|
738
|
+
const connectionHandlers = this._onSettingsChange[connectionId];
|
|
739
|
+
if (!connectionHandlers) continue;
|
|
740
|
+
for (const subscriptionId of import_commonjs.keys.strict(connectionHandlers)) {
|
|
741
|
+
const handler = connectionHandlers[subscriptionId];
|
|
742
|
+
if (!handler) continue;
|
|
743
|
+
try {
|
|
744
|
+
await handler({ newPKC });
|
|
745
|
+
} catch (error) {
|
|
746
|
+
log.error(`Failed to apply settings change to subscription ${subscriptionId} of connection ${connectionId}, continuing with remaining subscriptions`, error);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
for (const [subscriptionId, pub] of Object.entries(this.publishing).filter((pub) => pub[1].pkc === oldPKC)) pub.timeout = setTimeout(async () => {
|
|
751
|
+
await this._forceCleanupPublication(Number(subscriptionId), "timeout");
|
|
752
|
+
}, 6e4);
|
|
753
|
+
setTimeout(async () => {
|
|
754
|
+
await this._retirePKCIfNeeded(oldPKC);
|
|
755
|
+
}, 6e4);
|
|
756
|
+
};
|
|
757
|
+
const setSettingsRun = this._setSettingsQueue.then(() => runSetSettings());
|
|
758
|
+
this._setSettingsQueue = setSettingsRun.catch(() => {});
|
|
759
|
+
await setSettingsRun;
|
|
760
|
+
return { success: true };
|
|
761
|
+
}
|
|
762
|
+
async commentUpdateSubscribe(params, connectionId) {
|
|
763
|
+
const logUpdate = logger_default("pkc-js-rpc:pkc-ws-server:commentUpdateSubscribe");
|
|
764
|
+
const parsedCommentUpdateArgs = parseRpcCidParam(params[0]);
|
|
765
|
+
const subscriptionId = generateSubscriptionId();
|
|
766
|
+
const sendEvent = (event, result) => this.jsonRpcSendNotification({
|
|
767
|
+
method: "commentUpdateNotification",
|
|
768
|
+
subscription: subscriptionId,
|
|
769
|
+
event,
|
|
770
|
+
result,
|
|
771
|
+
connectionId
|
|
772
|
+
});
|
|
773
|
+
let sentCommentIpfsUpdateEvent = false;
|
|
774
|
+
let lastSentNameResolved = void 0;
|
|
775
|
+
const pkc = await this._getPKCInstance();
|
|
776
|
+
const comment = await pkc.createComment(parsedCommentUpdateArgs);
|
|
777
|
+
const sendUpdate = () => {
|
|
778
|
+
if (!sentCommentIpfsUpdateEvent && comment.raw.comment) {
|
|
779
|
+
sendEvent("comment", {
|
|
780
|
+
comment: comment.raw.comment,
|
|
781
|
+
runtimeFields: { author: { nameResolved: comment.author.nameResolved } }
|
|
782
|
+
});
|
|
783
|
+
sentCommentIpfsUpdateEvent = true;
|
|
784
|
+
lastSentNameResolved = comment.author.nameResolved;
|
|
785
|
+
}
|
|
786
|
+
if (comment.raw.commentUpdate) {
|
|
787
|
+
const updateEvent = { commentUpdate: comment.raw.commentUpdate };
|
|
788
|
+
const runtimeFields = {};
|
|
789
|
+
if (comment.raw.commentUpdate.replies?.pages) runtimeFields.replies = { pages: buildPagesRuntimeFields(comment.raw.commentUpdate.replies.pages, pkc._memCaches.nameResolvedCache) };
|
|
790
|
+
if (typeof comment.author.nameResolved === "boolean") runtimeFields.author = { nameResolved: comment.author.nameResolved };
|
|
791
|
+
if (Object.keys(runtimeFields).length > 0) updateEvent.runtimeFields = runtimeFields;
|
|
792
|
+
sendEvent("update", updateEvent);
|
|
793
|
+
lastSentNameResolved = comment.author.nameResolved;
|
|
794
|
+
} else if (sentCommentIpfsUpdateEvent && typeof comment.author.nameResolved === "boolean" && comment.author.nameResolved !== lastSentNameResolved) {
|
|
795
|
+
sendEvent("runtimeupdate", { author: { nameResolved: comment.author.nameResolved } });
|
|
796
|
+
lastSentNameResolved = comment.author.nameResolved;
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
const updateListener = () => sendUpdate();
|
|
800
|
+
comment.on("update", updateListener);
|
|
801
|
+
const updatingStateListener = () => sendEvent("updatingstatechange", { state: comment.updatingState });
|
|
802
|
+
comment.on("updatingstatechange", updatingStateListener);
|
|
803
|
+
const stateListener = () => sendEvent("statechange", { state: comment.state });
|
|
804
|
+
comment.on("statechange", stateListener);
|
|
805
|
+
const errorListener = (error) => {
|
|
806
|
+
const errorWithNewUpdatingState = error;
|
|
807
|
+
if (comment.state === "publishing") errorWithNewUpdatingState.details = {
|
|
808
|
+
...errorWithNewUpdatingState.details,
|
|
809
|
+
newPublishingState: comment.publishingState
|
|
810
|
+
};
|
|
811
|
+
else if (comment.state === "updating") errorWithNewUpdatingState.details = {
|
|
812
|
+
...errorWithNewUpdatingState.details,
|
|
813
|
+
newUpdatingState: comment.updatingState
|
|
814
|
+
};
|
|
815
|
+
sendEvent("error", errorWithNewUpdatingState);
|
|
816
|
+
};
|
|
817
|
+
comment.on("error", errorListener);
|
|
818
|
+
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {
|
|
819
|
+
logUpdate("Cleaning up commentUpdate subscription", {
|
|
820
|
+
subscriptionId,
|
|
821
|
+
connectionId,
|
|
822
|
+
cid: comment.cid
|
|
823
|
+
});
|
|
824
|
+
comment.removeListener("update", updateListener);
|
|
825
|
+
comment.removeListener("updatingstatechange", updatingStateListener);
|
|
826
|
+
comment.removeListener("statechange", stateListener);
|
|
827
|
+
comment.removeListener("error", errorListener);
|
|
828
|
+
await comment.stop();
|
|
829
|
+
if (this._onSettingsChange[connectionId]) delete this._onSettingsChange[connectionId][subscriptionId];
|
|
830
|
+
};
|
|
831
|
+
this._onSettingsChange[connectionId][subscriptionId] = async ({ newPKC }) => {
|
|
832
|
+
comment._pkc = newPKC;
|
|
833
|
+
await comment.update();
|
|
834
|
+
};
|
|
835
|
+
try {
|
|
836
|
+
sendUpdate();
|
|
837
|
+
await comment.update();
|
|
838
|
+
} catch (e) {
|
|
839
|
+
logUpdate.error("Cleaning up subscription to comment", comment.cid, "because comment.update threw an error", e);
|
|
840
|
+
const cleanup = this.subscriptionCleanups?.[connectionId]?.[subscriptionId];
|
|
841
|
+
if (cleanup) await cleanup();
|
|
842
|
+
throw e;
|
|
843
|
+
}
|
|
844
|
+
return { subscriptionId };
|
|
845
|
+
}
|
|
846
|
+
async communityUpdateSubscribe(params, connectionId) {
|
|
847
|
+
const parsedCommunityUpdateArgs = parseRpcCommunityIdentifierParam(params[0]);
|
|
848
|
+
const subscriptionId = generateSubscriptionId();
|
|
849
|
+
await this._bindCommunityUpdateSubscription(parsedCommunityUpdateArgs, connectionId, subscriptionId);
|
|
850
|
+
return { subscriptionId };
|
|
851
|
+
}
|
|
852
|
+
async _bindCommunityUpdateSubscription(parsedArgs, connectionId, subscriptionId) {
|
|
853
|
+
const sendEvent = (event, result) => this.jsonRpcSendNotification({
|
|
854
|
+
method: "communityUpdateNotification",
|
|
855
|
+
subscription: subscriptionId,
|
|
856
|
+
event,
|
|
857
|
+
result,
|
|
858
|
+
connectionId
|
|
859
|
+
});
|
|
860
|
+
const pkc = await this._getPKCInstance();
|
|
861
|
+
const startedCommunity = findStartedCommunity(pkc, parsedArgs);
|
|
862
|
+
const isStartedCommunity = Boolean(startedCommunity);
|
|
863
|
+
const community = startedCommunity || await pkc.createCommunity(parsedArgs);
|
|
864
|
+
const sendCommunityJson = () => {
|
|
865
|
+
let jsonToSend;
|
|
866
|
+
if (community instanceof LocalCommunity) jsonToSend = typeof community.updatedAt === "number" ? community.toJSONInternalRpcAfterFirstUpdate() : community.toJSONInternalRpcBeforeFirstUpdate();
|
|
867
|
+
else jsonToSend = community.toJSONRpcRemote();
|
|
868
|
+
const communityIpfsRecord = community.raw.communityIpfs;
|
|
869
|
+
if (communityIpfsRecord?.posts?.pages && "runtimeFields" in jsonToSend && jsonToSend.runtimeFields) Object.assign(jsonToSend.runtimeFields, { posts: { pages: buildPagesRuntimeFields(communityIpfsRecord.posts.pages, pkc._memCaches.nameResolvedCache) } });
|
|
870
|
+
sendEvent("update", jsonToSend);
|
|
871
|
+
};
|
|
872
|
+
const updateListener = () => sendCommunityJson();
|
|
873
|
+
community.on("update", updateListener);
|
|
874
|
+
const updatingStateListener = () => sendEvent("updatingstatechange", { state: community.updatingState });
|
|
875
|
+
community.on("updatingstatechange", updatingStateListener);
|
|
876
|
+
const startedStateListener = () => sendEvent("updatingstatechange", { state: community.startedState });
|
|
877
|
+
if (isStartedCommunity) community.on("startedstatechange", startedStateListener);
|
|
878
|
+
const errorListener = (error) => {
|
|
879
|
+
const rpcError = error;
|
|
880
|
+
if (community.state === "started") rpcError.details = {
|
|
881
|
+
...rpcError.details,
|
|
882
|
+
newStartedState: community.startedState
|
|
883
|
+
};
|
|
884
|
+
else if (community.state === "updating") rpcError.details = {
|
|
885
|
+
...rpcError.details,
|
|
886
|
+
newUpdatingState: community.updatingState
|
|
887
|
+
};
|
|
888
|
+
log("community rpc error", rpcError);
|
|
889
|
+
sendEvent("error", rpcError);
|
|
890
|
+
};
|
|
891
|
+
community.on("error", errorListener);
|
|
892
|
+
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {
|
|
893
|
+
log("Cleaning up community", community.address, "client subscription");
|
|
894
|
+
community.removeListener("update", updateListener);
|
|
895
|
+
community.removeListener("updatingstatechange", updatingStateListener);
|
|
896
|
+
community.removeListener("error", errorListener);
|
|
897
|
+
community.removeListener("startedstatechange", startedStateListener);
|
|
898
|
+
if (this._onSettingsChange[connectionId]) delete this._onSettingsChange[connectionId][subscriptionId];
|
|
899
|
+
if (!isStartedCommunity && community.state !== "stopped") await community.stop();
|
|
900
|
+
};
|
|
901
|
+
this._onSettingsChange[connectionId][subscriptionId] = async ({ newPKC }) => {
|
|
902
|
+
if (!isStartedCommunity) {
|
|
903
|
+
community._pkc = newPKC;
|
|
904
|
+
await community.stop();
|
|
905
|
+
await community.update();
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
try {
|
|
909
|
+
if ("signer" in community || community.raw.communityIpfs) sendCommunityJson();
|
|
910
|
+
if (!isStartedCommunity) await community.update();
|
|
911
|
+
} catch (e) {
|
|
912
|
+
const cleanup = this.subscriptionCleanups?.[connectionId]?.[subscriptionId];
|
|
913
|
+
if (cleanup) await cleanup();
|
|
914
|
+
throw e;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
async _createCommentInstanceFromPublishCommentParams(params) {
|
|
918
|
+
const comment = await (await this._getPKCInstance()).createComment(params.comment);
|
|
919
|
+
comment.challengeRequest = import_commonjs.omit(params, ["comment"]);
|
|
920
|
+
return comment;
|
|
921
|
+
}
|
|
922
|
+
async publishComment(params, connectionId) {
|
|
923
|
+
const publishOptions = parseCommentChallengeRequestToEncryptSchemaWithPKCErrorIfItFails(params[0]);
|
|
924
|
+
const subscriptionId = generateSubscriptionId();
|
|
925
|
+
const sendEvent = (event, result) => this.jsonRpcSendNotification({
|
|
926
|
+
method: "publishCommentNotification",
|
|
927
|
+
subscription: subscriptionId,
|
|
928
|
+
event,
|
|
929
|
+
result,
|
|
930
|
+
connectionId
|
|
931
|
+
});
|
|
932
|
+
const comment = await this._createCommentInstanceFromPublishCommentParams(publishOptions);
|
|
933
|
+
this._registerPublishing(subscriptionId, comment, comment._pkc, connectionId);
|
|
934
|
+
const challengeListener = (challenge) => sendEvent("challenge", encodeChallengeMessage(challenge));
|
|
935
|
+
comment.on("challenge", challengeListener);
|
|
936
|
+
const challengeAnswerListener = (answer) => sendEvent("challengeanswer", encodeChallengeAnswerMessage(answer));
|
|
937
|
+
comment.on("challengeanswer", challengeAnswerListener);
|
|
938
|
+
const challengeRequestListener = (request) => sendEvent("challengerequest", encodeChallengeRequest(request));
|
|
939
|
+
comment.on("challengerequest", challengeRequestListener);
|
|
940
|
+
const challengeVerificationListener = (challengeVerification) => sendEvent("challengeverification", {
|
|
941
|
+
challengeVerification: encodeChallengeVerificationMessage(challengeVerification),
|
|
942
|
+
runtimeFields: { author: { nameResolved: comment.author.nameResolved } }
|
|
943
|
+
});
|
|
944
|
+
comment.on("challengeverification", challengeVerificationListener);
|
|
945
|
+
const publishingStateListener = () => {
|
|
946
|
+
sendEvent("publishingstatechange", { state: comment.publishingState });
|
|
947
|
+
};
|
|
948
|
+
comment.on("publishingstatechange", publishingStateListener);
|
|
949
|
+
const stateListener = () => sendEvent("statechange", { state: comment.state });
|
|
950
|
+
comment.on("statechange", stateListener);
|
|
951
|
+
const errorListener = (error) => {
|
|
952
|
+
const commentRpcError = error;
|
|
953
|
+
commentRpcError.details = {
|
|
954
|
+
...commentRpcError.details,
|
|
955
|
+
newPublishingState: comment.publishingState
|
|
956
|
+
};
|
|
957
|
+
sendEvent("error", commentRpcError);
|
|
958
|
+
};
|
|
959
|
+
comment.on("error", errorListener);
|
|
960
|
+
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {
|
|
961
|
+
comment.removeListener("challenge", challengeListener);
|
|
962
|
+
comment.removeListener("challengeanswer", challengeAnswerListener);
|
|
963
|
+
comment.removeListener("challengerequest", challengeRequestListener);
|
|
964
|
+
comment.removeListener("challengeverification", challengeVerificationListener);
|
|
965
|
+
comment.removeListener("publishingstatechange", publishingStateListener);
|
|
966
|
+
comment.removeListener("statechange", stateListener);
|
|
967
|
+
comment.removeListener("error", errorListener);
|
|
968
|
+
await comment.stop();
|
|
969
|
+
this._clearPublishing(subscriptionId);
|
|
970
|
+
if (this._onSettingsChange[connectionId]) delete this._onSettingsChange[connectionId][subscriptionId];
|
|
971
|
+
};
|
|
972
|
+
try {
|
|
973
|
+
await comment.publish();
|
|
974
|
+
} catch (e) {
|
|
975
|
+
const error = e;
|
|
976
|
+
error.details = {
|
|
977
|
+
...error.details,
|
|
978
|
+
publishThrowError: true
|
|
979
|
+
};
|
|
980
|
+
errorListener(error);
|
|
981
|
+
const cleanup = this.subscriptionCleanups?.[connectionId]?.[subscriptionId];
|
|
982
|
+
if (cleanup) await cleanup();
|
|
983
|
+
return { subscriptionId };
|
|
984
|
+
}
|
|
985
|
+
return { subscriptionId };
|
|
986
|
+
}
|
|
987
|
+
async _createVoteInstanceFromPublishVoteParams(params) {
|
|
988
|
+
const vote = await (await this._getPKCInstance()).createVote(params.vote);
|
|
989
|
+
vote.challengeRequest = import_commonjs.omit(params, ["vote"]);
|
|
990
|
+
return vote;
|
|
991
|
+
}
|
|
992
|
+
async publishVote(params, connectionId) {
|
|
993
|
+
const publishOptions = parseVoteChallengeRequestToEncryptSchemaWithPKCErrorIfItFails(params[0]);
|
|
994
|
+
const subscriptionId = generateSubscriptionId();
|
|
995
|
+
const sendEvent = (event, result) => this.jsonRpcSendNotification({
|
|
996
|
+
method: "publishVoteNotification",
|
|
997
|
+
subscription: subscriptionId,
|
|
998
|
+
event,
|
|
999
|
+
result,
|
|
1000
|
+
connectionId
|
|
1001
|
+
});
|
|
1002
|
+
const vote = await this._createVoteInstanceFromPublishVoteParams(publishOptions);
|
|
1003
|
+
this._registerPublishing(subscriptionId, vote, vote._pkc, connectionId);
|
|
1004
|
+
const challengeListener = (challenge) => sendEvent("challenge", encodeChallengeMessage(challenge));
|
|
1005
|
+
vote.on("challenge", challengeListener);
|
|
1006
|
+
const challengeAnswerListener = (answer) => sendEvent("challengeanswer", encodeChallengeAnswerMessage(answer));
|
|
1007
|
+
vote.on("challengeanswer", challengeAnswerListener);
|
|
1008
|
+
const challengeRequestListener = (request) => sendEvent("challengerequest", encodeChallengeRequest(request));
|
|
1009
|
+
vote.on("challengerequest", challengeRequestListener);
|
|
1010
|
+
const challengeVerificationListener = (challengeVerification) => sendEvent("challengeverification", { challengeVerification: encodeChallengeVerificationMessage(challengeVerification) });
|
|
1011
|
+
vote.on("challengeverification", challengeVerificationListener);
|
|
1012
|
+
const publishingStateListener = () => sendEvent("publishingstatechange", { state: vote.publishingState });
|
|
1013
|
+
vote.on("publishingstatechange", publishingStateListener);
|
|
1014
|
+
const errorListener = (error) => {
|
|
1015
|
+
const voteRpcError = error;
|
|
1016
|
+
voteRpcError.details = {
|
|
1017
|
+
...voteRpcError.details,
|
|
1018
|
+
newPublishingState: vote.publishingState
|
|
1019
|
+
};
|
|
1020
|
+
sendEvent("error", voteRpcError);
|
|
1021
|
+
};
|
|
1022
|
+
vote.on("error", errorListener);
|
|
1023
|
+
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {
|
|
1024
|
+
this._clearPublishing(subscriptionId);
|
|
1025
|
+
await vote.stop();
|
|
1026
|
+
vote.removeListener("challenge", challengeListener);
|
|
1027
|
+
vote.removeListener("challengeanswer", challengeAnswerListener);
|
|
1028
|
+
vote.removeListener("challengerequest", challengeRequestListener);
|
|
1029
|
+
vote.removeListener("challengeverification", challengeVerificationListener);
|
|
1030
|
+
vote.removeListener("publishingstatechange", publishingStateListener);
|
|
1031
|
+
vote.removeListener("error", errorListener);
|
|
1032
|
+
};
|
|
1033
|
+
try {
|
|
1034
|
+
await vote.publish();
|
|
1035
|
+
} catch (e) {
|
|
1036
|
+
const error = e;
|
|
1037
|
+
error.details = {
|
|
1038
|
+
...error.details,
|
|
1039
|
+
publishThrowError: true
|
|
1040
|
+
};
|
|
1041
|
+
errorListener(error);
|
|
1042
|
+
const cleanup = this.subscriptionCleanups?.[connectionId]?.[subscriptionId];
|
|
1043
|
+
if (cleanup) await cleanup();
|
|
1044
|
+
}
|
|
1045
|
+
return { subscriptionId };
|
|
1046
|
+
}
|
|
1047
|
+
async _createCommunityEditInstanceFromPublishCommunityEditParams(params) {
|
|
1048
|
+
const communityEdit = await (await this._getPKCInstance()).createCommunityEdit(params.communityEdit);
|
|
1049
|
+
communityEdit.challengeRequest = import_commonjs.omit(params, ["communityEdit"]);
|
|
1050
|
+
return communityEdit;
|
|
1051
|
+
}
|
|
1052
|
+
async publishCommunityEdit(params, connectionId) {
|
|
1053
|
+
const publishOptions = parseCommunityEditChallengeRequestToEncryptSchemaWithPKCErrorIfItFails(params[0]);
|
|
1054
|
+
const subscriptionId = generateSubscriptionId();
|
|
1055
|
+
const sendEvent = (event, result) => this.jsonRpcSendNotification({
|
|
1056
|
+
method: "publishCommunityEditNotification",
|
|
1057
|
+
subscription: subscriptionId,
|
|
1058
|
+
event,
|
|
1059
|
+
result,
|
|
1060
|
+
connectionId
|
|
1061
|
+
});
|
|
1062
|
+
const communityEdit = await this._createCommunityEditInstanceFromPublishCommunityEditParams(publishOptions);
|
|
1063
|
+
this._registerPublishing(subscriptionId, communityEdit, communityEdit._pkc, connectionId);
|
|
1064
|
+
const challengeListener = (challenge) => sendEvent("challenge", encodeChallengeMessage(challenge));
|
|
1065
|
+
communityEdit.on("challenge", challengeListener);
|
|
1066
|
+
const challengeAnswerListener = (answer) => sendEvent("challengeanswer", encodeChallengeAnswerMessage(answer));
|
|
1067
|
+
communityEdit.on("challengeanswer", challengeAnswerListener);
|
|
1068
|
+
const challengeRequestListener = (request) => sendEvent("challengerequest", encodeChallengeRequest(request));
|
|
1069
|
+
communityEdit.on("challengerequest", challengeRequestListener);
|
|
1070
|
+
const challengeVerificationListener = (challengeVerification) => sendEvent("challengeverification", { challengeVerification: encodeChallengeVerificationMessage(challengeVerification) });
|
|
1071
|
+
communityEdit.on("challengeverification", challengeVerificationListener);
|
|
1072
|
+
const publishingStateListener = () => sendEvent("publishingstatechange", { state: communityEdit.publishingState });
|
|
1073
|
+
communityEdit.on("publishingstatechange", publishingStateListener);
|
|
1074
|
+
const errorListener = (error) => {
|
|
1075
|
+
const editRpcError = error;
|
|
1076
|
+
editRpcError.details = {
|
|
1077
|
+
...editRpcError.details,
|
|
1078
|
+
newPublishingState: communityEdit.publishingState
|
|
1079
|
+
};
|
|
1080
|
+
sendEvent("error", editRpcError);
|
|
1081
|
+
};
|
|
1082
|
+
communityEdit.on("error", errorListener);
|
|
1083
|
+
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {
|
|
1084
|
+
this._clearPublishing(subscriptionId);
|
|
1085
|
+
await communityEdit.stop();
|
|
1086
|
+
communityEdit.removeListener("challenge", challengeListener);
|
|
1087
|
+
communityEdit.removeListener("challengeanswer", challengeAnswerListener);
|
|
1088
|
+
communityEdit.removeListener("challengerequest", challengeRequestListener);
|
|
1089
|
+
communityEdit.removeListener("challengeverification", challengeVerificationListener);
|
|
1090
|
+
communityEdit.removeListener("publishingstatechange", publishingStateListener);
|
|
1091
|
+
communityEdit.removeListener("error", errorListener);
|
|
1092
|
+
};
|
|
1093
|
+
try {
|
|
1094
|
+
await communityEdit.publish();
|
|
1095
|
+
} catch (e) {
|
|
1096
|
+
const error = e;
|
|
1097
|
+
error.details = {
|
|
1098
|
+
...error.details,
|
|
1099
|
+
publishThrowError: true
|
|
1100
|
+
};
|
|
1101
|
+
errorListener(error);
|
|
1102
|
+
const cleanup = this.subscriptionCleanups?.[connectionId]?.[subscriptionId];
|
|
1103
|
+
if (cleanup) await cleanup();
|
|
1104
|
+
}
|
|
1105
|
+
return { subscriptionId };
|
|
1106
|
+
}
|
|
1107
|
+
async _createCommentEditInstanceFromPublishCommentEditParams(params) {
|
|
1108
|
+
const commentEdit = await (await this._getPKCInstance()).createCommentEdit(params.commentEdit);
|
|
1109
|
+
commentEdit.challengeRequest = import_commonjs.omit(params, ["commentEdit"]);
|
|
1110
|
+
return commentEdit;
|
|
1111
|
+
}
|
|
1112
|
+
async publishCommentEdit(params, connectionId) {
|
|
1113
|
+
const publishOptions = parseCommentEditChallengeRequestToEncryptSchemaWithPKCErrorIfItFails(params[0]);
|
|
1114
|
+
const subscriptionId = generateSubscriptionId();
|
|
1115
|
+
const sendEvent = (event, result) => this.jsonRpcSendNotification({
|
|
1116
|
+
method: "publishCommentEditNotification",
|
|
1117
|
+
subscription: subscriptionId,
|
|
1118
|
+
event,
|
|
1119
|
+
result,
|
|
1120
|
+
connectionId
|
|
1121
|
+
});
|
|
1122
|
+
const commentEdit = await this._createCommentEditInstanceFromPublishCommentEditParams(publishOptions);
|
|
1123
|
+
this._registerPublishing(subscriptionId, commentEdit, commentEdit._pkc, connectionId);
|
|
1124
|
+
const challengeListener = (challenge) => sendEvent("challenge", encodeChallengeMessage(challenge));
|
|
1125
|
+
commentEdit.on("challenge", challengeListener);
|
|
1126
|
+
const challengeAnswerListener = (answer) => sendEvent("challengeanswer", encodeChallengeAnswerMessage(answer));
|
|
1127
|
+
commentEdit.on("challengeanswer", challengeAnswerListener);
|
|
1128
|
+
const challengeRequestListener = (request) => sendEvent("challengerequest", encodeChallengeRequest(request));
|
|
1129
|
+
commentEdit.on("challengerequest", challengeRequestListener);
|
|
1130
|
+
const challengeVerificationListener = (challengeVerification) => sendEvent("challengeverification", { challengeVerification: encodeChallengeVerificationMessage(challengeVerification) });
|
|
1131
|
+
commentEdit.on("challengeverification", challengeVerificationListener);
|
|
1132
|
+
const publishingStateListener = () => sendEvent("publishingstatechange", { state: commentEdit.publishingState });
|
|
1133
|
+
commentEdit.on("publishingstatechange", publishingStateListener);
|
|
1134
|
+
const errorListener = (error) => {
|
|
1135
|
+
const commentEditRpcError = error;
|
|
1136
|
+
commentEditRpcError.details = {
|
|
1137
|
+
...commentEditRpcError.details,
|
|
1138
|
+
newPublishingState: commentEdit.publishingState
|
|
1139
|
+
};
|
|
1140
|
+
sendEvent("error", commentEditRpcError);
|
|
1141
|
+
};
|
|
1142
|
+
commentEdit.on("error", errorListener);
|
|
1143
|
+
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {
|
|
1144
|
+
this._clearPublishing(subscriptionId);
|
|
1145
|
+
await commentEdit.stop();
|
|
1146
|
+
commentEdit.removeListener("challenge", challengeListener);
|
|
1147
|
+
commentEdit.removeListener("challengeanswer", challengeAnswerListener);
|
|
1148
|
+
commentEdit.removeListener("challengerequest", challengeRequestListener);
|
|
1149
|
+
commentEdit.removeListener("challengeverification", challengeVerificationListener);
|
|
1150
|
+
commentEdit.removeListener("publishingstatechange", publishingStateListener);
|
|
1151
|
+
commentEdit.removeListener("error", errorListener);
|
|
1152
|
+
};
|
|
1153
|
+
try {
|
|
1154
|
+
await commentEdit.publish();
|
|
1155
|
+
} catch (e) {
|
|
1156
|
+
const error = e;
|
|
1157
|
+
error.details = {
|
|
1158
|
+
...error.details,
|
|
1159
|
+
publishThrowError: true
|
|
1160
|
+
};
|
|
1161
|
+
errorListener(error);
|
|
1162
|
+
const cleanup = this.subscriptionCleanups?.[connectionId]?.[subscriptionId];
|
|
1163
|
+
if (cleanup) await cleanup();
|
|
1164
|
+
}
|
|
1165
|
+
return { subscriptionId };
|
|
1166
|
+
}
|
|
1167
|
+
async _createCommentModerationInstanceFromPublishCommentModerationParams(params) {
|
|
1168
|
+
const commentModeration = await (await this._getPKCInstance()).createCommentModeration(params.commentModeration);
|
|
1169
|
+
commentModeration.challengeRequest = import_commonjs.omit(params, ["commentModeration"]);
|
|
1170
|
+
return commentModeration;
|
|
1171
|
+
}
|
|
1172
|
+
async publishCommentModeration(params, connectionId) {
|
|
1173
|
+
const publishOptions = parseCommentModerationChallengeRequestToEncryptSchemaWithPKCErrorIfItFails(params[0]);
|
|
1174
|
+
const subscriptionId = generateSubscriptionId();
|
|
1175
|
+
const sendEvent = (event, result) => this.jsonRpcSendNotification({
|
|
1176
|
+
method: "publishCommentModerationNotification",
|
|
1177
|
+
subscription: subscriptionId,
|
|
1178
|
+
event,
|
|
1179
|
+
result,
|
|
1180
|
+
connectionId
|
|
1181
|
+
});
|
|
1182
|
+
const commentMod = await this._createCommentModerationInstanceFromPublishCommentModerationParams(publishOptions);
|
|
1183
|
+
this._registerPublishing(subscriptionId, commentMod, commentMod._pkc, connectionId);
|
|
1184
|
+
const challengeListener = (challenge) => sendEvent("challenge", encodeChallengeMessage(challenge));
|
|
1185
|
+
commentMod.on("challenge", challengeListener);
|
|
1186
|
+
const challengeAnswerListener = (answer) => sendEvent("challengeanswer", encodeChallengeAnswerMessage(answer));
|
|
1187
|
+
commentMod.on("challengeanswer", challengeAnswerListener);
|
|
1188
|
+
const challengeRequestListener = (request) => sendEvent("challengerequest", encodeChallengeRequest(request));
|
|
1189
|
+
commentMod.on("challengerequest", challengeRequestListener);
|
|
1190
|
+
const challengeVerificationListener = (challengeVerification) => sendEvent("challengeverification", { challengeVerification: encodeChallengeVerificationMessage(challengeVerification) });
|
|
1191
|
+
commentMod.on("challengeverification", challengeVerificationListener);
|
|
1192
|
+
const publishingStateListener = () => sendEvent("publishingstatechange", { state: commentMod.publishingState });
|
|
1193
|
+
commentMod.on("publishingstatechange", publishingStateListener);
|
|
1194
|
+
const errorListener = (error) => {
|
|
1195
|
+
const commentModRpcError = error;
|
|
1196
|
+
commentModRpcError.details = {
|
|
1197
|
+
...commentModRpcError.details,
|
|
1198
|
+
newPublishingState: commentMod.publishingState
|
|
1199
|
+
};
|
|
1200
|
+
sendEvent("error", commentModRpcError);
|
|
1201
|
+
};
|
|
1202
|
+
commentMod.on("error", errorListener);
|
|
1203
|
+
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {
|
|
1204
|
+
commentMod.removeListener("challenge", challengeListener);
|
|
1205
|
+
commentMod.removeListener("challengeanswer", challengeAnswerListener);
|
|
1206
|
+
commentMod.removeListener("challengerequest", challengeRequestListener);
|
|
1207
|
+
commentMod.removeListener("challengeverification", challengeVerificationListener);
|
|
1208
|
+
commentMod.removeListener("publishingstatechange", publishingStateListener);
|
|
1209
|
+
commentMod.removeListener("error", errorListener);
|
|
1210
|
+
await commentMod.stop();
|
|
1211
|
+
this._clearPublishing(subscriptionId);
|
|
1212
|
+
};
|
|
1213
|
+
try {
|
|
1214
|
+
await commentMod.publish();
|
|
1215
|
+
} catch (e) {
|
|
1216
|
+
const error = e;
|
|
1217
|
+
error.details = {
|
|
1218
|
+
...error.details,
|
|
1219
|
+
publishThrowError: true
|
|
1220
|
+
};
|
|
1221
|
+
errorListener(error);
|
|
1222
|
+
const cleanup = this.subscriptionCleanups?.[connectionId]?.[subscriptionId];
|
|
1223
|
+
if (cleanup) await cleanup();
|
|
1224
|
+
}
|
|
1225
|
+
return { subscriptionId };
|
|
1226
|
+
}
|
|
1227
|
+
async publishChallengeAnswers(params) {
|
|
1228
|
+
const parsed = parseRpcPublishChallengeAnswersParam(params[0]);
|
|
1229
|
+
const subscriptionId = parsed.subscriptionId;
|
|
1230
|
+
const decryptedChallengeAnswers = parseDecryptedChallengeAnswerWithPKCErrorIfItFails({ challengeAnswers: parsed.challengeAnswers });
|
|
1231
|
+
const record = this.publishing[subscriptionId];
|
|
1232
|
+
if (!record?.publication) throw Error(`no subscription with id '${subscriptionId}'`);
|
|
1233
|
+
const publication = record.publication;
|
|
1234
|
+
await this._getPKCInstance();
|
|
1235
|
+
await publication.publishChallengeAnswers({ challengeAnswers: decryptedChallengeAnswers.challengeAnswers });
|
|
1236
|
+
return { success: true };
|
|
1237
|
+
}
|
|
1238
|
+
async resolveAuthorName(params) {
|
|
1239
|
+
const parsedArgs = parseRpcAuthorNameParam(params[0]);
|
|
1240
|
+
return (await this._getPKCInstance()).resolveAuthorName(parsedArgs);
|
|
1241
|
+
}
|
|
1242
|
+
async _resolveLocalCommunityForExport(parsedArgs) {
|
|
1243
|
+
const address = this._findCommunityAddress(parsedArgs);
|
|
1244
|
+
if (!address) throw new PKCError("ERR_COMMUNITY_NOT_FOUND", {
|
|
1245
|
+
name: parsedArgs.name,
|
|
1246
|
+
publicKey: parsedArgs.publicKey
|
|
1247
|
+
});
|
|
1248
|
+
const cached = this._exportCommunityInstances.get(address);
|
|
1249
|
+
if (cached) return cached;
|
|
1250
|
+
const started = findStartedCommunity(this.pkc, parsedArgs);
|
|
1251
|
+
if (started instanceof LocalCommunity) {
|
|
1252
|
+
this._exportCommunityInstances.set(address, started);
|
|
1253
|
+
return started;
|
|
1254
|
+
}
|
|
1255
|
+
const community = await this.pkc.createCommunity({ address });
|
|
1256
|
+
if (!(community instanceof LocalCommunity)) throw new PKCError("ERR_COMMUNITY_NOT_LOCAL", { address });
|
|
1257
|
+
this._exportCommunityInstances.set(address, community);
|
|
1258
|
+
return community;
|
|
1259
|
+
}
|
|
1260
|
+
_toWireExportRecord(rec) {
|
|
1261
|
+
if (rec.progress === 1 && rec.url) return {
|
|
1262
|
+
...rec,
|
|
1263
|
+
url: `/exports/${rec.exportId}`
|
|
1264
|
+
};
|
|
1265
|
+
return rec;
|
|
1266
|
+
}
|
|
1267
|
+
async exportCommunity(params, connectionId) {
|
|
1268
|
+
const parsedArgs = parseRpcExportCommunityParam(params[0]);
|
|
1269
|
+
if (parsedArgs.includePrivateKey === true && !this._allowPrivateKeyExport) throw new PKCError("ERR_PRIVATE_KEY_EXPORT_NOT_ALLOWED", {});
|
|
1270
|
+
return (await this._resolveLocalCommunityForExport(parsedArgs)).export({ includePrivateKey: parsedArgs.includePrivateKey });
|
|
1271
|
+
}
|
|
1272
|
+
async exportCommunityModLogs(params) {
|
|
1273
|
+
const { name, publicKey, startTimestamp, endTimestamp, commentCid, limit, order } = parseRpcExportCommunityModLogsParam(params[0]);
|
|
1274
|
+
const address = this._findCommunityAddress({
|
|
1275
|
+
name,
|
|
1276
|
+
publicKey
|
|
1277
|
+
});
|
|
1278
|
+
if (!address) throw new PKCError("ERR_COMMUNITY_NOT_FOUND", {
|
|
1279
|
+
name,
|
|
1280
|
+
publicKey
|
|
1281
|
+
});
|
|
1282
|
+
let community;
|
|
1283
|
+
if (this._startedCommunities[address] instanceof LocalCommunity) community = this._startedCommunities[address];
|
|
1284
|
+
else {
|
|
1285
|
+
const created = await (await this._getPKCInstance()).createCommunity({ address });
|
|
1286
|
+
if (!(created instanceof LocalCommunity)) throw new PKCError("ERR_COMMUNITY_NOT_LOCAL", { address });
|
|
1287
|
+
community = created;
|
|
1288
|
+
}
|
|
1289
|
+
return community.exportCommunityModLogs({
|
|
1290
|
+
startTimestamp,
|
|
1291
|
+
endTimestamp,
|
|
1292
|
+
commentCid,
|
|
1293
|
+
limit,
|
|
1294
|
+
order
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
async exportsSubscribe(params, connectionId) {
|
|
1298
|
+
const parsedArgs = parseRpcCommunityIdentifierParam(params[0]);
|
|
1299
|
+
const subscriptionId = generateSubscriptionId();
|
|
1300
|
+
const community = await this._resolveLocalCommunityForExport(parsedArgs);
|
|
1301
|
+
const sendEvent = (event, result) => this.jsonRpcSendNotification({
|
|
1302
|
+
method: "exportschange",
|
|
1303
|
+
subscription: subscriptionId,
|
|
1304
|
+
event,
|
|
1305
|
+
result,
|
|
1306
|
+
connectionId
|
|
1307
|
+
});
|
|
1308
|
+
const exportschangeListener = (records) => sendEvent("exportschange", { records: records.map((r) => this._toWireExportRecord(r)) });
|
|
1309
|
+
community.on("exportschange", exportschangeListener);
|
|
1310
|
+
this.subscriptionCleanups[connectionId][subscriptionId] = async () => {
|
|
1311
|
+
community.removeListener("exportschange", exportschangeListener);
|
|
1312
|
+
};
|
|
1313
|
+
sendEvent("exportschange", { records: community.exports.map((r) => this._toWireExportRecord(r)) });
|
|
1314
|
+
return { subscriptionId };
|
|
1315
|
+
}
|
|
1316
|
+
async cancelExport(params, connectionId) {
|
|
1317
|
+
const { exportId } = parseRpcCancelExportParam(params[0]);
|
|
1318
|
+
for (const community of this._exportLoadedCommunities()) if (community._activeExports.has(exportId)) {
|
|
1319
|
+
await community._cancelExport(exportId);
|
|
1320
|
+
break;
|
|
1321
|
+
}
|
|
1322
|
+
return { success: true };
|
|
1323
|
+
}
|
|
1324
|
+
*_exportLoadedCommunities() {
|
|
1325
|
+
for (const value of Object.values(this._startedCommunities)) if (value instanceof LocalCommunity) yield value;
|
|
1326
|
+
for (const value of this._exportCommunityInstances.values()) yield value;
|
|
1327
|
+
}
|
|
1328
|
+
_findCommunityOwningExport(exportId) {
|
|
1329
|
+
for (const community of this._exportLoadedCommunities()) {
|
|
1330
|
+
const record = community._exports.find((r) => r.exportId === exportId);
|
|
1331
|
+
if (record) return {
|
|
1332
|
+
community,
|
|
1333
|
+
record
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
async _sweepOldExportFiles() {
|
|
1338
|
+
const MAX_AGE_MS = this._exportFileMaxAgeMs;
|
|
1339
|
+
if (MAX_AGE_MS === 0) return;
|
|
1340
|
+
if (!this.pkc.dataPath) return;
|
|
1341
|
+
const exportsDir = path.join(this.pkc.dataPath, "exports");
|
|
1342
|
+
if (!existsSync(exportsDir)) return;
|
|
1343
|
+
const now = Date.now();
|
|
1344
|
+
let entries = [];
|
|
1345
|
+
try {
|
|
1346
|
+
entries = await promises.readdir(exportsDir);
|
|
1347
|
+
} catch {
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
for (const entry of entries) {
|
|
1351
|
+
if (!entry.endsWith(".sqlite")) continue;
|
|
1352
|
+
const filePath = path.join(exportsDir, entry);
|
|
1353
|
+
try {
|
|
1354
|
+
if (now - (await promises.stat(filePath)).mtimeMs > MAX_AGE_MS) await promises.unlink(filePath);
|
|
1355
|
+
} catch {}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
async _handleExportsHttpRequest(req, res) {
|
|
1359
|
+
const httpLog = logger_default("pkc-js:PKCWsServer:exports-http");
|
|
1360
|
+
const match = new URL(req.url ?? "/", "http://localhost").pathname.match(/^\/exports\/([0-9a-fA-F-]{36})$/);
|
|
1361
|
+
if (!match) {
|
|
1362
|
+
if (this._ownsHttpServer) {
|
|
1363
|
+
res.statusCode = 404;
|
|
1364
|
+
res.end();
|
|
1365
|
+
}
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
if (req.method !== "GET") {
|
|
1369
|
+
res.statusCode = 405;
|
|
1370
|
+
res.setHeader("Allow", "GET");
|
|
1371
|
+
res.end();
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
const exportId = match[1];
|
|
1375
|
+
const owner = this._findCommunityOwningExport(exportId);
|
|
1376
|
+
const community = owner?.community;
|
|
1377
|
+
let candidatePath;
|
|
1378
|
+
if (owner) {
|
|
1379
|
+
const { record } = owner;
|
|
1380
|
+
if (record.progress === 1 && record.url) try {
|
|
1381
|
+
const parsed = new URL(record.url);
|
|
1382
|
+
if (parsed.protocol === "file:") candidatePath = fileURLToPath(parsed);
|
|
1383
|
+
} catch {
|
|
1384
|
+
candidatePath = void 0;
|
|
1385
|
+
}
|
|
1386
|
+
} else if (this.pkc.dataPath) candidatePath = path.join(this.pkc.dataPath, "exports", `${exportId}.sqlite`);
|
|
1387
|
+
if (!candidatePath) {
|
|
1388
|
+
httpLog("GET /exports — unknown exportId", exportId);
|
|
1389
|
+
res.statusCode = 404;
|
|
1390
|
+
res.end();
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
const filePath = candidatePath;
|
|
1394
|
+
let size;
|
|
1395
|
+
try {
|
|
1396
|
+
size = statSync(filePath).size;
|
|
1397
|
+
} catch {
|
|
1398
|
+
res.statusCode = 404;
|
|
1399
|
+
res.end();
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
res.statusCode = 200;
|
|
1403
|
+
res.setHeader("Content-Type", "application/vnd.sqlite3");
|
|
1404
|
+
res.setHeader("Content-Length", String(size));
|
|
1405
|
+
const stream = createReadStream(filePath);
|
|
1406
|
+
let streamEnded = false;
|
|
1407
|
+
let streamErrored = false;
|
|
1408
|
+
stream.on("end", () => {
|
|
1409
|
+
streamEnded = true;
|
|
1410
|
+
});
|
|
1411
|
+
stream.on("error", (err) => {
|
|
1412
|
+
streamErrored = true;
|
|
1413
|
+
httpLog.error("Error streaming export file", filePath, err);
|
|
1414
|
+
if (!res.headersSent) res.statusCode = 500;
|
|
1415
|
+
res.end();
|
|
1416
|
+
});
|
|
1417
|
+
res.on("close", () => {
|
|
1418
|
+
if (streamErrored || !streamEnded || !res.writableEnded) return;
|
|
1419
|
+
if (community) community._deleteExport(exportId).catch((e) => httpLog.error("Failed to cleanup downloaded export", exportId, e));
|
|
1420
|
+
else promises.unlink(filePath).catch((e) => httpLog.error("Failed to cleanup downloaded export file", exportId, e));
|
|
1421
|
+
});
|
|
1422
|
+
stream.pipe(res);
|
|
1423
|
+
}
|
|
1424
|
+
async unsubscribe(params, connectionId) {
|
|
1425
|
+
const { subscriptionId } = parseRpcUnsubscribeParam(params[0]);
|
|
1426
|
+
log("Received unsubscribe", {
|
|
1427
|
+
connectionId,
|
|
1428
|
+
subscriptionId
|
|
1429
|
+
});
|
|
1430
|
+
const connectionCleanups = this.subscriptionCleanups[connectionId];
|
|
1431
|
+
if (!connectionCleanups || !connectionCleanups[subscriptionId]) return { success: true };
|
|
1432
|
+
await connectionCleanups[subscriptionId]();
|
|
1433
|
+
delete connectionCleanups[subscriptionId];
|
|
1434
|
+
return { success: true };
|
|
1435
|
+
}
|
|
1436
|
+
async destroy() {
|
|
1437
|
+
for (const connectionId of import_commonjs.keys.strict(this.subscriptionCleanups)) for (const subscriptionId of import_commonjs.keys.strict(this.subscriptionCleanups[connectionId])) await this.unsubscribe([{ subscriptionId: Number(subscriptionId) }], connectionId);
|
|
1438
|
+
this.ws.close();
|
|
1439
|
+
if (this._ownsHttpServer && this._httpServer) await new Promise((r) => this._httpServer.close(() => r()));
|
|
1440
|
+
await (await this._getPKCInstance()).destroy();
|
|
1441
|
+
for (const communityAddress of import_commonjs.keys.strict(this._startedCommunities)) delete this._startedCommunities[communityAddress];
|
|
1442
|
+
this._exportCommunityInstances.clear();
|
|
1443
|
+
this._rpcStateDb?.close();
|
|
1444
|
+
this._rpcStateDb = void 0;
|
|
1445
|
+
this._onSettingsChange = {};
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
const createPKCWsServer = async (options) => {
|
|
1449
|
+
const parsedOptions = parseCreatePKCWsServerOptionsSchemaWithPKCErrorIfItFails(options);
|
|
1450
|
+
const pkcWss = new PKCWsServer({
|
|
1451
|
+
pkc: await PKCJs.PKC(parsedOptions.pkcOptions),
|
|
1452
|
+
port: parsedOptions.port,
|
|
1453
|
+
server: parsedOptions.server,
|
|
1454
|
+
authKey: parsedOptions.authKey,
|
|
1455
|
+
startStartedCommunitiesOnStartup: parsedOptions.startStartedCommunitiesOnStartup,
|
|
1456
|
+
autoStartConcurrency: parsedOptions.autoStartConcurrency,
|
|
1457
|
+
allowPrivateKeyExport: parsedOptions.allowPrivateKeyExport,
|
|
1458
|
+
exportFileMaxAgeMs: parsedOptions.exportFileMaxAgeMs
|
|
1459
|
+
});
|
|
1460
|
+
pkcWss._autoStartPreviousCommunities().catch((e) => {
|
|
1461
|
+
log.error("Failed to auto-start previous communities", e);
|
|
1462
|
+
});
|
|
1463
|
+
pkcWss._sweepOldExportFiles().catch((e) => {
|
|
1464
|
+
log.error("Failed to sweep old export files", e);
|
|
1465
|
+
});
|
|
1466
|
+
return pkcWss;
|
|
1467
|
+
};
|
|
1468
|
+
const PKCRpc = {
|
|
1469
|
+
PKCWsServer: createPKCWsServer,
|
|
1470
|
+
setPKCJs
|
|
1471
|
+
};
|
|
1472
|
+
//#endregion
|
|
1473
|
+
export { PKCRpc as default };
|