dsh-featherhunter-plugin-base 0.0.1 → 0.0.2-drill

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schema": 1,
3
- "version": "0.0.1",
4
- "revision": 1,
5
- "updatedAt": "2026-10-23T00:00:00+08:00",
3
+ "version": "0.0.2-drill",
4
+ "revision": 2,
5
+ "updatedAt": "2026-10-31T00:00:00+08:00",
6
6
  "fallbackUrl": "https://github.com/FeatherHunter/featherhunter-plugin-manage",
7
7
  "groups": [
8
8
  {
@@ -77,10 +77,10 @@
77
77
  "zh": "DSH 插件交流群微信群二维码",
78
78
  "en": "DSH plugin community group WeChat QR code"
79
79
  },
80
- "card": "card-wechat-2026-10-23.png",
80
+ "card": "card-wechat-2026-11-06.png",
81
81
  "qr": {
82
82
  "kind": "remote",
83
- "path": "qr-wechat-2026-10-23.png"
83
+ "path": "qr-wechat-2026-11-06.png"
84
84
  },
85
85
  "title": {
86
86
  "zh": "DSH 插件交流群",
@@ -90,7 +90,7 @@
90
90
  "zh": "群聊:DSH 插件交流群",
91
91
  "en": "Group chat: DSH Plugin Community"
92
92
  },
93
- "validUntil": "2026-10-30"
93
+ "validUntil": "2026-11-06"
94
94
  }
95
95
  ],
