@rmdes/indiekit-endpoint-webmention-io 1.2.0 → 1.2.2

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/README.md CHANGED
@@ -7,7 +7,7 @@ Webmention moderation endpoint for [Indiekit](https://getindiekit.com). Syncs we
7
7
  - **Background Sync**: Automatically fetches webmentions from webmention.io every 15 minutes (configurable)
8
8
  - **Moderation Dashboard**: Admin UI for hiding/unhiding webmentions
9
9
  - **Domain Blocking**: Block spam domains (hides all mentions, blocks future ones)
10
- - **Privacy Removal**: GDPR-compliant permanent deletion with domain blocking
10
+ - **Privacy Removal**: permanently delete a domain's mentions from this database and block it (webmention.io keeps its own copy)
11
11
  - **Public JSON API**: Drop-in replacement for webmention.io API with server-side caching
12
12
  - **MongoDB Storage**: Persistent storage with indexes for fast queries
13
13
  - **Incremental Sync**: Only fetches new webmentions since last sync (efficient)
@@ -59,7 +59,7 @@ Visit `/webmentions` in your Indiekit admin panel to:
59
59
  - Filter by type (likes/replies/reposts/mentions)
60
60
  - Hide/unhide individual webmentions
61
61
  - Block spam domains
62
- - Remove mentions for privacy requests (GDPR)
62
+ - Remove mentions for privacy requests (local copy only)
63
63
 
64
64
  ### Manual Sync
65
65
 
@@ -147,14 +147,14 @@ Body: domain=spam.example.com
147
147
  - Adds domain to blocklist
148
148
  - Future mentions from this domain are filtered during sync
149
149
 
150
- #### Privacy removal (GDPR)
150
+ #### Privacy removal
151
151
  ```bash
152
152
  POST /webmentions/privacy-remove
153
153
  Body: domain=user-request.example.com
154
154
  ```
155
155
  - **Permanently deletes** all mentions from the domain
156
156
  - Adds domain to blocklist with reason="privacy"
157
- - Irreversible - use for GDPR/privacy requests only
157
+ - Irreversible here; the mention still exists on webmention.io, ask them separately for removal at the source
158
158
 
159
159
  #### Unblock a domain
160
160
  ```bash
@@ -38,7 +38,7 @@ export const blocklistController = {
38
38
  response.status(500).render("error", {
39
39
  title: "Error",
40
40
  message: "Failed to load blocklist",
41
- error: error.message,
41
+ error: error instanceof Error ? error.message : String(error),
42
42
  });
43
43
  }
44
44
  },
@@ -142,7 +142,7 @@ export const dashboardController = {
142
142
  response.status(500).render("error", {
143
143
  title: "Error",
144
144
  message: "Failed to load webmentions",
145
- error: error.message,
145
+ error: error instanceof Error ? error.message : String(error),
146
146
  });
147
147
  }
148
148
  },
@@ -248,7 +248,12 @@ export const dashboardController = {
248
248
  * @type {import("express").RequestHandler}
249
249
  */
250
250
  async quarantineDomainHandler(request, response) {
251
- const { application } = request.app.locals;
251
+ // This plug-in hangs its mount path and database getter on the shared
252
+ // application object at init(); the global type does not know them.
253
+ const application =
254
+ /** @type {IndiekitApplication & { webmentionEndpoint: string, getWebmentionDb: () => { collection: (name: string) => object } }} */ (
255
+ request.app.locals.application
256
+ );
252
257
 
253
258
  try {
254
259
  const { domain, reason } = request.body;
package/lib/hcard.js CHANGED
@@ -52,7 +52,7 @@ export async function discoverAuthorData(domain, cacheCollection) {
52
52
  result = await fetchHcardData(domain);
53
53
  } catch (error) {
54
54
  console.log(
55
- `[Webmentions] h-card discovery failed for ${domain}: ${error.message}`,
55
+ `[Webmentions] h-card discovery failed for ${domain}: ${error instanceof Error ? error.message : String(error)}`,
56
56
  );
57
57
  }
58
58
 
@@ -44,7 +44,7 @@ export async function blockDomain(
44
44
  return true;
45
45
  } catch (error) {
46
46
  // Duplicate key — domain already blocked
47
- if (error.code === 11_000) {
47
+ if (/** @type {{ code?: number }} */ (error).code === 11_000) {
48
48
  // Update reason and count
49
49
  // Re-applying an action to a listed domain also moves it between
50
50
  // quarantine and block, which is how "promote to block" works.
@@ -176,7 +176,7 @@ export async function getWebmentions(collection, options = {}) {
176
176
  /**
177
177
  * Get webmention counts
178
178
  * @param {object} collection - MongoDB collection
179
- * @returns {Promise<{total: number, hidden: number, visible: number}>}
179
+ * @returns {Promise<{total: number, hidden: number, quarantined: number, visible: number}>}
180
180
  */
181
181
  export async function getWebmentionCounts(collection) {
182
182
  const total = await collection.countDocuments({});
@@ -263,7 +263,10 @@ export async function hideByDomain(collection, domain, reason = "blocklist") {
263
263
  */
264
264
  export async function unhideByDomain(collection, domain) {
265
265
  const result = await collection.updateMany(
266
- { sourceDomain: normaliseDomain(domain) ?? domain, hiddenReason: "blocklist" },
266
+ {
267
+ sourceDomain: normaliseDomain(domain) ?? domain,
268
+ hiddenReason: "blocklist",
269
+ },
267
270
  { $set: { hidden: false, hiddenAt: null, hiddenReason: null } },
268
271
  );
269
272
  return result.modifiedCount;
package/lib/sync.js CHANGED
@@ -189,10 +189,11 @@ export async function runSync(dbOrIndiekit, options) {
189
189
  mentionsEnriched: enriched,
190
190
  };
191
191
  } catch (error) {
192
- syncState.lastError = error.message;
192
+ const message = error instanceof Error ? error.message : String(error);
193
+ syncState.lastError = message;
193
194
  syncState.syncing = false;
194
- console.error("[Webmentions] Sync failed:", error.message);
195
- return { error: error.message };
195
+ console.error("[Webmentions] Sync failed:", message);
196
+ return { error: message };
196
197
  }
197
198
  }
198
199
 
@@ -297,10 +298,11 @@ export async function runFullSync(dbOrIndiekit, options) {
297
298
  mentionsEnriched: enriched,
298
299
  };
299
300
  } catch (error) {
300
- syncState.lastError = error.message;
301
+ const message = error instanceof Error ? error.message : String(error);
302
+ syncState.lastError = message;
301
303
  syncState.syncing = false;
302
- console.error("[Webmentions] Full sync failed:", error.message);
303
- return { error: error.message };
304
+ console.error("[Webmentions] Full sync failed:", message);
305
+ return { error: message };
304
306
  }
305
307
  }
306
308
 
@@ -344,7 +346,7 @@ async function enrichMissingPhotos(db, wmCollection) {
344
346
  await delay(200);
345
347
  }
346
348
  } catch (error) {
347
- console.error("[Webmentions] h-card enrichment error:", error.message);
349
+ console.error("[Webmentions] h-card enrichment error:", error);
348
350
  }
349
351
 
350
352
  return totalUpdated;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-webmention-io",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Webmention moderation endpoint for Indiekit. Syncs webmentions from webmention.io into MongoDB with delete, block, and privacy removal capabilities.",
5
5
  "keywords": [
6
6
  "indiekit",