@rmdes/indiekit-endpoint-activitypub 3.13.7 → 3.13.9

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/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import express from "express";
2
2
  import { waitForReady } from "@rmdes/indiekit-startup-gate";
3
+ import { ACTIVITYPUB_BLOCKS } from "./lib/blocks.js";
3
4
 
4
5
  import { setupFederation, buildPersonActor } from "./lib/federation-setup.js";
5
6
  import { createMastodonRouter } from "./lib/mastodon/router.js";
@@ -173,6 +174,10 @@ export default class ActivityPubEndpoint {
173
174
  this._fedifyMiddleware = null;
174
175
  }
175
176
 
177
+ get blocks() {
178
+ return ACTIVITYPUB_BLOCKS;
179
+ }
180
+
176
181
  get navigationItems() {
177
182
  return [
178
183
  {
package/lib/blocks.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * ActivityPub v2 block declaration (Phase 7b — plugin block ownership).
3
+ *
4
+ * The `fediverse-follow` sidebar widget was a site-config BUILTIN_BLOCKS seed
5
+ * (requiresPlugin null, gated only by the theme's legacy widgetPluginRequirements
6
+ * render-map). Declaring it here makes site-config's scanPlugins stamp
7
+ * `sourcePlugin` → `requiresPlugin` ("ActivityPub endpoint"), so the block is
8
+ * properly plugin-gated (theme ENDPOINT_SLUGS maps it to the `activitypub`
9
+ * loadout slug). scanPlugins precedence is `built-in < plugin blocks`, so this
10
+ * entry OVERWRITES the builtin seed on sites where the plugin is loaded; the seed
11
+ * itself is removed from site-config in Phase 7d alongside the legacy-map bridge.
12
+ *
13
+ * activitypub is `default_enabled: false` — on a site without it loaded (e.g.
14
+ * chardonsbleus) the block correctly never appears in that site's catalog once
15
+ * the builtin seed is removed in 7d. Descriptor is byte-faithful to the
16
+ * BUILTIN_BLOCKS entry. Bespoke template: the theme owns
17
+ * `components/widgets/fediverse-follow.njk` (no generic `render.renderer`);
18
+ * `data.source:"config"` documents that it renders from config, not a runtime feed.
19
+ *
20
+ * @module lib/blocks
21
+ */
22
+
23
+ /** @type {Array<object>} */
24
+ export const ACTIVITYPUB_BLOCKS = [
25
+ {
26
+ id: "fediverse-follow",
27
+ version: 1,
28
+ label: "Fediverse Follow",
29
+ description: "Follow button for fediverse instances",
30
+ icon: "globe",
31
+ category: "social",
32
+ placement: { regions: ["sidebar"], surfaces: ["homepage"] },
33
+ multiple: false,
34
+ data: { source: "config" },
35
+ schema: { type: "object", additionalProperties: false, properties: {} },
36
+ },
37
+ ];
package/lib/jf2-to-as2.js CHANGED
@@ -540,10 +540,12 @@ function buildPlainTags(properties, publicationUrl, existing) {
540
540
  const tags = [...(existing || [])];
541
541
  if (properties.category) {
542
542
  for (const cat of asArray(properties.category)) {
543
+ const value = categoryString(cat);
544
+ if (!value) continue;
543
545
  tags.push({
544
546
  type: "Hashtag",
545
- name: `#${cat.split("/").at(-1).replace(/\s+/g, "")}`,
546
- href: `${publicationUrl}categories/${encodeURIComponent(cat)}`,
547
+ name: `#${value.split("/").at(-1).replace(/\s+/g, "")}`,
548
+ href: `${publicationUrl}categories/${encodeURIComponent(value)}`,
547
549
  });
548
550
  }
549
551
  }
@@ -562,11 +564,13 @@ function buildFedifyTags(properties, publicationUrl, postType) {
562
564
  }
563
565
  if (properties.category) {
564
566
  for (const cat of asArray(properties.category)) {
567
+ const value = categoryString(cat);
568
+ if (!value) continue;
565
569
  tags.push(
566
570
  new Hashtag({
567
- name: `#${cat.split("/").at(-1).replace(/\s+/g, "")}`,
571
+ name: `#${value.split("/").at(-1).replace(/\s+/g, "")}`,
568
572
  href: new URL(
569
- `${publicationUrl}categories/${encodeURIComponent(cat)}`,
573
+ `${publicationUrl}categories/${encodeURIComponent(value)}`,
570
574
  ),
571
575
  }),
572
576
  );
@@ -583,6 +587,24 @@ function asArray(value) {
583
587
  return Array.isArray(value) ? value : [value];
584
588
  }
585
589
 
590
+ /**
591
+ * Coerce a JF2 `category` entry to a hashtag-safe string, or null if it is not
592
+ * a plain tag.
593
+ *
594
+ * A Microformats/JF2 `category` array can legitimately contain non-strings —
595
+ * most commonly an IndieWeb person-tag (a nested h-card object), but malformed
596
+ * data can also yield null/number. Those are NOT hashtags. Returning null lets
597
+ * the tag builders skip them instead of crashing on `cat.split` (regression
598
+ * fixed 2026-06-06: "cat.split is not a function" broke ActivityPub syndication
599
+ * of any post carrying a person-tag).
600
+ *
601
+ * @param {unknown} cat
602
+ * @returns {string|null}
603
+ */
604
+ function categoryString(cat) {
605
+ return typeof cat === "string" && cat.trim() !== "" ? cat : null;
606
+ }
607
+
586
608
  function guessMediaType(url) {
587
609
  const ext = url.split(".").pop()?.toLowerCase();
588
610
  const types = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-activitypub",
3
- "version": "3.13.7",
3
+ "version": "3.13.9",
4
4
  "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
5
5
  "keywords": [
6
6
  "indiekit",
@@ -22,6 +22,9 @@
22
22
  "type": "module",
23
23
  "main": "index.js",
24
24
  "exports": "./index.js",
25
+ "scripts": {
26
+ "test": "node --test tests/*.test.js"
27
+ },
25
28
  "files": [
26
29
  "assets",
27
30
  "lib",