96
96
  "plugins": [
@@ -0,0 +1,116 @@
1
+ import type { Manifest } from "../core/types";
2
+ /**
3
+ * Remote manifest transport (ticket 05).
4
+ *
5
+ * The contact modal must always paint the bundled baseline first and only then
6
+ * swap in the published manifest. Everything in this module is therefore
7
+ * best-effort: any failure (timeout, HTTP error, invalid JSON, invalid data,
8
+ * oversized image) silently degrades to the baseline, never to an error state.
9
+ */
10
+ /** Published package name; the CDN URLs below are derived from it. */
11
+ export declare const PACKAGE_NAME = "dsh-featherhunter-plugin-base";
12
+ /** One request may take at most 1.5s; there are no retries. */
13
+ export declare const MANIFEST_TIMEOUT_MS = 1500;
14
+ /** A fetched manifest (success or failure) is reused for 30 minutes. */
15
+ export declare const MANIFEST_CACHE_TTL_MS: number;
16
+ /** Hard cap for a single QR image, enforced before it is shown. */
17
+ export declare const MAX_IMAGE_BYTES: number;
18
+ /** The only hosts the client is allowed to talk to (images and manifest). */
19
+ export declare const ALLOWED_CDN_HOSTS: readonly string[];
20
+ /** Minimal structural view of a fetch Response; easy to fake in tests. */
21
+ export interface FetchResponseLike {
22
+ ok: boolean;
23
+ status: number;
24
+ headers: {
25
+ get(name: string): string | null;
26
+ };
27
+ text(): Promise<string>;
28
+ arrayBuffer(): Promise<ArrayBuffer>;
29
+ }
30
+ export type FetchLike = (url: string, init?: {
31
+ signal?: AbortSignal;
32
+ cache?: string;
33
+ credentials?: string;
34
+ }) => Promise<FetchResponseLike>;
35
+ /** A loaded and validated manifest plus the QR images prefetched for it. */
36
+ export interface LoadedContent {
37
+ manifest: Manifest;
38
+ /** Channel name that produced the manifest ("unpkg" / "npmmirror" / ...). */
39
+ source: string;
40
+ /** Timestamp (loader clock) at which the manifest was accepted. */
41
+ fetchedAt: number;
42
+ /** Prefetched QR images keyed by "<version>/<path>", values are data URIs. */
43
+ images: Map<string, string>;
44
+ }
45
+ /** Context handed to a channel loader. */
46
+ export interface ChannelContext {
47
+ packageName: string;
48
+ fetchText: (url: string) => Promise<string>;
49
+ fetchJson: (url: string) => Promise<unknown>;
50
+ }
51
+ /** One CDN channel; the channels are tried in array order. */
52
+ export interface ManifestChannel {
53
+ name: string;
54
+ load: (context: ChannelContext) => Promise<{
55
+ raw: unknown;
56
+ url: string;
57
+ }>;
58
+ }
59
+ export interface ContentLoaderOptions {
60
+ /** Explicit manifest URL; when set, the default CDN channels are not used. */
61
+ manifestUrl?: string;
62
+ packageName?: string;
63
+ fetchImpl?: FetchLike;
64
+ timeoutMs?: number;
65
+ maxImageBytes?: number;
66
+ /** Clock override (testing seam). */
67
+ now?: () => number;
68
+ /** Channel override (testing seam). */
69
+ channels?: ManifestChannel[];
70
+ }
71
+ /** True for https URLs only. */
72
+ export declare function isHttpsUrl(value: string): boolean;
73
+ /** True when the URL is https and its host is on the CDN allowlist. */
74
+ export declare function isAllowedCdnUrl(value: string): boolean;
75
+ /** Exact-version QR image URLs, primary jsDelivr then the npmmirror mirror. */
76
+ export declare function imageUrls(version: string, path: string, packageName?: string): string[];
77
+ /** The three published channels, in fallback order. */
78
+ export declare function defaultChannels(packageName?: string): ManifestChannel[];
79
+ /**
80
+ * Validate a remote manifest beyond what parseManifest checks: length limits
81
+ * for user-visible text, a sane version, safe image paths and https links.
82
+ * Throws an Error whose message starts with "manifest: ".
83
+ */
84
+ export declare function validateRemoteManifest(raw: unknown): Manifest;
85
+ /** Drop the module cache; tests use this, production never needs it. */
86
+ export declare function resetContentCache(): void;
87
+ export declare class ContentLoader {
88
+ readonly packageName: string;
89
+ readonly timeoutMs: number;
90
+ readonly maxImageBytes: number;
91
+ private readonly fetchImpl;
92
+ private readonly now;
93
+ private readonly channels;
94
+ constructor(options?: ContentLoaderOptions);
95
+ /** Cached content when it is still inside the 30 minute window. */
96
+ getCached(): LoadedContent | null;
97
+ /**
98
+ * Fetch and validate the manifest once per 30 minutes; concurrent callers
99
+ * share one request. Resolves to null when every channel failed.
100
+ */
101
+ load(): Promise<LoadedContent | null>;
102
+ /** Prefetch every remote QR image of a manifest (best effort). */
103
+ ensureImages(content: LoadedContent): Promise<void>;
104
+ /** Data URI for a remote QR path, or null when it is not prefetched. */
105
+ imageFor(content: LoadedContent | null, path: string): string | null;
106
+ /** Warm the cache: manifest plus images. Never rejects. */
107
+ preload(): Promise<void>;
108
+ private loadImage;
109
+ private fetchImage;
110
+ private fetchContent;
111
+ private fetchText;
112
+ /** One request with the 1.5s abort window; the timer is always cleared. */
113
+ private request;
114
+ }
115
+ /** Build a loader bound to the published package (or a custom manifest URL). */
116
+ export declare function createContentLoader(options?: ContentLoaderOptions): ContentLoader;
@@ -11,3 +11,5 @@ export declare function createBaseUI(React: typeof ReactNamespace, options?: Bas
11
11
  export { BASE_CSS, BASE_STYLE_ID, ensureBaseStyles } from "./styles";
12
12
  export { MESSAGES, resolveText } from "./texts";
13
13
  export type { TextKey, TextOverrides } from "./texts";
14
+ export { ALLOWED_CDN_HOSTS, ContentLoader, MANIFEST_CACHE_TTL_MS, MANIFEST_TIMEOUT_MS, MAX_IMAGE_BYTES, createContentLoader, defaultChannels, imageUrls, isAllowedCdnUrl, isHttpsUrl, resetContentCache, validateRemoteManifest, } from "./content";
15
+ export type { ContentLoaderOptions, FetchLike, FetchResponseLike, LoadedContent, ManifestChannel } from "./content";