underpost 3.0.1 → 3.0.3

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.
@@ -23,6 +23,7 @@ import {
23
23
  coreUI,
24
24
  sanitizeRoute,
25
25
  getQueryParams,
26
+ setRouterReady,
26
27
  } from './Router.js';
27
28
  import { NotificationManager } from './NotificationManager.js';
28
29
  import { EventsUI } from './EventsUI.js';
@@ -322,7 +323,7 @@ const Modal = {
322
323
  'body',
323
324
  html`
324
325
  <div
325
- class="abs main-body-btn-container hide"
326
+ class="abs main-body-btn-container"
326
327
  style="top: ${options.heightTopBar + 50}px; z-index: 9; ${true ||
327
328
  (options.mode && options.mode.match('right'))
328
329
  ? 'right'
@@ -1401,6 +1402,7 @@ const Modal = {
1401
1402
  }
1402
1403
  });
1403
1404
  setTimeout(window.onresize);
1405
+ setRouterReady();
1404
1406
  });
1405
1407
  })();
1406
1408
  break;
@@ -196,7 +196,7 @@ const PublicProfile = {
196
196
  user: { _id: userId, username },
197
197
  } = options;
198
198
  const idModal = options.idModal || getId();
199
- const profileId = `public-profile-${userId}`;
199
+ const profileId = `public-profile-${username}`;
200
200
  const waveAnimationId = `${profileId}-wave`;
201
201
  const profileImageClass = `${profileId}-image`;
202
202
  const profileContainerId = `${profileId}-container`;
@@ -673,7 +673,7 @@ const PublicProfile = {
673
673
  "
674
674
  >
675
675
  <div
676
- class="${profileId}-image-container"
676
+ class="${profileId}-image-container public-profile-image-container"
677
677
  style="
678
678
  position: relative;
679
679
  width: 160px;
@@ -688,7 +688,7 @@ const PublicProfile = {
688
688
  "
689
689
  >
690
690
  <img
691
- class="${profileImageClass}"
691
+ class="${profileImageClass} public-profile-image"
692
692
  style="
693
693
  width: 100%;
694
694
  height: 100%;
@@ -39,6 +39,36 @@ const coreUI = ['modal-menu', 'main-body', 'main-body-top', 'bottom-bar', 'board
39
39
  */
40
40
  const closeModalRouteChangeEvents = {};
41
41
 
42
+ /**
43
+ * Deferred promise that resolves once the full UI (including deferred slide-menu DOM
44
+ * setup in Modal) is ready. Any code that depends on the complete DOM — route handlers,
45
+ * session callbacks, panel updates, etc. — can simply `await RouterReady` instead of
46
+ * scattering individual null-checks across every microfrontend.
47
+ *
48
+ * Resolved by calling `setRouterReady()`, which should happen exactly once at the end
49
+ * of Modal's deferred slide-menu `setTimeout` block.
50
+ * @type {Promise<void>}
51
+ * @memberof PwaRouter
52
+ */
53
+ let _routerReadyResolve;
54
+ const RouterReady = new Promise((resolve) => {
55
+ _routerReadyResolve = resolve;
56
+ });
57
+
58
+ /**
59
+ * Signals that the deferred UI setup is complete and the router (and any other
60
+ * awaiter of `RouterReady`) may safely access the full DOM.
61
+ * This must be called exactly once – typically at the end of Modal's deferred
62
+ * slide-menu `setTimeout` block.
63
+ * @memberof PwaRouter
64
+ */
65
+ const setRouterReady = () => {
66
+ if (_routerReadyResolve) {
67
+ _routerReadyResolve();
68
+ _routerReadyResolve = undefined;
69
+ }
70
+ };
71
+
42
72
  /**
43
73
  * Determines the base path for the application, often used for routing within a sub-directory.
44
74
  * It checks the current URL's pathname and `window.Routes` to return the appropriate proxy path.
@@ -209,7 +239,8 @@ const Router = function (options = { Routes: () => {}, e: new PopStateEvent() })
209
239
  * @param {object} RouterInstance - The router instance configuration, including the `Routes` function.
210
240
  * @memberof PwaRouter
211
241
  */
212
- const LoadRouter = function (RouterInstance) {
242
+ const LoadRouter = async function (RouterInstance) {
243
+ await RouterReady;
213
244
  Router(RouterInstance);
214
245
  window.onpopstate = (e) => {
215
246
  Router({ ...RouterInstance, e });
@@ -466,4 +497,6 @@ export {
466
497
  sanitizeRoute,
467
498
  queryParamsChangeListeners,
468
499
  listenQueryParamsChange,
500
+ setRouterReady,
501
+ RouterReady,
469
502
  };
@@ -135,7 +135,7 @@ class PwaWorker {
135
135
  const isInstall = await this.status();
136
136
  if (!isInstall) await this.install();
137
137
  await render();
138
- LoadRouter(this.RouterInstance);
138
+ await LoadRouter(this.RouterInstance);
139
139
  LoadingAnimation.removeSplashScreen();
140
140
  if (this.devMode()) {
141
141
  // const delayLiveReload = 1250;
package/src/index.js CHANGED
@@ -42,7 +42,7 @@ class Underpost {
42
42
  * @type {String}
43
43
  * @memberof Underpost
44
44
  */
45
- static version = 'v3.0.1';
45
+ static version = 'v3.0.3';
46
46
 
47
47
  /**
48
48
  * Required Node.js major version
@@ -99,7 +99,7 @@ class ExpressService {
99
99
 
100
100
  if (origins && isDevProxyContext())
101
101
  origins.push(devProxyHostFactory({ host, includeHttp: true, tls: isTlsDevProxy() }));
102
- app.set('trust proxy', true);
102
+ app.set('trust proxy', 1);
103
103
 
104
104
  app.use((req, res, next) => {
105
105
  res.on('finish', () => {
@@ -647,24 +647,24 @@ function applySecurity(app, opts = {}) {
647
647
  }),
648
648
  );
649
649
  logger.info('Cors origin', origin);
650
-
651
- // Rate limiting + slow down
652
- const limiter = rateLimit({
653
- windowMs: rate.windowMs,
654
- max: rate.max,
655
- standardHeaders: true,
656
- legacyHeaders: false,
657
- message: { error: 'Too many requests, please try again later.' },
658
- });
659
- app.use(limiter);
660
-
661
- const speedLimiter = slowDown({
662
- windowMs: slowdown.windowMs,
663
- delayAfter: slowdown.delayAfter,
664
- delayMs: () => slowdown.delayMs,
665
- });
666
- app.use(speedLimiter);
667
-
650
+ if (!process.env.DISABLE_API_RATE_LIMIT) {
651
+ // Rate limiting + slow down
652
+ const limiter = rateLimit({
653
+ windowMs: rate.windowMs,
654
+ max: rate.max,
655
+ standardHeaders: true,
656
+ legacyHeaders: false,
657
+ message: { error: 'Too many requests, please try again later.' },
658
+ });
659
+ app.use(limiter);
660
+
661
+ const speedLimiter = slowDown({
662
+ windowMs: slowdown.windowMs,
663
+ delayAfter: slowdown.delayAfter,
664
+ delayMs: () => slowdown.delayMs,
665
+ });
666
+ app.use(speedLimiter);
667
+ }
668
668
  // Cookie parsing
669
669
  app.use(cookieParser(process.env.JWT_SECRET));
670
670
  }
@@ -0,0 +1,433 @@
1
+ /**
2
+ * Lightweight IPFS HTTP client for communicating with a Kubo (go-ipfs) node
3
+ * and an IPFS Cluster daemon running side-by-side in the same StatefulSet.
4
+ *
5
+ * Kubo API (port 5001) – add / pin / cat content.
6
+ * Cluster API (port 9094) – replicate pins across the cluster.
7
+ *
8
+ * Uses native `FormData` + `Blob` (Node ≥ 18) for reliable multipart encoding.
9
+ *
10
+ * @module src/server/ipfs-client.js
11
+ * @namespace IpfsClient
12
+ */
13
+
14
+ import stringify from 'fast-json-stable-stringify';
15
+ import { loggerFactory } from './logger.js';
16
+
17
+ const logger = loggerFactory(import.meta);
18
+
19
+ // ─────────────────────────────────────────────────────────
20
+ // URL helpers
21
+ // ─────────────────────────────────────────────────────────
22
+
23
+ /**
24
+ * Base URL of the Kubo RPC API (port 5001).
25
+ * @returns {string}
26
+ */
27
+ const getIpfsApiUrl = () =>
28
+ process.env.IPFS_API_URL || `http://${process.env.NODE_ENV === 'development' ? 'localhost' : 'ipfs-cluster'}:5001`;
29
+
30
+ /**
31
+ * Base URL of the IPFS Cluster REST API (port 9094).
32
+ * @returns {string}
33
+ */
34
+ const getClusterApiUrl = () =>
35
+ process.env.IPFS_CLUSTER_API_URL ||
36
+ `http://${process.env.NODE_ENV === 'development' ? 'localhost' : 'ipfs-cluster'}:9094`;
37
+
38
+ /**
39
+ * Base URL of the IPFS HTTP Gateway (port 8080).
40
+ * @returns {string}
41
+ */
42
+ const getGatewayUrl = () =>
43
+ process.env.IPFS_GATEWAY_URL ||
44
+ `http://${process.env.NODE_ENV === 'development' ? 'localhost' : 'ipfs-cluster'}:8080`;
45
+
46
+ // ─────────────────────────────────────────────────────────
47
+ // Core: add content
48
+ // ─────────────────────────────────────────────────────────
49
+
50
+ /**
51
+ * @typedef {Object} IpfsAddResult
52
+ * @property {string} cid – CID (Content Identifier) returned by the node.
53
+ * @property {number} size – Cumulative DAG size reported by the node.
54
+ */
55
+
56
+ /**
57
+ * Add arbitrary bytes to the Kubo node AND pin them on the IPFS Cluster.
58
+ *
59
+ * 1. `POST /api/v0/add?pin=true` to Kubo (5001) – stores + locally pins.
60
+ * 2. `POST /pins/<CID>` to the Cluster REST API (9094) – replicates the pin
61
+ * across every peer so `GET /pins` on the cluster shows the content.
62
+ * 3. Copies into MFS so the Web UI "Files" section shows the file.
63
+ *
64
+ * @param {Buffer|string} content – raw bytes or a UTF-8 string to store.
65
+ * @param {string} [filename='data'] – logical filename for the upload.
66
+ * @param {string} [mfsPath] – optional full MFS path.
67
+ * When omitted defaults to `/pinned/<filename>`.
68
+ * @returns {Promise<IpfsAddResult|null>} `null` when the node is unreachable.
69
+ */
70
+ const addToIpfs = async (content, filename = 'data', mfsPath) => {
71
+ const kuboUrl = getIpfsApiUrl();
72
+ const clusterUrl = getClusterApiUrl();
73
+
74
+ // Build multipart body using native FormData + Blob (Node ≥ 18).
75
+ const buf = Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf-8');
76
+ const formData = new FormData();
77
+ formData.append('file', new Blob([buf]), filename);
78
+
79
+ // ── Step 1: add to Kubo ──────────────────────────────
80
+ let cid;
81
+ let size;
82
+ try {
83
+ const res = await fetch(`${kuboUrl}/api/v0/add?pin=true&cid-version=1`, {
84
+ method: 'POST',
85
+ body: formData,
86
+ });
87
+
88
+ if (!res.ok) {
89
+ const text = await res.text();
90
+ logger.error(`IPFS Kubo add failed (${res.status}): ${text}`);
91
+ return null;
92
+ }
93
+
94
+ const json = await res.json();
95
+ cid = json.Hash;
96
+ size = Number(json.Size);
97
+ logger.info(`IPFS Kubo add OK – CID: ${cid}, size: ${size}`);
98
+ } catch (err) {
99
+ logger.warn(`IPFS Kubo node unreachable at ${kuboUrl}: ${err.message}`);
100
+ return null;
101
+ }
102
+
103
+ // ── Step 2: pin to the Cluster ───────────────────────
104
+ try {
105
+ const clusterRes = await fetch(`${clusterUrl}/pins/${encodeURIComponent(cid)}`, {
106
+ method: 'POST',
107
+ });
108
+
109
+ if (!clusterRes.ok) {
110
+ const text = await clusterRes.text();
111
+ logger.warn(`IPFS Cluster pin failed (${clusterRes.status}): ${text}`);
112
+ } else {
113
+ logger.info(`IPFS Cluster pin OK – CID: ${cid}`);
114
+ }
115
+ } catch (err) {
116
+ logger.warn(`IPFS Cluster unreachable at ${clusterUrl}: ${err.message}`);
117
+ }
118
+
119
+ // ── Step 3: copy into MFS so the Web UI "Files" section shows it ─
120
+ const destPath = mfsPath || `/pinned/${filename}`;
121
+ const destDir = destPath.substring(0, destPath.lastIndexOf('/')) || '/';
122
+ try {
123
+ // Ensure parent directory exists in MFS
124
+ await fetch(`${kuboUrl}/api/v0/files/mkdir?arg=${encodeURIComponent(destDir)}&parents=true`, { method: 'POST' });
125
+
126
+ // Remove existing entry if present (cp fails on duplicates)
127
+ await fetch(`${kuboUrl}/api/v0/files/rm?arg=${encodeURIComponent(destPath)}&force=true`, {
128
+ method: 'POST',
129
+ });
130
+
131
+ // Copy the CID into MFS
132
+ const cpRes = await fetch(
133
+ `${kuboUrl}/api/v0/files/cp?arg=/ipfs/${encodeURIComponent(cid)}&arg=${encodeURIComponent(destPath)}`,
134
+ { method: 'POST' },
135
+ );
136
+
137
+ if (!cpRes.ok) {
138
+ const text = await cpRes.text();
139
+ logger.warn(`IPFS MFS cp failed (${cpRes.status}): ${text}`);
140
+ } else {
141
+ logger.info(`IPFS MFS cp OK – ${destPath} → ${cid}`);
142
+ }
143
+ } catch (err) {
144
+ logger.warn(`IPFS MFS cp unreachable: ${err.message}`);
145
+ }
146
+
147
+ return { cid, size };
148
+ };
149
+
150
+ // ─────────────────────────────────────────────────────────
151
+ // Convenience wrappers
152
+ // ─────────────────────────────────────────────────────────
153
+
154
+ /**
155
+ * Add a JSON-serialisable object to IPFS.
156
+ *
157
+ * @param {any} obj – value to serialise.
158
+ * @param {string} [filename='data.json']
159
+ * @param {string} [mfsPath] – optional full MFS destination path.
160
+ * @returns {Promise<IpfsAddResult|null>}
161
+ */
162
+ const addJsonToIpfs = async (obj, filename = 'data.json', mfsPath) => {
163
+ const payload = stringify(obj);
164
+ return addToIpfs(Buffer.from(payload, 'utf-8'), filename, mfsPath);
165
+ };
166
+
167
+ /**
168
+ * Add a binary buffer (e.g. a PNG image) to IPFS.
169
+ *
170
+ * @param {Buffer} buffer – raw image / file bytes.
171
+ * @param {string} filename – e.g. `"atlas.png"`.
172
+ * @param {string} [mfsPath] – optional full MFS destination path.
173
+ * @returns {Promise<IpfsAddResult|null>}
174
+ */
175
+ const addBufferToIpfs = async (buffer, filename, mfsPath) => {
176
+ return addToIpfs(buffer, filename, mfsPath);
177
+ };
178
+
179
+ // ─────────────────────────────────────────────────────────
180
+ // Pin management
181
+ // ─────────────────────────────────────────────────────────
182
+
183
+ /**
184
+ * Explicitly pin an existing CID on both the Kubo node and the Cluster.
185
+ *
186
+ * @param {string} cid
187
+ * @param {string} [type='recursive'] – `'recursive'` | `'direct'`
188
+ * @returns {Promise<boolean>} `true` when at least the Kubo pin succeeded.
189
+ */
190
+ const pinCid = async (cid, type = 'recursive') => {
191
+ const kuboUrl = getIpfsApiUrl();
192
+ const clusterUrl = getClusterApiUrl();
193
+ let kuboOk = false;
194
+
195
+ // Kubo pin
196
+ try {
197
+ const res = await fetch(`${kuboUrl}/api/v0/pin/add?arg=${encodeURIComponent(cid)}&type=${type}`, {
198
+ method: 'POST',
199
+ });
200
+ if (!res.ok) {
201
+ const text = await res.text();
202
+ logger.error(`IPFS Kubo pin/add failed (${res.status}): ${text}`);
203
+ } else {
204
+ kuboOk = true;
205
+ logger.info(`IPFS Kubo pin OK – CID: ${cid} (${type})`);
206
+ }
207
+ } catch (err) {
208
+ logger.warn(`IPFS Kubo pin unreachable: ${err.message}`);
209
+ }
210
+
211
+ // Cluster pin
212
+ try {
213
+ const clusterRes = await fetch(`${clusterUrl}/pins/${encodeURIComponent(cid)}`, {
214
+ method: 'POST',
215
+ });
216
+ if (!clusterRes.ok) {
217
+ const text = await clusterRes.text();
218
+ logger.warn(`IPFS Cluster pin failed (${clusterRes.status}): ${text}`);
219
+ } else {
220
+ logger.info(`IPFS Cluster pin OK – CID: ${cid}`);
221
+ }
222
+ } catch (err) {
223
+ logger.warn(`IPFS Cluster pin unreachable: ${err.message}`);
224
+ }
225
+
226
+ return kuboOk;
227
+ };
228
+
229
+ /**
230
+ * Unpin a CID from both the Kubo node and the Cluster.
231
+ *
232
+ * @param {string} cid
233
+ * @returns {Promise<boolean>}
234
+ */
235
+ const unpinCid = async (cid) => {
236
+ const kuboUrl = getIpfsApiUrl();
237
+ const clusterUrl = getClusterApiUrl();
238
+ let kuboOk = false;
239
+
240
+ // Cluster unpin
241
+ try {
242
+ const clusterRes = await fetch(`${clusterUrl}/pins/${encodeURIComponent(cid)}`, {
243
+ method: 'DELETE',
244
+ });
245
+ if (!clusterRes.ok) {
246
+ const text = await clusterRes.text();
247
+ logger.warn(`IPFS Cluster unpin failed (${clusterRes.status}): ${text}`);
248
+ } else {
249
+ logger.info(`IPFS Cluster unpin OK – CID: ${cid}`);
250
+ }
251
+ } catch (err) {
252
+ logger.warn(`IPFS Cluster unpin unreachable: ${err.message}`);
253
+ }
254
+
255
+ // Kubo unpin
256
+ try {
257
+ const res = await fetch(`${kuboUrl}/api/v0/pin/rm?arg=${encodeURIComponent(cid)}`, {
258
+ method: 'POST',
259
+ });
260
+ if (!res.ok) {
261
+ const text = await res.text();
262
+ // "not pinned or pinned indirectly" means the CID is already unpinned – treat as success
263
+ if (text.includes('not pinned')) {
264
+ kuboOk = true;
265
+ logger.info(`IPFS Kubo unpin – CID already not pinned: ${cid}`);
266
+ } else {
267
+ logger.warn(`IPFS Kubo pin/rm failed (${res.status}): ${text}`);
268
+ }
269
+ } else {
270
+ kuboOk = true;
271
+ logger.info(`IPFS Kubo unpin OK – CID: ${cid}`);
272
+ }
273
+ } catch (err) {
274
+ logger.warn(`IPFS Kubo unpin unreachable: ${err.message}`);
275
+ }
276
+
277
+ return kuboOk;
278
+ };
279
+
280
+ // ─────────────────────────────────────────────────────────
281
+ // Retrieval
282
+ // ─────────────────────────────────────────────────────────
283
+
284
+ /**
285
+ * Retrieve raw bytes for a CID from the IPFS HTTP Gateway (port 8080).
286
+ *
287
+ * @param {string} cid
288
+ * @returns {Promise<Buffer|null>}
289
+ */
290
+ const getFromIpfs = async (cid) => {
291
+ const url = getGatewayUrl();
292
+ try {
293
+ const res = await fetch(`${url}/ipfs/${encodeURIComponent(cid)}`);
294
+ if (!res.ok) {
295
+ logger.error(`IPFS gateway GET failed (${res.status}) for ${cid}`);
296
+ return null;
297
+ }
298
+ const arrayBuffer = await res.arrayBuffer();
299
+ return Buffer.from(arrayBuffer);
300
+ } catch (err) {
301
+ logger.warn(`IPFS gateway unreachable at ${url}: ${err.message}`);
302
+ return null;
303
+ }
304
+ };
305
+
306
+ // ─────────────────────────────────────────────────────────
307
+ // Diagnostics
308
+ // ─────────────────────────────────────────────────────────
309
+
310
+ /**
311
+ * List all pins tracked by the IPFS Cluster (port 9094).
312
+ * Each line in the response is a JSON object with at least a `cid` field.
313
+ *
314
+ * @returns {Promise<Array<{ cid: string, name: string, peer_map: object }>>}
315
+ */
316
+ const listClusterPins = async () => {
317
+ const clusterUrl = getClusterApiUrl();
318
+ try {
319
+ const res = await fetch(`${clusterUrl}/pins`);
320
+ if (res.status === 204) {
321
+ // 204 No Content → the cluster has no pins at all.
322
+ return [];
323
+ }
324
+ if (!res.ok) {
325
+ const text = await res.text();
326
+ logger.error(`IPFS Cluster list pins failed (${res.status}): ${text}`);
327
+ return [];
328
+ }
329
+ const text = await res.text();
330
+ // The cluster streams one JSON object per line (NDJSON).
331
+ return text
332
+ .split('\n')
333
+ .filter((line) => line.trim())
334
+ .map((line) => {
335
+ try {
336
+ return JSON.parse(line);
337
+ } catch {
338
+ return null;
339
+ }
340
+ })
341
+ .filter(Boolean);
342
+ } catch (err) {
343
+ logger.warn(`IPFS Cluster unreachable at ${clusterUrl}: ${err.message}`);
344
+ return [];
345
+ }
346
+ };
347
+
348
+ /**
349
+ * List pins tracked by the local Kubo node (port 5001).
350
+ *
351
+ * @param {string} [type='recursive'] – `'all'` | `'recursive'` | `'direct'` | `'indirect'`
352
+ * @returns {Promise<Object<string, { Type: string }>>} Map of CID → pin info.
353
+ */
354
+ const listKuboPins = async (type = 'recursive') => {
355
+ const kuboUrl = getIpfsApiUrl();
356
+ try {
357
+ const res = await fetch(`${kuboUrl}/api/v0/pin/ls?type=${type}`, {
358
+ method: 'POST',
359
+ });
360
+ if (!res.ok) {
361
+ const text = await res.text();
362
+ logger.error(`IPFS Kubo pin/ls failed (${res.status}): ${text}`);
363
+ return {};
364
+ }
365
+ const json = await res.json();
366
+ return json.Keys || {};
367
+ } catch (err) {
368
+ logger.warn(`IPFS Kubo pin/ls unreachable: ${err.message}`);
369
+ return {};
370
+ }
371
+ };
372
+
373
+ // ─────────────────────────────────────────────────────────
374
+ // MFS management
375
+ // ─────────────────────────────────────────────────────────
376
+
377
+ /**
378
+ * Remove a file or directory from the Kubo MFS (Mutable File System).
379
+ * This cleans up entries visible in the IPFS Web UI "Files" section.
380
+ *
381
+ * @param {string} mfsPath – Full MFS path to remove, e.g. `/pinned/myfile.json`
382
+ * or `/object-layer/itemId`.
383
+ * @param {boolean} [recursive=true] – When `true`, removes directories recursively.
384
+ * @returns {Promise<boolean>} `true` when the removal succeeded or the path didn't exist.
385
+ */
386
+ const removeMfsPath = async (mfsPath, recursive = true) => {
387
+ const kuboUrl = getIpfsApiUrl();
388
+ try {
389
+ // First check if the path exists via stat; if it doesn't we can return early.
390
+ const statRes = await fetch(`${kuboUrl}/api/v0/files/stat?arg=${encodeURIComponent(mfsPath)}`, { method: 'POST' });
391
+ if (!statRes.ok) {
392
+ // Path doesn't exist – nothing to remove.
393
+ logger.info(`IPFS MFS rm – path does not exist, skipping: ${mfsPath}`);
394
+ return true;
395
+ }
396
+
397
+ const rmRes = await fetch(
398
+ `${kuboUrl}/api/v0/files/rm?arg=${encodeURIComponent(mfsPath)}&force=true${recursive ? '&recursive=true' : ''}`,
399
+ { method: 'POST' },
400
+ );
401
+ if (!rmRes.ok) {
402
+ const text = await rmRes.text();
403
+ logger.warn(`IPFS MFS rm failed (${rmRes.status}): ${text}`);
404
+ return false;
405
+ }
406
+ logger.info(`IPFS MFS rm OK – ${mfsPath}`);
407
+ return true;
408
+ } catch (err) {
409
+ logger.warn(`IPFS MFS rm unreachable: ${err.message}`);
410
+ return false;
411
+ }
412
+ };
413
+
414
+ // ─────────────────────────────────────────────────────────
415
+ // Export
416
+ // ─────────────────────────────────────────────────────────
417
+
418
+ const IpfsClient = {
419
+ getIpfsApiUrl,
420
+ getClusterApiUrl,
421
+ getGatewayUrl,
422
+ addToIpfs,
423
+ addJsonToIpfs,
424
+ addBufferToIpfs,
425
+ pinCid,
426
+ unpinCid,
427
+ getFromIpfs,
428
+ listClusterPins,
429
+ listKuboPins,
430
+ removeMfsPath,
431
+ };
432
+
433
+ export { IpfsClient };
package/bin/ssl.js DELETED
@@ -1,63 +0,0 @@
1
- import fs from 'fs';
2
- import read from 'read';
3
- import dotenv from 'dotenv';
4
- import { getRootDirectory, pbcopy, shellExec } from '../src/server/process.js';
5
- import { loggerFactory } from '../src/server/logger.js';
6
- import { Cmd, loadConf } from '../src/server/conf.js';
7
- import { buildSSL } from '../src/server/ssl.js';
8
-
9
- dotenv.config();
10
-
11
- const logger = loggerFactory(import.meta);
12
-
13
- await logger.setUpInfo();
14
-
15
- const [exe, dir, deployId, hosts] = process.argv;
16
-
17
- try {
18
- let cmd;
19
- await loadConf(deployId);
20
- shellExec(Cmd.conf(deployId));
21
- const confServer = JSON.parse(fs.readFileSync(`./conf/conf.server.json`, 'utf8'));
22
- for (const host of hosts.split(',')) {
23
- if (host in confServer) {
24
- const directory = confServer[host]['/']?.['directory'] ? confServer[host]['/']['directory'] : undefined;
25
- cmd = `sudo certbot certonly --webroot --webroot-path ${
26
- directory ? directory : `${getRootDirectory()}/public/${host}`
27
- } --cert-name ${host} -d ${host}`;
28
- // directory ? directory : `${getRootDirectory()}/public/${host}`
29
- // directory ? directory : `${getRootDirectory()}/public/www.${host.split('.').slice(-2).join('.')}`
30
-
31
- // You can get multi domain cert by specifying (extra) -d
32
- // For example
33
- // certbot -d example.com -d example.net -d www.example.org
34
-
35
- // delete all file (no increment live folder)
36
- // certbot delete --cert-name <domain>
37
-
38
- logger.info(`Run the following command`, cmd);
39
- try {
40
- await pbcopy(cmd);
41
- await read({ prompt: 'Command copy to clipboard, press enter to continue.\n' });
42
- } catch (error) {
43
- logger.error(error);
44
- }
45
- // Certificate
46
- if (process.argv.includes('build')) await buildSSL(host);
47
- logger.info('Certificate saved', host);
48
- } else throw new Error(`host not found: ${host}`);
49
- }
50
- // check for renewal conf:
51
- // /etc/letsencrypt/renewal
52
- // /etc/letsencrypt/live
53
- cmd = `sudo certbot renew --dry-run`;
54
- try {
55
- await pbcopy(cmd);
56
- } catch (error) {
57
- logger.error(error);
58
- }
59
- logger.info(`run the following command for renewal. Command copy to clipboard`, cmd);
60
- logger.info(`success install SLL`, hosts);
61
- } catch (error) {
62
- logger.error(error, error.stack);
63
- }