@riebeckite/plugin-webmention 0.0.6
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/LICENSE +201 -0
- package/README.md +119 -0
- package/README_ja.md +116 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +1159 -0
- package/dist/src/diagnostics.d.ts +6 -0
- package/dist/src/endpoints.d.ts +9 -0
- package/dist/src/feed.d.ts +28 -0
- package/dist/src/http.d.ts +5 -0
- package/dist/src/memory_provider.d.ts +14 -0
- package/dist/src/mention.d.ts +32 -0
- package/dist/src/options.d.ts +12 -0
- package/dist/src/parse.d.ts +25 -0
- package/dist/src/provider.d.ts +30 -0
- package/dist/src/query.d.ts +24 -0
- package/dist/src/receive.d.ts +20 -0
- package/dist/src/render.d.ts +12 -0
- package/dist/src/server.d.ts +24 -0
- package/dist/src/targets.d.ts +10 -0
- package/dist/src/types.d.ts +50 -0
- package/dist/src/url.d.ts +14 -0
- package/dist/src/verify.d.ts +74 -0
- package/package.json +60 -0
- package/style.css +66 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Diagnostic } from "@riebeckite/core";
|
|
2
|
+
import { type WebmentionProvider } from "./provider.js";
|
|
3
|
+
import type { ResolvedWebmentionOptions } from "./types.js";
|
|
4
|
+
export declare const WEBMENTION_PLUGIN_NAME = "webmention";
|
|
5
|
+
/** Structured health checks for the configured provider and rendering mode. */
|
|
6
|
+
export declare function buildWebmentionDiagnostics(provider: WebmentionProvider, options: ResolvedWebmentionOptions): Diagnostic[];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PluginEndpoint } from "@riebeckite/core";
|
|
2
|
+
import type { ResolvedWebmentionOptions } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Receive (`POST`) and feed (`GET`) endpoints for the configured path.
|
|
5
|
+
*
|
|
6
|
+
* The plugin only declares the HTTP contract; the HonoX integration mounts it
|
|
7
|
+
* on the host router. Route-framework specifics never enter this package.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createWebmentionEndpoints(options: ResolvedWebmentionOptions): PluginEndpoint[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ContentManifest, PluginEndpointContext, PluginEndpointResponse, ResolvedRiebeckiteConfig } from "@riebeckite/core";
|
|
2
|
+
import type { WebmentionMention } from "./mention.js";
|
|
3
|
+
import { type WebmentionProvider } from "./provider.js";
|
|
4
|
+
import type { ResolvedWebmentionOptions } from "./types.js";
|
|
5
|
+
/** Public article summary attached to a fed mention. */
|
|
6
|
+
export type WebmentionFeedEntry = Readonly<{
|
|
7
|
+
slug: string;
|
|
8
|
+
permalink: string;
|
|
9
|
+
title: string;
|
|
10
|
+
}>;
|
|
11
|
+
export type WebmentionFeedMention = WebmentionMention & Readonly<{
|
|
12
|
+
entry: WebmentionFeedEntry | null;
|
|
13
|
+
}>;
|
|
14
|
+
export type WebmentionFeed = Readonly<{
|
|
15
|
+
version: "1";
|
|
16
|
+
generatedAt: string;
|
|
17
|
+
count: number;
|
|
18
|
+
mentions: readonly WebmentionFeedMention[];
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Builds the `GET` feed handler. It returns verified mentions as JSON so an
|
|
22
|
+
* application route, island, or external consumer can read them. The feed is
|
|
23
|
+
* optional when the provider cannot query.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createWebmentionFeedHandler(options: ResolvedWebmentionOptions, provider: WebmentionProvider): (context: PluginEndpointContext) => Promise<PluginEndpointResponse>;
|
|
26
|
+
/** Builds the feed payload shared by the endpoint and application consumers. */
|
|
27
|
+
export declare function buildFeed(manifest: ContentManifest, config: ResolvedRiebeckiteConfig | undefined, mentions: readonly WebmentionMention[]): WebmentionFeed;
|
|
28
|
+
export declare function summarizeEntry(manifest: ContentManifest, config: ResolvedRiebeckiteConfig | undefined, target: string): WebmentionFeedEntry | null;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PluginEndpointResponse } from "@riebeckite/core";
|
|
2
|
+
/** JSON endpoint response with an explicit content type. */
|
|
3
|
+
export declare function jsonResponse(status: number, value: unknown): PluginEndpointResponse;
|
|
4
|
+
/** A structured, non-throwing error body shared by receive and feed. */
|
|
5
|
+
export declare function errorResponse(status: number, error: string): PluginEndpointResponse;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type WebmentionMention } from "./mention.js";
|
|
2
|
+
import { type WebmentionProvider } from "./provider.js";
|
|
3
|
+
import type { WebmentionQuery, WebmentionQueryResult } from "./query.js";
|
|
4
|
+
/**
|
|
5
|
+
* In-memory provider for tests, local previews, and provider contract
|
|
6
|
+
* examples. State lives for the lifetime of the process/isolate only.
|
|
7
|
+
*/
|
|
8
|
+
export declare class MemoryWebmentionProvider implements WebmentionProvider {
|
|
9
|
+
#private;
|
|
10
|
+
readonly capabilities: Set<"store" | "query">;
|
|
11
|
+
constructor(initialMentions?: readonly WebmentionMention[]);
|
|
12
|
+
store(mention: WebmentionMention): Promise<void>;
|
|
13
|
+
query(query: WebmentionQuery): Promise<WebmentionQueryResult>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage- and runtime-independent Webmention records.
|
|
3
|
+
*
|
|
4
|
+
* A mention is only created after the source document has been fetched and
|
|
5
|
+
* verified to link to the target, so every stored record is a verified
|
|
6
|
+
* mention by construction.
|
|
7
|
+
*/
|
|
8
|
+
export declare const WEBMENTION_TYPES: readonly ["mention", "reply", "like", "repost", "bookmark"];
|
|
9
|
+
export type WebmentionType = (typeof WEBMENTION_TYPES)[number];
|
|
10
|
+
export type WebmentionAuthor = Readonly<{
|
|
11
|
+
name?: string;
|
|
12
|
+
url?: string;
|
|
13
|
+
photo?: string;
|
|
14
|
+
}>;
|
|
15
|
+
export type WebmentionMention = Readonly<{
|
|
16
|
+
/** Absolute URL of the verified source document. */
|
|
17
|
+
source: string;
|
|
18
|
+
/** Absolute target URL on this site. */
|
|
19
|
+
target: string;
|
|
20
|
+
type: WebmentionType;
|
|
21
|
+
/** ISO-8601 timestamp of the verification that created this record. */
|
|
22
|
+
verifiedAt: string;
|
|
23
|
+
/** ISO-8601 publication date discovered on the source, when available. */
|
|
24
|
+
publishedAt?: string;
|
|
25
|
+
/** Source title, when discoverable. */
|
|
26
|
+
title?: string;
|
|
27
|
+
/** Short plain-text excerpt of the source, when discoverable. */
|
|
28
|
+
excerpt?: string;
|
|
29
|
+
author?: WebmentionAuthor;
|
|
30
|
+
}>;
|
|
31
|
+
/** Stable identity of a mention: a source/target pair. */
|
|
32
|
+
export declare function webmentionKey(mention: Pick<WebmentionMention, "source" | "target">): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ConfigValidationIssue } from "@riebeckite/core";
|
|
2
|
+
import type { ResolvedWebmentionOptions, WebmentionOptions } from "./types.js";
|
|
3
|
+
export declare const DEFAULT_WEBMENTION_ENDPOINT = "/webmentions";
|
|
4
|
+
export declare const DEFAULT_WEBMENTION_HEADING_TEXT = "Mentions";
|
|
5
|
+
export declare const DEFAULT_WEBMENTION_LIMIT = 20;
|
|
6
|
+
export declare const DEFAULT_WEBMENTION_CLASS_NAME = "rr-webmention";
|
|
7
|
+
/**
|
|
8
|
+
* Applies defaults and creates the default source fetcher. Pure with respect
|
|
9
|
+
* to the filesystem; the only side effect is constructing a fetcher closure.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveWebmentionOptions(options?: WebmentionOptions): ResolvedWebmentionOptions;
|
|
12
|
+
export declare function validateWebmentionOptions(options: WebmentionOptions | undefined): readonly ConfigValidationIssue[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WebmentionAuthor, WebmentionType } from "./mention.js";
|
|
2
|
+
export type ParsedWebmentionLink = Readonly<{
|
|
3
|
+
href: string;
|
|
4
|
+
normalized: string | null;
|
|
5
|
+
rels: readonly string[];
|
|
6
|
+
text: string;
|
|
7
|
+
}>;
|
|
8
|
+
export type ParsedWebmentionSource = Readonly<{
|
|
9
|
+
title?: string;
|
|
10
|
+
excerpt?: string;
|
|
11
|
+
author?: WebmentionAuthor;
|
|
12
|
+
publishedAt?: string;
|
|
13
|
+
links: readonly ParsedWebmentionLink[];
|
|
14
|
+
}>;
|
|
15
|
+
/**
|
|
16
|
+
* Parses the parts of a source document that a Webmention display needs:
|
|
17
|
+
* outbound links (for verification) and lightweight citation metadata. It
|
|
18
|
+
* deliberately does not implement the full microformats2 parser; recognized
|
|
19
|
+
* `rel` values and common meta tags cover the common cases.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseWebmentionSource(html: string, sourceUrl: string): ParsedWebmentionSource;
|
|
22
|
+
/** Finds the parsed link that points at the target, if any. */
|
|
23
|
+
export declare function findTargetLink(source: ParsedWebmentionSource, target: string): ParsedWebmentionLink | null;
|
|
24
|
+
/** Maps recognized `rel` values to a Webmention type. */
|
|
25
|
+
export declare function webmentionTypeForRels(rels: readonly string[]): WebmentionType;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { WebmentionMention } from "./mention.js";
|
|
2
|
+
import type { WebmentionQuery, WebmentionQueryResult } from "./query.js";
|
|
3
|
+
export declare const WEBMENTION_CAPABILITIES: readonly ["store", "query"];
|
|
4
|
+
export type WebmentionCapability = (typeof WEBMENTION_CAPABILITIES)[number];
|
|
5
|
+
/**
|
|
6
|
+
* Storage- and runtime-independent Webmention boundary.
|
|
7
|
+
*
|
|
8
|
+
* The plugin only ever sees this interface. A provider may keep credentials,
|
|
9
|
+
* database handles, or runtime bindings internally; those never enter the
|
|
10
|
+
* plugin options that reach generated output.
|
|
11
|
+
*/
|
|
12
|
+
export interface WebmentionProvider {
|
|
13
|
+
readonly capabilities: ReadonlySet<WebmentionCapability>;
|
|
14
|
+
/**
|
|
15
|
+
* Persists a verified mention. Implementations upsert by `(source, target)`
|
|
16
|
+
* so repeated deliveries converge on one record.
|
|
17
|
+
*/
|
|
18
|
+
store(mention: WebmentionMention): Promise<void>;
|
|
19
|
+
/** Reads stored mentions. */
|
|
20
|
+
query(query: WebmentionQuery): Promise<WebmentionQueryResult>;
|
|
21
|
+
}
|
|
22
|
+
export declare function supportsWebmentionCapability(provider: WebmentionProvider, capability: WebmentionCapability): boolean;
|
|
23
|
+
export declare class UnsupportedWebmentionQueryError extends Error {
|
|
24
|
+
readonly query: WebmentionQuery;
|
|
25
|
+
readonly requiredCapability: WebmentionCapability;
|
|
26
|
+
readonly name = "UnsupportedWebmentionQueryError";
|
|
27
|
+
constructor(query: WebmentionQuery, requiredCapability: WebmentionCapability);
|
|
28
|
+
}
|
|
29
|
+
export declare function requiredCapabilityForQuery(_query: WebmentionQuery): WebmentionCapability;
|
|
30
|
+
export declare function assertWebmentionQuerySupported(provider: WebmentionProvider, query: WebmentionQuery): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { WebmentionMention } from "./mention.js";
|
|
2
|
+
export type MentionsForTargetQuery = Readonly<{
|
|
3
|
+
type: "mentions_for_target";
|
|
4
|
+
/** Absolute target URL. */
|
|
5
|
+
target: string;
|
|
6
|
+
limit?: number;
|
|
7
|
+
}>;
|
|
8
|
+
export type AllMentionsQuery = Readonly<{
|
|
9
|
+
type: "all_mentions";
|
|
10
|
+
limit?: number;
|
|
11
|
+
/** Only mentions verified at or after this ISO-8601 timestamp. */
|
|
12
|
+
since?: string;
|
|
13
|
+
}>;
|
|
14
|
+
export type WebmentionQuery = MentionsForTargetQuery | AllMentionsQuery;
|
|
15
|
+
export type MentionsForTargetResult = Readonly<{
|
|
16
|
+
type: "mentions_for_target";
|
|
17
|
+
target: string;
|
|
18
|
+
mentions: readonly WebmentionMention[];
|
|
19
|
+
}>;
|
|
20
|
+
export type AllMentionsResult = Readonly<{
|
|
21
|
+
type: "all_mentions";
|
|
22
|
+
mentions: readonly WebmentionMention[];
|
|
23
|
+
}>;
|
|
24
|
+
export type WebmentionQueryResult = MentionsForTargetResult | AllMentionsResult;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PluginEndpointContext, PluginEndpointResponse } from "@riebeckite/core";
|
|
2
|
+
import { type WebmentionProvider } from "./provider.js";
|
|
3
|
+
import type { ResolvedWebmentionOptions } from "./types.js";
|
|
4
|
+
type WebmentionRequestBody = Readonly<{
|
|
5
|
+
source?: string;
|
|
6
|
+
target?: string;
|
|
7
|
+
}>;
|
|
8
|
+
/**
|
|
9
|
+
* Builds the `POST` receive handler. It validates the request, checks the
|
|
10
|
+
* target belongs to the site, verifies the source actually links to it, and
|
|
11
|
+
* stores the verified mention through the provider.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createWebmentionReceiveHandler(options: ResolvedWebmentionOptions, provider: WebmentionProvider): (context: PluginEndpointContext) => Promise<PluginEndpointResponse>;
|
|
14
|
+
/**
|
|
15
|
+
* Parses `source`/`target` from an `application/x-www-form-urlencoded` or
|
|
16
|
+
* `application/json` body. Returns `null` for malformed JSON; missing fields
|
|
17
|
+
* are reported separately by the caller.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseWebmentionRequestBody(body: string, headers: Readonly<Record<string, string>>): WebmentionRequestBody | null;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WebmentionMention } from "./mention.js";
|
|
2
|
+
import type { ResolvedWebmentionOptions } from "./types.js";
|
|
3
|
+
/** Boolean attribute that marks the generated mention section. */
|
|
4
|
+
export declare const WEBMENTION_ATTRIBUTE = "data-webmention";
|
|
5
|
+
/**
|
|
6
|
+
* Renders the mentions section with stable `rr-webmention` hooks. Returns an
|
|
7
|
+
* empty string when there is nothing to show so callers can leave an entry's
|
|
8
|
+
* HTML untouched.
|
|
9
|
+
*/
|
|
10
|
+
export declare function renderWebmentionSection(mentions: readonly WebmentionMention[], options: ResolvedWebmentionOptions): string;
|
|
11
|
+
/** Renders one mention list item. */
|
|
12
|
+
export declare function renderWebmentionItem(mention: WebmentionMention, options: ResolvedWebmentionOptions): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ContentManifest, ResolvedRiebeckiteConfig } from "@riebeckite/core";
|
|
2
|
+
import type { WebmentionMention } from "./mention.js";
|
|
3
|
+
import type { WebmentionProvider } from "./provider.js";
|
|
4
|
+
/**
|
|
5
|
+
* Reads verified mentions for one entry. Application routes call this at
|
|
6
|
+
* request/SSR time when they render mentions themselves instead of relying on
|
|
7
|
+
* build-time injection.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getWebmentionsForEntry(input: {
|
|
10
|
+
manifest: ContentManifest;
|
|
11
|
+
config: ResolvedRiebeckiteConfig | undefined;
|
|
12
|
+
provider: WebmentionProvider;
|
|
13
|
+
slug: string;
|
|
14
|
+
limit?: number;
|
|
15
|
+
}): Promise<WebmentionMention[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Groups mentions by the manifest entry they target. Mentions whose target is
|
|
18
|
+
* not a published entry are omitted; callers that need them can query the
|
|
19
|
+
* provider directly.
|
|
20
|
+
*/
|
|
21
|
+
export declare function groupMentionsBySlug(manifest: ContentManifest, config: ResolvedRiebeckiteConfig | undefined, mentions: readonly WebmentionMention[]): Map<string, WebmentionMention[]>;
|
|
22
|
+
/** Counts mentions whose target does not match a published entry. */
|
|
23
|
+
export declare function countUnmatchedMentions(manifest: ContentManifest, config: ResolvedRiebeckiteConfig | undefined, mentions: readonly WebmentionMention[]): number;
|
|
24
|
+
export declare function compareMentions(left: WebmentionMention, right: WebmentionMention): number;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ContentManifest, ContentManifestEntry, ResolvedRiebeckiteConfig } from "@riebeckite/core";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves an entry's public URL. Relative permalinks need the configured
|
|
4
|
+
* base URL; without one, only an already-absolute permalink can be matched.
|
|
5
|
+
*/
|
|
6
|
+
export declare function entryPublicUrl(entry: ContentManifestEntry, config: ResolvedRiebeckiteConfig | undefined): string | null;
|
|
7
|
+
/** Finds the content entry whose public URL matches `target`, if any. */
|
|
8
|
+
export declare function findTargetEntry(manifest: ContentManifest, config: ResolvedRiebeckiteConfig | undefined, target: string): ContentManifestEntry | undefined;
|
|
9
|
+
/** Whether `target` is explicitly allowed by `allowedTargets`. */
|
|
10
|
+
export declare function isAllowedTarget(target: string, allowedTargets: readonly string[], config: ResolvedRiebeckiteConfig | undefined): boolean;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { WebmentionProvider } from "./provider.js";
|
|
2
|
+
import type { WebmentionSourceFetcher } from "./verify.js";
|
|
3
|
+
/**
|
|
4
|
+
* Options accepted by `webmention()`.
|
|
5
|
+
*
|
|
6
|
+
* `provider` is optional so a site can register the plugin before choosing a
|
|
7
|
+
* runtime store. Without one the plugin falls back to an in-memory provider,
|
|
8
|
+
* which is appropriate for local previews and tests but does not persist
|
|
9
|
+
* across processes or isolates.
|
|
10
|
+
*/
|
|
11
|
+
export type WebmentionOptions = Readonly<{
|
|
12
|
+
/** Verified-mention storage. Defaults to a per-plugin memory provider. */
|
|
13
|
+
provider?: WebmentionProvider;
|
|
14
|
+
/** Receive (POST) and feed (GET) path. Defaults to `/webmentions`. */
|
|
15
|
+
endpoint?: string;
|
|
16
|
+
/** Render stored mentions near articles. Defaults to `true`. */
|
|
17
|
+
render?: boolean;
|
|
18
|
+
/** Heading text for the rendered section. Defaults to `"Mentions"`. */
|
|
19
|
+
headingText?: string;
|
|
20
|
+
/** Maximum mentions rendered per article. Defaults to `20`. */
|
|
21
|
+
limit?: number;
|
|
22
|
+
/** Root CSS class of the rendered section. Defaults to `"rr-webmention"`. */
|
|
23
|
+
className?: string;
|
|
24
|
+
/** Extra absolute target URLs to accept beyond published entries. */
|
|
25
|
+
allowedTargets?: readonly string[];
|
|
26
|
+
/** Source fetcher override, mainly for tests and custom runtimes. */
|
|
27
|
+
fetchSource?: WebmentionSourceFetcher;
|
|
28
|
+
/** Fetch timeout in milliseconds. Defaults to `10000`. */
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
/** Maximum accepted source document size in bytes. Defaults to `1000000`. */
|
|
31
|
+
maxBytes?: number;
|
|
32
|
+
/** Override the verification `User-Agent` header. */
|
|
33
|
+
userAgent?: string;
|
|
34
|
+
/** Allow fetching private-network sources. Defaults to `false`. */
|
|
35
|
+
allowPrivateHosts?: boolean;
|
|
36
|
+
/** Add `rel="nofollow ugc"` to rendered source links. Defaults to `true`. */
|
|
37
|
+
nofollow?: boolean;
|
|
38
|
+
}>;
|
|
39
|
+
/** `WebmentionOptions` with every default applied. */
|
|
40
|
+
export type ResolvedWebmentionOptions = Readonly<{
|
|
41
|
+
provider: WebmentionProvider;
|
|
42
|
+
endpoint: string;
|
|
43
|
+
render: boolean;
|
|
44
|
+
headingText: string;
|
|
45
|
+
limit: number;
|
|
46
|
+
className: string;
|
|
47
|
+
allowedTargets: readonly string[];
|
|
48
|
+
fetchSource: WebmentionSourceFetcher;
|
|
49
|
+
nofollow: boolean;
|
|
50
|
+
}>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL helpers shared by verification, target matching, and storage. They keep
|
|
3
|
+
* comparison stable without changing the URLs that are persisted or rendered.
|
|
4
|
+
*/
|
|
5
|
+
/** Parses an absolute http(s) URL and returns a canonical comparison form. */
|
|
6
|
+
export declare function normalizeWebmentionUrl(value: string): string | null;
|
|
7
|
+
/**
|
|
8
|
+
* Canonical form used for equality checks. Fragments and a single trailing
|
|
9
|
+
* slash are ignored so `https://example.com/a`, `https://example.com/a/`, and
|
|
10
|
+
* `https://example.com/a#section` are treated as the same target.
|
|
11
|
+
*/
|
|
12
|
+
export declare function urlComparisonKey(value: string): string;
|
|
13
|
+
/** Resolves a site-relative permalink against the configured base URL. */
|
|
14
|
+
export declare function resolvePublicUrl(permalink: string, baseUrl: string | undefined): string | null;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { WebmentionMention } from "./mention.js";
|
|
2
|
+
export type WebmentionSourceDocument = Readonly<{
|
|
3
|
+
/** Final absolute URL of the fetched document. */
|
|
4
|
+
url: string;
|
|
5
|
+
html: string;
|
|
6
|
+
}>;
|
|
7
|
+
/**
|
|
8
|
+
* Fetches a source document. It returns `null` for an unreachable, non-HTML,
|
|
9
|
+
* oversized, or otherwise unusable source. Implementations keep their own
|
|
10
|
+
* network/runtime details; the plugin core only calls this function.
|
|
11
|
+
*/
|
|
12
|
+
export type WebmentionSourceFetcher = (url: string) => Promise<WebmentionSourceDocument | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Resolves a hostname to every IP address it currently points at. It is
|
|
15
|
+
* injectable so a runtime (or a test) can supply its own resolver. The default
|
|
16
|
+
* uses `node:dns` and falls back to no resolution when the builtin is
|
|
17
|
+
* unavailable, such as in a Worker without a DNS module.
|
|
18
|
+
*/
|
|
19
|
+
export type WebmentionHostResolver = (hostname: string) => Promise<readonly string[]>;
|
|
20
|
+
export type WebmentionSourceFetcherOptions = Readonly<{
|
|
21
|
+
/** Request timeout in milliseconds. Defaults to `10000`. */
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
/** Maximum accepted body size in bytes. Defaults to `1_000_000`. */
|
|
24
|
+
maxBytes?: number;
|
|
25
|
+
userAgent?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Allow fetching loopback and private-network hosts. Defaults to `false`.
|
|
28
|
+
* Enable only for a trusted, self-hosted verification service.
|
|
29
|
+
*/
|
|
30
|
+
allowPrivateHosts?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Hostname resolver used for DNS-rebinding checks. Defaults to the runtime's
|
|
33
|
+
* `node:dns` resolver. When resolution is unavailable, the hostname string
|
|
34
|
+
* and literal-IP checks still apply.
|
|
35
|
+
*/
|
|
36
|
+
resolveHostname?: WebmentionHostResolver;
|
|
37
|
+
}>;
|
|
38
|
+
export declare const DEFAULT_WEBMENTION_TIMEOUT_MS = 10000;
|
|
39
|
+
export declare const DEFAULT_WEBMENTION_MAX_BYTES = 1000000;
|
|
40
|
+
export declare const DEFAULT_WEBMENTION_USER_AGENT = "Riebeckite-Webmention/1.0 (+https://github.com/Rerurate514/riebeckite)";
|
|
41
|
+
/**
|
|
42
|
+
* Default fetcher backed by the global `fetch`. It follows redirects manually,
|
|
43
|
+
* validating every hop's host and resolved addresses, and caps the response
|
|
44
|
+
* body while it streams.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createWebmentionSourceFetcher(options?: WebmentionSourceFetcherOptions): WebmentionSourceFetcher;
|
|
47
|
+
export type WebmentionRejectionReason = "invalid_source" | "invalid_target" | "source_unreachable" | "no_link_found";
|
|
48
|
+
export type WebmentionVerification = Readonly<{
|
|
49
|
+
ok: true;
|
|
50
|
+
mention: WebmentionMention;
|
|
51
|
+
}> | Readonly<{
|
|
52
|
+
ok: false;
|
|
53
|
+
reason: WebmentionRejectionReason;
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Verifies that `source` actually links to `target` and, when it does, builds
|
|
57
|
+
* the verified mention record. Network access happens only through `fetchSource`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function verifyWebmention(input: {
|
|
60
|
+
source: string;
|
|
61
|
+
target: string;
|
|
62
|
+
fetchSource: WebmentionSourceFetcher;
|
|
63
|
+
verifiedAt?: string;
|
|
64
|
+
}): Promise<WebmentionVerification>;
|
|
65
|
+
/**
|
|
66
|
+
* Literal hostname check: rejects explicit local names and any literal IP in a
|
|
67
|
+
* loopback, private, link-local, unique-local, or reserved range.
|
|
68
|
+
*/
|
|
69
|
+
export declare function isDisallowedHostname(hostname: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Rejects loopback, private, link-local, unique-local, and reserved addresses.
|
|
72
|
+
* Malformed input is treated as disallowed so a parsing gap cannot fail open.
|
|
73
|
+
*/
|
|
74
|
+
export declare function isDisallowedIpAddress(address: string): boolean;
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@riebeckite/plugin-webmention",
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"source": "./index.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./style.css": "./style.css"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@riebeckite/core": "0.0.6"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"tsx": "^4.22.4"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"README.md",
|
|
29
|
+
"README_ja.md",
|
|
30
|
+
"dist",
|
|
31
|
+
"style.css"
|
|
32
|
+
],
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/Rerurate514/riebeckite.git",
|
|
36
|
+
"directory": "packages/plugins/webmention"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/Rerurate514/riebeckite#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/Rerurate514/riebeckite/issues"
|
|
41
|
+
},
|
|
42
|
+
"description": "Receive verified Webmentions and render them as mentions for Riebeckite sites.",
|
|
43
|
+
"keywords": [
|
|
44
|
+
"riebeckite",
|
|
45
|
+
"plugin",
|
|
46
|
+
"webmention",
|
|
47
|
+
"indieweb",
|
|
48
|
+
"mentions"
|
|
49
|
+
],
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
52
|
+
},
|
|
53
|
+
"sideEffects": [
|
|
54
|
+
"./style.css"
|
|
55
|
+
],
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "node ../../../scripts/build_package.mjs",
|
|
58
|
+
"test": "node --import tsx --test \"test/*.test.ts\""
|
|
59
|
+
}
|
|
60
|
+
}
|
package/style.css
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
.rr-webmention {
|
|
2
|
+
--rr-webmention-ink: var(--rb-color-ink, #181613);
|
|
3
|
+
--rr-webmention-muted: var(--rb-color-muted, #6b655c);
|
|
4
|
+
--rr-webmention-rule: color-mix(
|
|
5
|
+
in srgb,
|
|
6
|
+
var(--rr-webmention-ink) 16%,
|
|
7
|
+
transparent
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
margin-top: 3rem;
|
|
11
|
+
padding-top: 1.5rem;
|
|
12
|
+
border-top: 1px solid var(--rr-webmention-rule);
|
|
13
|
+
color: var(--rr-webmention-ink);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.rr-webmention__heading {
|
|
17
|
+
margin: 0 0 1rem;
|
|
18
|
+
font-size: 1.125rem;
|
|
19
|
+
font-weight: 700;
|
|
20
|
+
letter-spacing: 0.02em;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.rr-webmention__list {
|
|
24
|
+
display: grid;
|
|
25
|
+
gap: 1rem;
|
|
26
|
+
margin: 0;
|
|
27
|
+
padding: 0;
|
|
28
|
+
list-style: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.rr-webmention__item {
|
|
32
|
+
margin: 0;
|
|
33
|
+
padding: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.rr-webmention__source {
|
|
37
|
+
color: inherit;
|
|
38
|
+
font-weight: 600;
|
|
39
|
+
text-decoration: underline;
|
|
40
|
+
text-decoration-color: var(--rr-webmention-rule);
|
|
41
|
+
text-underline-offset: 0.2em;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.rr-webmention__source:hover,
|
|
45
|
+
.rr-webmention__source:focus-visible {
|
|
46
|
+
text-decoration-color: var(--rr-webmention-muted);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.rr-webmention__author,
|
|
50
|
+
.rr-webmention__date {
|
|
51
|
+
margin-inline-start: 0.4em;
|
|
52
|
+
color: var(--rr-webmention-muted);
|
|
53
|
+
font-size: 0.8125rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.rr-webmention__excerpt {
|
|
57
|
+
margin: 0.35rem 0 0;
|
|
58
|
+
color: var(--rr-webmention-muted);
|
|
59
|
+
font-size: 0.9375rem;
|
|
60
|
+
line-height: 1.5;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
html[data-theme="dark"] .rr-webmention,
|
|
64
|
+
html.dark .rr-webmention {
|
|
65
|
+
--rr-webmention-ink: var(--rb-color-paper, #f5f2ec);
|
|
66
|
+
}
|