@hoardodile/host-web 0.0.0
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 +18 -0
- package/README.md +31 -0
- package/dist/file-backends-Dyy2BxUM.d.ts +27 -0
- package/dist/index.d.ts +293 -0
- package/dist/index.js +532 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.ts +12 -0
- package/dist/node.js +25 -0
- package/dist/node.js.map +1 -0
- package/package.json +60 -0
- package/src/consent/consent-store.test.ts +105 -0
- package/src/consent/consent-store.ts +129 -0
- package/src/host-core/request-schemas.ts +85 -0
- package/src/host-core/router.test.ts +231 -0
- package/src/host-core/router.ts +234 -0
- package/src/index.ts +47 -0
- package/src/mock/file-backends.ts +69 -0
- package/src/mock/host.test.ts +188 -0
- package/src/mock/host.ts +370 -0
- package/src/mock/stores.ts +89 -0
- package/src/mock-node/index.ts +37 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import { anchorData as anchorData$1 } from '@hoardodile/sdk-types/schema';
|
|
2
|
+
import { pluginMethods, PROTOCOL_VERSION, invalidatePushKeys, hostPushKeys } from '@hoardodile/sdk-web';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
// src/consent/consent-store.ts
|
|
6
|
+
var state = { queue: [] };
|
|
7
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
8
|
+
var pending = /* @__PURE__ */ new Map();
|
|
9
|
+
function setState(next) {
|
|
10
|
+
state = next;
|
|
11
|
+
for (const listener of listeners) {
|
|
12
|
+
listener();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function subscribeDownloadConsent(listener) {
|
|
16
|
+
listeners.add(listener);
|
|
17
|
+
return () => {
|
|
18
|
+
listeners.delete(listener);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function getDownloadConsentSnapshot() {
|
|
22
|
+
return state;
|
|
23
|
+
}
|
|
24
|
+
function enqueueDownloadConsent(entry) {
|
|
25
|
+
for (const existing of state.queue) {
|
|
26
|
+
if (existing.ticketId === entry.ticketId) return;
|
|
27
|
+
}
|
|
28
|
+
setState({ queue: [...state.queue, entry] });
|
|
29
|
+
}
|
|
30
|
+
function closeDownloadConsent(ticketId) {
|
|
31
|
+
const queue = state.queue.filter((entry) => entry.ticketId !== ticketId);
|
|
32
|
+
if (queue.length === state.queue.length) return;
|
|
33
|
+
setState({ queue });
|
|
34
|
+
}
|
|
35
|
+
function rehydrateDownloadConsent(entries) {
|
|
36
|
+
const byTicket = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const entry of entries) byTicket.set(entry.ticketId, entry);
|
|
38
|
+
setState({ queue: [...byTicket.values()] });
|
|
39
|
+
}
|
|
40
|
+
function requestDownloadConsent(entry) {
|
|
41
|
+
if (pending.has(entry.ticketId)) {
|
|
42
|
+
return Promise.resolve(false);
|
|
43
|
+
}
|
|
44
|
+
enqueueDownloadConsent(entry);
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
pending.set(entry.ticketId, { resolve });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function decideDownloadConsent(ticketId, approved) {
|
|
50
|
+
const waiter = pending.get(ticketId);
|
|
51
|
+
if (waiter !== void 0) {
|
|
52
|
+
pending.delete(ticketId);
|
|
53
|
+
waiter.resolve(approved);
|
|
54
|
+
}
|
|
55
|
+
closeDownloadConsent(ticketId);
|
|
56
|
+
}
|
|
57
|
+
function resetDownloadConsent() {
|
|
58
|
+
for (const waiter of pending.values()) waiter.resolve(false);
|
|
59
|
+
pending.clear();
|
|
60
|
+
setState({ queue: [] });
|
|
61
|
+
}
|
|
62
|
+
var anchorData = anchorData$1;
|
|
63
|
+
var requestSchemas = {
|
|
64
|
+
[pluginMethods.readFile]: z.object({
|
|
65
|
+
path: z.string().min(1),
|
|
66
|
+
range: z.object({
|
|
67
|
+
start: z.number().int().nonnegative().optional(),
|
|
68
|
+
end: z.number().int().nonnegative().optional()
|
|
69
|
+
}).optional()
|
|
70
|
+
}),
|
|
71
|
+
[pluginMethods.listFiles]: void 0,
|
|
72
|
+
[pluginMethods.listMessages]: void 0,
|
|
73
|
+
[pluginMethods.createMessage]: z.object({
|
|
74
|
+
body: z.string().min(1),
|
|
75
|
+
anchor: anchorData.optional()
|
|
76
|
+
}),
|
|
77
|
+
[pluginMethods.listDanmaku]: z.object({
|
|
78
|
+
// Filter keys are plugin-defined vocabulary; matching is generic
|
|
79
|
+
// strict equality against the anchor's data.
|
|
80
|
+
filter: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional()
|
|
81
|
+
}),
|
|
82
|
+
[pluginMethods.createDanmaku]: z.object({
|
|
83
|
+
text: z.string().min(1),
|
|
84
|
+
anchor: anchorData,
|
|
85
|
+
mode: z.string().optional()
|
|
86
|
+
}),
|
|
87
|
+
[pluginMethods.setPref]: z.object({
|
|
88
|
+
key: z.string().min(1),
|
|
89
|
+
value: z.string()
|
|
90
|
+
}),
|
|
91
|
+
[pluginMethods.setCache]: z.object({
|
|
92
|
+
key: z.string().min(1),
|
|
93
|
+
value: z.string()
|
|
94
|
+
}),
|
|
95
|
+
[pluginMethods.invalidate]: z.object({
|
|
96
|
+
target: z.enum(["resource", "resources", "messages", "danmaku"])
|
|
97
|
+
}),
|
|
98
|
+
// The URL is length-capped but not URI-validated here: the host parses
|
|
99
|
+
// it server-side and answers with a machine-readable POLICY error.
|
|
100
|
+
[pluginMethods.download]: z.object({
|
|
101
|
+
url: z.string().min(1).max(2048),
|
|
102
|
+
dest: z.string().min(1).max(256),
|
|
103
|
+
sha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
104
|
+
reason: z.string().max(200).optional()
|
|
105
|
+
}),
|
|
106
|
+
[pluginMethods.deleteAsset]: z.object({
|
|
107
|
+
path: z.string().min(1).max(256)
|
|
108
|
+
}),
|
|
109
|
+
[pluginMethods.logInfo]: z.object({
|
|
110
|
+
message: z.string(),
|
|
111
|
+
data: z.record(z.string(), z.unknown()).optional()
|
|
112
|
+
}),
|
|
113
|
+
[pluginMethods.logWarn]: z.object({
|
|
114
|
+
message: z.string(),
|
|
115
|
+
data: z.record(z.string(), z.unknown()).optional()
|
|
116
|
+
}),
|
|
117
|
+
[pluginMethods.logError]: z.object({
|
|
118
|
+
message: z.string(),
|
|
119
|
+
data: z.record(z.string(), z.unknown()).optional()
|
|
120
|
+
})
|
|
121
|
+
};
|
|
122
|
+
function defineHandler(method, schemaOrHandler, maybeHandler) {
|
|
123
|
+
if (typeof schemaOrHandler === "function") {
|
|
124
|
+
return {
|
|
125
|
+
method,
|
|
126
|
+
schema: void 0,
|
|
127
|
+
handler: async (ctx) => schemaOrHandler(ctx)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (maybeHandler === void 0) {
|
|
131
|
+
throw new Error(`defineHandler("${method}") called without a handler`);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
method,
|
|
135
|
+
schema: schemaOrHandler,
|
|
136
|
+
handler: async (ctx, params) => maybeHandler(ctx, params)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function createHostRouter(handlers, deps) {
|
|
140
|
+
const registry = /* @__PURE__ */ new Map();
|
|
141
|
+
const schemas = /* @__PURE__ */ new Map();
|
|
142
|
+
for (const entry of handlers) {
|
|
143
|
+
if (registry.has(entry.method)) {
|
|
144
|
+
throw new Error(`Duplicate handler method: ${entry.method}`);
|
|
145
|
+
}
|
|
146
|
+
registry.set(entry.method, entry.handler);
|
|
147
|
+
if (entry.schema !== void 0) schemas.set(entry.method, entry.schema);
|
|
148
|
+
}
|
|
149
|
+
const warnedSources = /* @__PURE__ */ new WeakSet();
|
|
150
|
+
return function handleMessage(event) {
|
|
151
|
+
const resolved = deps.resolveSource(event);
|
|
152
|
+
if (resolved === void 0) return;
|
|
153
|
+
const { source, record } = resolved;
|
|
154
|
+
const msg = event.data;
|
|
155
|
+
if (msg == null || typeof msg !== "object" || msg.type === void 0) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const proto = msg.proto;
|
|
159
|
+
if (proto !== PROTOCOL_VERSION && !warnedSources.has(source)) {
|
|
160
|
+
warnedSources.add(source);
|
|
161
|
+
if (proto === void 0) {
|
|
162
|
+
console.warn(
|
|
163
|
+
`[host-web] plugin iframe (${record.pluginId}) did not stamp its protocol version \u2014 it was built against an older SDK. Assuming PROTOCOL_VERSION ${PROTOCOL_VERSION}; rebuild the plugin to silence this warning.`
|
|
164
|
+
);
|
|
165
|
+
} else {
|
|
166
|
+
console.warn(
|
|
167
|
+
`[host-web] plugin iframe (${record.pluginId}) speaks protocol version ${String(proto)} but this host speaks ${PROTOCOL_VERSION} \u2014 the plugin was built against a different SDK version and may misbehave.`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (msg.type === "subscribe") {
|
|
172
|
+
deps.subscribe(source, msg.key);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (msg.type !== "request") return;
|
|
176
|
+
function respond(response) {
|
|
177
|
+
deps.respond(source, response);
|
|
178
|
+
}
|
|
179
|
+
if (typeof msg.resId === "string" && msg.resId !== "" && msg.resId !== record.resId) {
|
|
180
|
+
respond({ type: "response", id: msg.id, ok: true });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const ctx = {
|
|
184
|
+
source,
|
|
185
|
+
resId: record.resId,
|
|
186
|
+
pluginId: record.pluginId
|
|
187
|
+
};
|
|
188
|
+
const handler = registry.get(msg.method);
|
|
189
|
+
if (handler === void 0) {
|
|
190
|
+
respond({
|
|
191
|
+
type: "response",
|
|
192
|
+
id: msg.id,
|
|
193
|
+
ok: false,
|
|
194
|
+
error: `Unknown method: ${msg.method}`
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
let params;
|
|
199
|
+
const schema = schemas.get(msg.method);
|
|
200
|
+
if (schema !== void 0) {
|
|
201
|
+
const parsed = schema.safeParse(msg.params);
|
|
202
|
+
if (!parsed.success) {
|
|
203
|
+
respond({
|
|
204
|
+
type: "response",
|
|
205
|
+
id: msg.id,
|
|
206
|
+
ok: false,
|
|
207
|
+
error: `Invalid params for ${msg.method}: ${parsed.error.message}`
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
params = parsed.data;
|
|
212
|
+
} else {
|
|
213
|
+
params = msg.params;
|
|
214
|
+
}
|
|
215
|
+
handler(ctx, params).then((data) => {
|
|
216
|
+
respond({ type: "response", id: msg.id, ok: true, data });
|
|
217
|
+
}).catch((err) => {
|
|
218
|
+
const errorName = err instanceof Error && err.name !== "Error" ? err.name : void 0;
|
|
219
|
+
respond({
|
|
220
|
+
type: "response",
|
|
221
|
+
id: msg.id,
|
|
222
|
+
ok: false,
|
|
223
|
+
error: err instanceof Error ? err.message : String(err),
|
|
224
|
+
// Machine-readable plugin error code (`DENIED`/…)
|
|
225
|
+
// travels as `errorCode`; `errorName` is the legacy
|
|
226
|
+
// alias (same value) so older SDK builds keep working.
|
|
227
|
+
errorCode: errorName,
|
|
228
|
+
errorName
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/mock/file-backends.ts
|
|
235
|
+
function createInMemoryFileBackend(files = {}) {
|
|
236
|
+
return {
|
|
237
|
+
async listFiles() {
|
|
238
|
+
return Object.keys(files);
|
|
239
|
+
},
|
|
240
|
+
async readFile(_resId, path, range) {
|
|
241
|
+
const content = files[path];
|
|
242
|
+
if (content === void 0) {
|
|
243
|
+
throw new Error(`mock file backend has no entry ${path}`);
|
|
244
|
+
}
|
|
245
|
+
const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
246
|
+
if (range === void 0) return bytes.slice().buffer;
|
|
247
|
+
const start = Math.max(0, range.start ?? 0);
|
|
248
|
+
const end = Math.min(range.end ?? bytes.length, bytes.length);
|
|
249
|
+
return bytes.slice(start, end).buffer;
|
|
250
|
+
},
|
|
251
|
+
async statFile(_resId, path) {
|
|
252
|
+
const content = files[path];
|
|
253
|
+
if (content === void 0) return void 0;
|
|
254
|
+
const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
255
|
+
return { sizeBytes: bytes.byteLength };
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/mock/stores.ts
|
|
261
|
+
var nextId = 1;
|
|
262
|
+
function generateId(prefix) {
|
|
263
|
+
return `${prefix}-${nextId++}`;
|
|
264
|
+
}
|
|
265
|
+
function createMockMessageStore(seed = []) {
|
|
266
|
+
const rows = [...seed];
|
|
267
|
+
return {
|
|
268
|
+
list(resId) {
|
|
269
|
+
return rows.filter((m) => m.resIds.includes(resId));
|
|
270
|
+
},
|
|
271
|
+
create(resId, input) {
|
|
272
|
+
const message = {
|
|
273
|
+
id: generateId("msg"),
|
|
274
|
+
body: input.body,
|
|
275
|
+
createdAt: Date.now(),
|
|
276
|
+
charIds: [],
|
|
277
|
+
resIds: [resId],
|
|
278
|
+
likeCount: 0,
|
|
279
|
+
dislikeCount: 0,
|
|
280
|
+
replyCount: 0,
|
|
281
|
+
anchor: input.anchor
|
|
282
|
+
};
|
|
283
|
+
rows.push(message);
|
|
284
|
+
return message;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function createMockDanmakuStore(seed = []) {
|
|
289
|
+
const rows = [...seed];
|
|
290
|
+
return {
|
|
291
|
+
list(resId) {
|
|
292
|
+
return rows.filter((d) => d.anchor.resId === resId);
|
|
293
|
+
},
|
|
294
|
+
create(resId, input) {
|
|
295
|
+
const danmaku = {
|
|
296
|
+
id: generateId("dm"),
|
|
297
|
+
anchor: { resId, data: input.anchor.data },
|
|
298
|
+
text: input.text,
|
|
299
|
+
color: "#fff",
|
|
300
|
+
mode: input.mode === "scroll" || input.mode === "top" || input.mode === "bottom" ? input.mode : "scroll",
|
|
301
|
+
createdAt: Date.now()
|
|
302
|
+
};
|
|
303
|
+
rows.push(danmaku);
|
|
304
|
+
return danmaku;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/mock/host.ts
|
|
310
|
+
var defaultLogger = {
|
|
311
|
+
info(message, data) {
|
|
312
|
+
console.log(`[mock-host] ${message}`, data ?? "");
|
|
313
|
+
},
|
|
314
|
+
warn(message, data) {
|
|
315
|
+
console.warn(`[mock-host] ${message}`, data ?? "");
|
|
316
|
+
},
|
|
317
|
+
error(message, data) {
|
|
318
|
+
console.error(`[mock-host] ${message}`, data ?? "");
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
function createMockHost(opts) {
|
|
322
|
+
const targetWindow = opts.targetWindow;
|
|
323
|
+
const logger = opts.logger ?? defaultLogger;
|
|
324
|
+
const messages = opts.messages ?? createMockMessageStore();
|
|
325
|
+
const danmaku = opts.danmaku ?? createMockDanmakuStore();
|
|
326
|
+
const prefs = new Map(Object.entries(opts.prefs ?? {}));
|
|
327
|
+
const cache = new Map(Object.entries(opts.cache ?? {}));
|
|
328
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
329
|
+
const subscriptions = /* @__PURE__ */ new Map();
|
|
330
|
+
function postToSource(source, msg) {
|
|
331
|
+
source.postMessage(msg, "*");
|
|
332
|
+
}
|
|
333
|
+
function pushToSource(source, key, data) {
|
|
334
|
+
postToSource(source, { type: "push", key, data });
|
|
335
|
+
}
|
|
336
|
+
const handlers = [
|
|
337
|
+
defineHandler(
|
|
338
|
+
pluginMethods.readFile,
|
|
339
|
+
requestSchemas[pluginMethods.readFile],
|
|
340
|
+
async (ctx, params) => {
|
|
341
|
+
return opts.files.readFile(ctx.resId, params.path, params.range);
|
|
342
|
+
}
|
|
343
|
+
),
|
|
344
|
+
defineHandler(pluginMethods.listFiles, async (ctx) => {
|
|
345
|
+
const entries = await opts.files.listFileEntries?.(ctx.resId);
|
|
346
|
+
if (entries !== void 0) return entries;
|
|
347
|
+
const names = [...await opts.files.listFiles(ctx.resId)];
|
|
348
|
+
names.sort(
|
|
349
|
+
(a, b) => a.localeCompare(b, void 0, { sensitivity: "base", numeric: true })
|
|
350
|
+
);
|
|
351
|
+
return names;
|
|
352
|
+
}),
|
|
353
|
+
defineHandler(
|
|
354
|
+
pluginMethods.logInfo,
|
|
355
|
+
requestSchemas[pluginMethods.logInfo],
|
|
356
|
+
(_ctx, params) => {
|
|
357
|
+
logger.info(logMessage(params), logData(params));
|
|
358
|
+
}
|
|
359
|
+
),
|
|
360
|
+
defineHandler(
|
|
361
|
+
pluginMethods.logWarn,
|
|
362
|
+
requestSchemas[pluginMethods.logWarn],
|
|
363
|
+
(_ctx, params) => {
|
|
364
|
+
logger.warn(logMessage(params), logData(params));
|
|
365
|
+
}
|
|
366
|
+
),
|
|
367
|
+
defineHandler(
|
|
368
|
+
pluginMethods.logError,
|
|
369
|
+
requestSchemas[pluginMethods.logError],
|
|
370
|
+
(_ctx, params) => {
|
|
371
|
+
logger.error(logMessage(params), logData(params));
|
|
372
|
+
}
|
|
373
|
+
),
|
|
374
|
+
defineHandler(pluginMethods.listMessages, async (ctx) => {
|
|
375
|
+
return messages.list(ctx.resId);
|
|
376
|
+
}),
|
|
377
|
+
defineHandler(
|
|
378
|
+
pluginMethods.createMessage,
|
|
379
|
+
requestSchemas[pluginMethods.createMessage],
|
|
380
|
+
async (ctx, params) => {
|
|
381
|
+
const anchor = params.anchor === void 0 ? void 0 : { ...params.anchor, resId: ctx.resId };
|
|
382
|
+
return messages.create(ctx.resId, { body: params.body, anchor });
|
|
383
|
+
}
|
|
384
|
+
),
|
|
385
|
+
defineHandler(
|
|
386
|
+
pluginMethods.listDanmaku,
|
|
387
|
+
requestSchemas[pluginMethods.listDanmaku],
|
|
388
|
+
async (ctx, params) => {
|
|
389
|
+
const rows = danmaku.list(ctx.resId);
|
|
390
|
+
const filter = params.filter;
|
|
391
|
+
if (filter === void 0) return rows;
|
|
392
|
+
return rows.filter((d) => matchesDanmakuFilter(d.anchor.data, filter));
|
|
393
|
+
}
|
|
394
|
+
),
|
|
395
|
+
defineHandler(
|
|
396
|
+
pluginMethods.createDanmaku,
|
|
397
|
+
requestSchemas[pluginMethods.createDanmaku],
|
|
398
|
+
async (ctx, params) => {
|
|
399
|
+
return danmaku.create(ctx.resId, {
|
|
400
|
+
text: params.text,
|
|
401
|
+
anchor: { ...params.anchor, resId: ctx.resId },
|
|
402
|
+
mode: params.mode
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
),
|
|
406
|
+
defineHandler(
|
|
407
|
+
pluginMethods.setPref,
|
|
408
|
+
requestSchemas[pluginMethods.setPref],
|
|
409
|
+
async (_ctx, params) => {
|
|
410
|
+
prefs.set(params.key, params.value);
|
|
411
|
+
opts.onPrefChanged?.(params.key, params.value);
|
|
412
|
+
}
|
|
413
|
+
),
|
|
414
|
+
defineHandler(
|
|
415
|
+
pluginMethods.setCache,
|
|
416
|
+
requestSchemas[pluginMethods.setCache],
|
|
417
|
+
async (ctx, params) => {
|
|
418
|
+
if (ctx.resId === "") return;
|
|
419
|
+
cache.set(`${ctx.resId}:${params.key}`, params.value);
|
|
420
|
+
opts.onCacheChanged?.(ctx.resId, params.key, params.value);
|
|
421
|
+
}
|
|
422
|
+
),
|
|
423
|
+
defineHandler(
|
|
424
|
+
pluginMethods.invalidate,
|
|
425
|
+
requestSchemas[pluginMethods.invalidate],
|
|
426
|
+
async (ctx, params) => {
|
|
427
|
+
pushToSource(ctx.source, invalidatePushKeys[params.target]);
|
|
428
|
+
}
|
|
429
|
+
),
|
|
430
|
+
// Without a wired asset vault the offline host answers
|
|
431
|
+
// UNAVAILABLE — the exact vocabulary a plugin sees from a generic
|
|
432
|
+
// mock. The workbench passes `assetVault` to run the real flow
|
|
433
|
+
// (consent dialog + dev-server download into a local vault).
|
|
434
|
+
defineHandler(
|
|
435
|
+
pluginMethods.download,
|
|
436
|
+
requestSchemas[pluginMethods.download],
|
|
437
|
+
async (_ctx, params) => {
|
|
438
|
+
const vault = opts.assetVault;
|
|
439
|
+
if (vault === void 0) throw unavailableAssetError("download");
|
|
440
|
+
return vault.download(params);
|
|
441
|
+
}
|
|
442
|
+
),
|
|
443
|
+
defineHandler(
|
|
444
|
+
pluginMethods.deleteAsset,
|
|
445
|
+
requestSchemas[pluginMethods.deleteAsset],
|
|
446
|
+
async (_ctx, params) => {
|
|
447
|
+
const vault = opts.assetVault;
|
|
448
|
+
if (vault === void 0) throw unavailableAssetError("deleteAsset");
|
|
449
|
+
return vault.deleteAsset(params.path);
|
|
450
|
+
}
|
|
451
|
+
)
|
|
452
|
+
];
|
|
453
|
+
const router = createHostRouter(handlers, {
|
|
454
|
+
resolveSource(event) {
|
|
455
|
+
const source = event.source === null ? targetWindow : event.source;
|
|
456
|
+
const record = bindings.get(source);
|
|
457
|
+
if (record === void 0) return void 0;
|
|
458
|
+
return { source, record };
|
|
459
|
+
},
|
|
460
|
+
respond(source, response) {
|
|
461
|
+
postToSource(source, response);
|
|
462
|
+
},
|
|
463
|
+
subscribe(source, key) {
|
|
464
|
+
let keys = subscriptions.get(source);
|
|
465
|
+
if (keys === void 0) {
|
|
466
|
+
keys = /* @__PURE__ */ new Set();
|
|
467
|
+
subscriptions.set(source, keys);
|
|
468
|
+
}
|
|
469
|
+
keys.add(key);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
function onMessage(event) {
|
|
473
|
+
router(event);
|
|
474
|
+
}
|
|
475
|
+
targetWindow.addEventListener("message", onMessage);
|
|
476
|
+
return {
|
|
477
|
+
register(source, binding) {
|
|
478
|
+
bindings.set(source, binding);
|
|
479
|
+
},
|
|
480
|
+
unregister(source) {
|
|
481
|
+
bindings.delete(source);
|
|
482
|
+
subscriptions.delete(source);
|
|
483
|
+
},
|
|
484
|
+
push(source, key, data) {
|
|
485
|
+
pushToSource(source, key, data);
|
|
486
|
+
},
|
|
487
|
+
pushContext(source, ctx) {
|
|
488
|
+
pushToSource(source, hostPushKeys.context, ctx);
|
|
489
|
+
},
|
|
490
|
+
setVisibility(source, visible) {
|
|
491
|
+
pushToSource(source, hostPushKeys.visibility, { visible });
|
|
492
|
+
},
|
|
493
|
+
messages,
|
|
494
|
+
danmaku,
|
|
495
|
+
prefs,
|
|
496
|
+
cache,
|
|
497
|
+
dispose() {
|
|
498
|
+
targetWindow.removeEventListener("message", onMessage);
|
|
499
|
+
bindings.clear();
|
|
500
|
+
subscriptions.clear();
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function logMessage(params) {
|
|
505
|
+
const p = params;
|
|
506
|
+
return typeof p?.message === "string" ? p.message : String(params);
|
|
507
|
+
}
|
|
508
|
+
function logData(params) {
|
|
509
|
+
const p = params;
|
|
510
|
+
return p?.data;
|
|
511
|
+
}
|
|
512
|
+
function isRecord(value) {
|
|
513
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
514
|
+
}
|
|
515
|
+
function matchesDanmakuFilter(data, filter) {
|
|
516
|
+
if (!isRecord(data)) return false;
|
|
517
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
518
|
+
if (value !== void 0 && data[key] !== value) return false;
|
|
519
|
+
}
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
function unavailableAssetError(method) {
|
|
523
|
+
const err = new Error(
|
|
524
|
+
`${method}() is unavailable in the offline host \u2014 plugin asset downloads need the app server runtime`
|
|
525
|
+
);
|
|
526
|
+
err.name = "UNAVAILABLE";
|
|
527
|
+
return err;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export { anchorData, closeDownloadConsent, createHostRouter, createInMemoryFileBackend, createMockDanmakuStore, createMockHost, createMockMessageStore, decideDownloadConsent, defineHandler, enqueueDownloadConsent, getDownloadConsentSnapshot, rehydrateDownloadConsent, requestDownloadConsent, requestSchemas, resetDownloadConsent, subscribeDownloadConsent };
|
|
531
|
+
//# sourceMappingURL=index.js.map
|
|
532
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/consent/consent-store.ts","../src/host-core/request-schemas.ts","../src/host-core/router.ts","../src/mock/file-backends.ts","../src/mock/stores.ts","../src/mock/host.ts"],"names":["anchorDataSchema","pluginMethods"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * The consent-dialog queue for plugin asset downloads — the shared\n * implementation both host apps mount:\n *\n * - the **app** feeds it from SSE (`pluginDownloadRequested` /\n * `pluginDownloadResolved`) and drives decisions through the server\n * (`pluginAsset.decide`); the resolved broadcast closes entries here;\n * - the **workbench** has no server: its asset engine enqueues via\n * {@link request} and the dialog answers via {@link decide} — the same\n * dialog component, one queue per page, FIFO, one dialog at a time.\n *\n * State-only by design: decisions never mutate the queue directly\n * except through `close`/`decide`, which the two hosts wire to their\n * own decision path.\n */\n\n/**\n * A queued consent question: the ticket shape. Structurally identical to\n * the server's `pluginDownloadRequested` SSE event minus its `type`\n * discriminator (and to `pluginAsset.listPending` rows) — declared here\n * instead of importing `@hoardodile/schemas` so the host-core package\n * stays dependency-lean.\n */\nexport type DownloadConsentEntry = {\n\treadonly ticketId: string\n\treadonly pluginId: string\n\treadonly pluginName: string\n\treadonly url: string\n\treadonly dest: string\n\treadonly sizeBytes?: number\n\treadonly reason?: string\n}\n\ntype State = {\n\treadonly queue: readonly DownloadConsentEntry[]\n}\n\nlet state: State = { queue: [] }\nconst listeners = new Set<() => void>()\n\n/** Engine side: tickets awaiting a local decision (workbench path). */\nconst pending = new Map<\n\tstring,\n\t{ readonly resolve: (approved: boolean) => void }\n>()\n\nfunction setState(next: State): void {\n\tstate = next\n\tfor (const listener of listeners) {\n\t\tlistener()\n\t}\n}\n\nexport function subscribeDownloadConsent(listener: () => void): () => void {\n\tlisteners.add(listener)\n\treturn () => {\n\t\tlisteners.delete(listener)\n\t}\n}\n\nexport function getDownloadConsentSnapshot(): State {\n\treturn state\n}\n\n/** A new ticket arrived (SSE) or was requested locally: append if absent. */\nexport function enqueueDownloadConsent(entry: DownloadConsentEntry): void {\n\tfor (const existing of state.queue) {\n\t\tif (existing.ticketId === entry.ticketId) return\n\t}\n\tsetState({ queue: [...state.queue, entry] })\n}\n\n/** A ticket resolved (decide/timeout/dispose): drop it in every consumer. */\nexport function closeDownloadConsent(ticketId: string): void {\n\tconst queue = state.queue.filter((entry) => entry.ticketId !== ticketId)\n\tif (queue.length === state.queue.length) return\n\tsetState({ queue })\n}\n\n/**\n * Replace the queue from a pending list (app: `listPending` after an SSE\n * reconnect; workbench: same API on restart).\n */\nexport function rehydrateDownloadConsent(\n\tentries: readonly DownloadConsentEntry[],\n): void {\n\tconst byTicket = new Map<string, DownloadConsentEntry>()\n\tfor (const entry of entries) byTicket.set(entry.ticketId, entry)\n\tsetState({ queue: [...byTicket.values()] })\n}\n\n/**\n * Enqueue a ticket and wait for its decision (workbench engine path).\n * The dialog's answer arrives through {@link decideDownloadConsent}.\n */\nexport function requestDownloadConsent(\n\tentry: DownloadConsentEntry,\n): Promise<boolean> {\n\tif (pending.has(entry.ticketId)) {\n\t\treturn Promise.resolve(false)\n\t}\n\tenqueueDownloadConsent(entry)\n\treturn new Promise<boolean>((resolve) => {\n\t\tpending.set(entry.ticketId, { resolve })\n\t})\n}\n\n/**\n * Answer a ticket from the dialog. Closes the entry and resolves any\n * awaiting engine call. Idempotent; unknown tickets are a no-op.\n */\nexport function decideDownloadConsent(\n\tticketId: string,\n\tapproved: boolean,\n): void {\n\tconst waiter = pending.get(ticketId)\n\tif (waiter !== undefined) {\n\t\tpending.delete(ticketId)\n\t\twaiter.resolve(approved)\n\t}\n\tcloseDownloadConsent(ticketId)\n}\n\n/** Test-only: reset the singleton between tests. */\nexport function resetDownloadConsent(): void {\n\tfor (const waiter of pending.values()) waiter.resolve(false)\n\tpending.clear()\n\tsetState({ queue: [] })\n}\n","import { anchorData as anchorDataSchema } from \"@hoardodile/sdk-types/schema\"\nimport type { PluginRequests } from \"@hoardodile/sdk-web\"\nimport { pluginMethods } from \"@hoardodile/sdk-web\"\nimport { z } from \"zod\"\n\n/**\n * Wire-level param schemas for every request method, shared by the real\n * host (apps/web) and the offline mock host. Both hosts validate\n * identical shapes, so a plugin that passes validation in the mock is\n * guaranteed to pass in production.\n */\n\n/**\n * Wire shape of a message/danmaku anchor: the plugin location payload\n * only. The resource id is host state — the host stamps its own binding\n * into the anchor before it reaches the server.\n */\nexport const anchorData = anchorDataSchema\n\nexport const requestSchemas = {\n\t[pluginMethods.readFile]: z.object({\n\t\tpath: z.string().min(1),\n\t\trange: z\n\t\t\t.object({\n\t\t\t\tstart: z.number().int().nonnegative().optional(),\n\t\t\t\tend: z.number().int().nonnegative().optional(),\n\t\t\t})\n\t\t\t.optional(),\n\t}),\n\t[pluginMethods.listFiles]: undefined,\n\t[pluginMethods.listMessages]: undefined,\n\t[pluginMethods.createMessage]: z.object({\n\t\tbody: z.string().min(1),\n\t\tanchor: anchorData.optional(),\n\t}),\n\t[pluginMethods.listDanmaku]: z.object({\n\t\t// Filter keys are plugin-defined vocabulary; matching is generic\n\t\t// strict equality against the anchor's data.\n\t\tfilter: z\n\t\t\t.record(z.string(), z.union([z.string(), z.number(), z.boolean()]))\n\t\t\t.optional(),\n\t}),\n\t[pluginMethods.createDanmaku]: z.object({\n\t\ttext: z.string().min(1),\n\t\tanchor: anchorData,\n\t\tmode: z.string().optional(),\n\t}),\n\t[pluginMethods.setPref]: z.object({\n\t\tkey: z.string().min(1),\n\t\tvalue: z.string(),\n\t}),\n\t[pluginMethods.setCache]: z.object({\n\t\tkey: z.string().min(1),\n\t\tvalue: z.string(),\n\t}),\n\t[pluginMethods.invalidate]: z.object({\n\t\ttarget: z.enum([\"resource\", \"resources\", \"messages\", \"danmaku\"]),\n\t}),\n\t// The URL is length-capped but not URI-validated here: the host parses\n\t// it server-side and answers with a machine-readable POLICY error.\n\t[pluginMethods.download]: z.object({\n\t\turl: z.string().min(1).max(2048),\n\t\tdest: z.string().min(1).max(256),\n\t\tsha256: z\n\t\t\t.string()\n\t\t\t.regex(/^[0-9a-f]{64}$/)\n\t\t\t.optional(),\n\t\treason: z.string().max(200).optional(),\n\t}),\n\t[pluginMethods.deleteAsset]: z.object({\n\t\tpath: z.string().min(1).max(256),\n\t}),\n\t[pluginMethods.logInfo]: z.object({\n\t\tmessage: z.string(),\n\t\tdata: z.record(z.string(), z.unknown()).optional(),\n\t}),\n\t[pluginMethods.logWarn]: z.object({\n\t\tmessage: z.string(),\n\t\tdata: z.record(z.string(), z.unknown()).optional(),\n\t}),\n\t[pluginMethods.logError]: z.object({\n\t\tmessage: z.string(),\n\t\tdata: z.record(z.string(), z.unknown()).optional(),\n\t}),\n} satisfies Record<keyof PluginRequests, z.ZodTypeAny | undefined>\n","import {\n\ttype HostResponse,\n\ttype PluginMessage,\n\tPROTOCOL_VERSION,\n} from \"@hoardodile/sdk-web\"\nimport type { z } from \"zod\"\n\n/**\n * The shared host-side protocol core: message demux, method routing,\n * per-method param validation, stale-request scoping and the response\n * envelope. Both the real host (apps/web) and the offline mock host\n * assemble on this module, so routing and validation never drift.\n */\n\n/** What a registered iframe (or mock window) is bound to. */\nexport type HostBinding = {\n\treadonly pluginId: string\n\treadonly resId: string\n}\n\nexport type HostRouterDeps = {\n\t/**\n\t * Authenticate an inbound message event: narrow it to a trusted source\n\t * + binding, or return `undefined` to drop the message. The real host\n\t * validates origin/source against its iframe registry; the mock\n\t * validates against its own registered windows.\n\t */\n\treadonly resolveSource: (\n\t\tevent: MessageEvent,\n\t) => { readonly source: unknown; readonly record: HostBinding } | undefined\n\t/** Send a response back to the source (the layer's postMessage exit). */\n\treadonly respond: (source: unknown, response: HostResponse) => void\n\t/** Record a subscription key for the source. */\n\treadonly subscribe: (source: unknown, key: string) => void\n}\n\nexport type HostHandlerContext = {\n\treadonly source: unknown\n\t/**\n\t * The resource this request is scoped to: the source's current\n\t * binding (\"\" only for never-bound sources). Requests stamped by the\n\t * SDK with a different resource are dropped as stale before they\n\t * reach a handler.\n\t */\n\treadonly resId: string\n\treadonly pluginId: string\n}\n\nexport type HostHandlerEntry = {\n\treadonly method: string\n\t/** Param schema; validated in the router before the handler runs. */\n\treadonly schema: z.ZodTypeAny | undefined\n\treadonly handler: (\n\t\tctx: HostHandlerContext,\n\t\tparams: unknown,\n\t) => Promise<unknown>\n}\n\nexport function defineHandler<TReturn>(\n\tmethod: string,\n\thandler: (ctx: HostHandlerContext) => Promise<TReturn> | TReturn,\n): HostHandlerEntry\n\nexport function defineHandler<TSchema extends z.ZodTypeAny, TReturn>(\n\tmethod: string,\n\tschema: TSchema,\n\thandler: (\n\t\tctx: HostHandlerContext,\n\t\tparams: z.infer<TSchema>,\n\t) => Promise<TReturn> | TReturn,\n): HostHandlerEntry\n\nexport function defineHandler(\n\tmethod: string,\n\tschemaOrHandler:\n\t\t| z.ZodTypeAny\n\t\t| ((ctx: HostHandlerContext) => Promise<unknown> | unknown),\n\tmaybeHandler?: (\n\t\tctx: HostHandlerContext,\n\t\tparams: unknown,\n\t) => Promise<unknown> | unknown,\n): HostHandlerEntry {\n\tif (typeof schemaOrHandler === \"function\") {\n\t\treturn {\n\t\t\tmethod,\n\t\t\tschema: undefined,\n\t\t\thandler: async (ctx) => schemaOrHandler(ctx),\n\t\t}\n\t}\n\tif (maybeHandler === undefined) {\n\t\tthrow new Error(`defineHandler(\"${method}\") called without a handler`)\n\t}\n\treturn {\n\t\tmethod,\n\t\tschema: schemaOrHandler,\n\t\thandler: async (ctx, params) => maybeHandler(ctx, params),\n\t}\n}\n\n/**\n * Create the per-window message handler: authenticates the event,\n * routes requests by method, validates params, drops stale scopes and\n * wraps every outcome in the response envelope.\n */\nexport function createHostRouter(\n\thandlers: readonly HostHandlerEntry[],\n\tdeps: HostRouterDeps,\n): (event: MessageEvent) => void {\n\tconst registry = new Map<string, HostHandlerEntry[\"handler\"]>()\n\tconst schemas = new Map<string, z.ZodTypeAny>()\n\tfor (const entry of handlers) {\n\t\tif (registry.has(entry.method)) {\n\t\t\tthrow new Error(`Duplicate handler method: ${entry.method}`)\n\t\t}\n\t\tregistry.set(entry.method, entry.handler)\n\t\tif (entry.schema !== undefined) schemas.set(entry.method, entry.schema)\n\t}\n\n\t// Warn once per source so a mismatched plugin build does not spam the\n\t// console on every message, while still surfacing the problem loudly.\n\tconst warnedSources = new WeakSet<object>()\n\n\treturn function handleMessage(event: MessageEvent) {\n\t\tconst resolved = deps.resolveSource(event)\n\t\tif (resolved === undefined) return\n\t\tconst { source, record } = resolved\n\n\t\tconst msg = event.data as PluginMessage\n\t\tif (msg == null || typeof msg !== \"object\" || msg.type === undefined) {\n\t\t\treturn\n\t\t}\n\n\t\t// Protocol handshake: every SDK-build plugin stamps its messages\n\t\t// with the protocol version it was built against. A mismatch (or a\n\t\t// missing stamp — an old plugin build) means the wire contract may\n\t\t// have drifted; the message still routes so old plugins keep\n\t\t// working, but the developer gets a loud warning.\n\t\tconst proto = (msg as { proto?: unknown }).proto\n\t\tif (proto !== PROTOCOL_VERSION && !warnedSources.has(source as object)) {\n\t\t\twarnedSources.add(source as object)\n\t\t\tif (proto === undefined) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[host-web] plugin iframe (${record.pluginId}) did not stamp its protocol version — it was built against an older SDK. Assuming PROTOCOL_VERSION ${PROTOCOL_VERSION}; rebuild the plugin to silence this warning.`,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[host-web] plugin iframe (${record.pluginId}) speaks protocol version ${String(proto)} but this host speaks ${PROTOCOL_VERSION} — the plugin was built against a different SDK version and may misbehave.`,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tif (msg.type === \"subscribe\") {\n\t\t\tdeps.subscribe(source, msg.key)\n\t\t\treturn\n\t\t}\n\n\t\tif (msg.type !== \"request\") return\n\n\t\tfunction respond(response: HostResponse): void {\n\t\t\tdeps.respond(source, response)\n\t\t}\n\n\t\t// The SDK stamps each request with the resource it was issued for\n\t\t// (PluginRequest.resId). A stamp that no longer matches the\n\t\t// binding marks the request as stale — the tree that issued it is\n\t\t// gone (e.g. an unmount flush racing a rebind) — so it is dropped\n\t\t// silently instead of leaking into the wrong resource. Unstamped\n\t\t// requests (older plugin builds) use the current binding, which\n\t\t// outlives release, so late flushes after a close still land.\n\t\tif (\n\t\t\ttypeof msg.resId === \"string\" &&\n\t\t\tmsg.resId !== \"\" &&\n\t\t\tmsg.resId !== record.resId\n\t\t) {\n\t\t\trespond({ type: \"response\", id: msg.id, ok: true })\n\t\t\treturn\n\t\t}\n\n\t\tconst ctx: HostHandlerContext = {\n\t\t\tsource,\n\t\t\tresId: record.resId,\n\t\t\tpluginId: record.pluginId,\n\t\t}\n\n\t\tconst handler = registry.get(msg.method)\n\t\tif (handler === undefined) {\n\t\t\trespond({\n\t\t\t\ttype: \"response\",\n\t\t\t\tid: msg.id,\n\t\t\t\tok: false,\n\t\t\t\terror: `Unknown method: ${msg.method}`,\n\t\t\t})\n\t\t\treturn\n\t\t}\n\n\t\tlet params: unknown\n\t\tconst schema = schemas.get(msg.method)\n\t\tif (schema !== undefined) {\n\t\t\tconst parsed = schema.safeParse(msg.params)\n\t\t\tif (!parsed.success) {\n\t\t\t\trespond({\n\t\t\t\t\ttype: \"response\",\n\t\t\t\t\tid: msg.id,\n\t\t\t\t\tok: false,\n\t\t\t\t\terror: `Invalid params for ${msg.method}: ${parsed.error.message}`,\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\t\t\tparams = parsed.data\n\t\t} else {\n\t\t\tparams = msg.params\n\t\t}\n\n\t\thandler(ctx, params)\n\t\t\t.then((data) => {\n\t\t\t\trespond({ type: \"response\", id: msg.id, ok: true, data })\n\t\t\t})\n\t\t\t.catch((err) => {\n\t\t\t\tconst errorName =\n\t\t\t\t\terr instanceof Error && err.name !== \"Error\" ? err.name : undefined\n\t\t\t\trespond({\n\t\t\t\t\ttype: \"response\",\n\t\t\t\t\tid: msg.id,\n\t\t\t\t\tok: false,\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t// Machine-readable plugin error code (`DENIED`/…)\n\t\t\t\t\t// travels as `errorCode`; `errorName` is the legacy\n\t\t\t\t\t// alias (same value) so older SDK builds keep working.\n\t\t\t\t\terrorCode: errorName,\n\t\t\t\t\terrorName,\n\t\t\t\t})\n\t\t\t})\n\t}\n}\n","import type { ReadFileRange } from \"@hoardodile/sdk-types\"\n\n/** Shared read-range contract; defined once in `@hoardodile/sdk-types`. */\nexport type { ReadFileRange } from \"@hoardodile/sdk-types\"\n\n/**\n * File backends for the offline mock host: the plugin's `listFiles` and\n * `readFile` requests resolve against this interface, so a mock can be\n * pointed at an in-memory map, a real directory (Node), or a read-only\n * HTTP mount (workbench) without changing the host.\n */\nexport type MockFileBackend = {\n\treadonly listFiles: (resId: string) => Promise<readonly string[]>\n\treadonly readFile: (\n\t\tresId: string,\n\t\tpath: string,\n\t\trange?: ReadFileRange,\n\t) => Promise<ArrayBuffer>\n\treadonly statFile: (\n\t\tresId: string,\n\t\tpath: string,\n\t) => Promise<{ readonly sizeBytes: number } | undefined>\n\t/**\n\t * The rows the plugin's own `listFiles` hook produced, when the\n\t * backend can obtain them (the workbench reads them from a sandboxed\n\t * hook snapshot). In production the host answers `listFiles` with\n\t * exactly these plugin-shaped entries; returning `undefined` falls\n\t * back to generic `{filename, ext, sizeBytes}` rows.\n\t */\n\treadonly listFileEntries?: (\n\t\tresId: string,\n\t) => Promise<readonly unknown[] | undefined>\n}\n\n/** In-memory file map backend for unit tests. `resId` is ignored. */\nexport function createInMemoryFileBackend(\n\tfiles: Readonly<Record<string, string | Uint8Array>> = {},\n): MockFileBackend {\n\treturn {\n\t\tasync listFiles() {\n\t\t\treturn Object.keys(files)\n\t\t},\n\t\tasync readFile(_resId, path, range) {\n\t\t\tconst content = files[path]\n\t\t\tif (content === undefined) {\n\t\t\t\tthrow new Error(`mock file backend has no entry ${path}`)\n\t\t\t}\n\t\t\tconst bytes =\n\t\t\t\ttypeof content === \"string\"\n\t\t\t\t\t? new TextEncoder().encode(content)\n\t\t\t\t\t: content\n\t\t\t// Mirrors host semantics: the range is clamped to the content\n\t\t\t// size; a start at or past the end resolves to an empty result.\n\t\t\tif (range === undefined) return bytes.slice().buffer\n\t\t\tconst start = Math.max(0, range.start ?? 0)\n\t\t\tconst end = Math.min(range.end ?? bytes.length, bytes.length)\n\t\t\treturn bytes.slice(start, end).buffer\n\t\t},\n\t\tasync statFile(_resId, path) {\n\t\t\tconst content = files[path]\n\t\t\tif (content === undefined) return undefined\n\t\t\tconst bytes =\n\t\t\t\ttypeof content === \"string\"\n\t\t\t\t\t? new TextEncoder().encode(content)\n\t\t\t\t\t: content\n\t\t\treturn { sizeBytes: bytes.byteLength }\n\t\t},\n\t}\n}\n","import type { Danmaku, Message } from \"@hoardodile/sdk-types\"\n\n/**\n * In-memory stores backing the offline mock host's message and danmaku\n * handlers. Rows are shaped like the server's responses so plugin UI\n * tests exercise the real consumption paths.\n */\n\nlet nextId = 1\n\nfunction generateId(prefix: string): string {\n\treturn `${prefix}-${nextId++}`\n}\n\nexport type MockMessageStore = {\n\treadonly list: (resId: string) => readonly Message[]\n\treadonly create: (\n\t\tresId: string,\n\t\tinput: { body: string; anchor?: unknown },\n\t) => Message\n}\n\n/**\n * `seed` pre-fills the store with rows the plugin should already see —\n * the workbench passes the resource's real comments so the iframe opens\n * with the same content the app would show.\n */\nexport function createMockMessageStore(\n\tseed: readonly Message[] = [],\n): MockMessageStore {\n\tconst rows: Message[] = [...seed]\n\treturn {\n\t\tlist(resId) {\n\t\t\treturn rows.filter((m) => m.resIds.includes(resId))\n\t\t},\n\t\tcreate(resId, input) {\n\t\t\tconst message: Message = {\n\t\t\t\tid: generateId(\"msg\"),\n\t\t\t\tbody: input.body,\n\t\t\t\tcreatedAt: Date.now(),\n\t\t\t\tcharIds: [],\n\t\t\t\tresIds: [resId],\n\t\t\t\tlikeCount: 0,\n\t\t\t\tdislikeCount: 0,\n\t\t\t\treplyCount: 0,\n\t\t\t\tanchor: input.anchor as Message[\"anchor\"],\n\t\t\t}\n\t\t\trows.push(message)\n\t\t\treturn message\n\t\t},\n\t}\n}\n\nexport type MockDanmakuStore = {\n\treadonly list: (resId: string) => readonly Danmaku[]\n\treadonly create: (\n\t\tresId: string,\n\t\tinput: { text: string; anchor: unknown; mode?: string },\n\t) => Danmaku\n}\n\n/** See {@link createMockMessageStore} for `seed`. */\nexport function createMockDanmakuStore(\n\tseed: readonly Danmaku[] = [],\n): MockDanmakuStore {\n\tconst rows: Danmaku[] = [...seed]\n\treturn {\n\t\tlist(resId) {\n\t\t\treturn rows.filter((d) => d.anchor.resId === resId)\n\t\t},\n\t\tcreate(resId, input) {\n\t\t\tconst danmaku: Danmaku = {\n\t\t\t\tid: generateId(\"dm\"),\n\t\t\t\tanchor: { resId, data: (input.anchor as { data?: unknown }).data },\n\t\t\t\ttext: input.text,\n\t\t\t\tcolor: \"#fff\",\n\t\t\t\tmode:\n\t\t\t\t\tinput.mode === \"scroll\" ||\n\t\t\t\t\tinput.mode === \"top\" ||\n\t\t\t\t\tinput.mode === \"bottom\"\n\t\t\t\t\t\t? input.mode\n\t\t\t\t\t\t: \"scroll\",\n\t\t\t\tcreatedAt: Date.now(),\n\t\t\t}\n\t\t\trows.push(danmaku)\n\t\t\treturn danmaku\n\t\t},\n\t}\n}\n","import type {\n\tDanmakuListFilter,\n\tPluginAssetDeleteResult,\n\tPluginDownloadRequest,\n\tPluginDownloadResult,\n} from \"@hoardodile/sdk-types\"\nimport {\n\ttype HostPush,\n\ttype HostResponse,\n\thostPushKeys,\n\tinvalidatePushKeys,\n\ttype PluginIframeContext,\n\tpluginMethods,\n} from \"@hoardodile/sdk-web\"\nimport { requestSchemas } from \"../host-core/request-schemas.ts\"\nimport type { HostBinding } from \"../host-core/router.ts\"\nimport {\n\tcreateHostRouter,\n\tdefineHandler,\n\ttype HostHandlerEntry,\n} from \"../host-core/router.ts\"\nimport type { MockFileBackend } from \"./file-backends.ts\"\nimport {\n\tcreateMockDanmakuStore,\n\tcreateMockMessageStore,\n\ttype MockDanmakuStore,\n\ttype MockMessageStore,\n} from \"./stores.ts\"\n\n/**\n * The offline host side of the plugin postMessage bridge. Implements the\n * same routing, validation and scoping as the production host (via\n * host-core) with in-memory data — so plugin iframes run with no server\n * at all. Shared by automated component tests (jsdom: register the test\n * window) and the manual workbench page (register the real iframe\n * window).\n */\n\nexport type MockHostLogger = {\n\treadonly info: (message: string, data?: unknown) => void\n\treadonly warn: (message: string, data?: unknown) => void\n\treadonly error: (message: string, data?: unknown) => void\n}\n\nconst defaultLogger: MockHostLogger = {\n\tinfo(message, data) {\n\t\tconsole.log(`[mock-host] ${message}`, data ?? \"\")\n\t},\n\twarn(message, data) {\n\t\tconsole.warn(`[mock-host] ${message}`, data ?? \"\")\n\t},\n\terror(message, data) {\n\t\tconsole.error(`[mock-host] ${message}`, data ?? \"\")\n\t},\n}\n\nexport type MockHostOptions = {\n\t/**\n\t * Window that receives plugin postMessage traffic: the page window in\n\t * a workbench, the test window in jsdom. The host listens on it and\n\t * posts responses to it.\n\t */\n\treadonly targetWindow: Window\n\treadonly files: MockFileBackend\n\treadonly messages?: MockMessageStore\n\treadonly danmaku?: MockDanmakuStore\n\t/** Initial plugin-scoped prefs. */\n\treadonly prefs?: Readonly<Record<string, string>>\n\t/** Initial plugin+resId cache entries. */\n\treadonly cache?: Readonly<Record<string, string>>\n\treadonly logger?: MockHostLogger\n\t/** Called after a plugin writes a pref. */\n\treadonly onPrefChanged?: (key: string, value: string) => void\n\t/** Called after a plugin writes a cache entry. */\n\treadonly onCacheChanged?: (resId: string, key: string, value: string) => void\n\t/**\n\t * Plugin asset vault implementation (workbench only). Absent → the\n\t * asset methods answer `UNAVAILABLE` (tests, jsdom hosts). The real\n\t * app routes these through its server pipeline via tRPC — the mock\n\t * never talks to it.\n\t */\n\treadonly assetVault?: PluginAssetVaultMock\n}\n\n/**\n * The workbench-side plugin asset vault: the mock hands `download` and\n * `deleteAsset` to the host app's implementation (consent dialog + dev\n * server fetch + local vault), mirroring the server pipeline's\n * request/result vocabulary.\n */\nexport type PluginAssetVaultMock = {\n\treadonly download: (\n\t\trequest: PluginDownloadRequest,\n\t) => Promise<PluginDownloadResult>\n\treadonly deleteAsset: (path: string) => Promise<PluginAssetDeleteResult>\n}\n\nexport type MockHost = {\n\t/**\n\t * Bind a message source to a plugin/resource. The real host binds\n\t * iframe contentWindows; jsdom tests register the test window itself\n\t * (plugin code posts to `window.parent`, which is itself).\n\t */\n\treadonly register: (source: unknown, binding: HostBinding) => void\n\treadonly unregister: (source: unknown) => void\n\t/** Push a host event to one source. */\n\treadonly push: (source: unknown, key: string, data?: unknown) => void\n\t/** Push the plugin context (the iframe mounts on this). */\n\treadonly pushContext: (source: unknown, ctx: PluginIframeContext) => void\n\treadonly setVisibility: (source: unknown, visible: boolean) => void\n\treadonly messages: MockMessageStore\n\treadonly danmaku: MockDanmakuStore\n\treadonly prefs: ReadonlyMap<string, string>\n\treadonly cache: ReadonlyMap<string, string>\n\treadonly dispose: () => void\n}\n\nexport function createMockHost(opts: MockHostOptions): MockHost {\n\tconst targetWindow = opts.targetWindow\n\tconst logger = opts.logger ?? defaultLogger\n\tconst messages = opts.messages ?? createMockMessageStore()\n\tconst danmaku = opts.danmaku ?? createMockDanmakuStore()\n\tconst prefs = new Map(Object.entries(opts.prefs ?? {}))\n\tconst cache = new Map(Object.entries(opts.cache ?? {}))\n\tconst bindings = new Map<unknown, HostBinding>()\n\tconst subscriptions = new Map<unknown, Set<string>>()\n\n\tfunction postToSource(source: unknown, msg: HostPush | HostResponse): void {\n\t\t;(source as Window).postMessage(msg, \"*\")\n\t}\n\n\tfunction pushToSource(source: unknown, key: string, data?: unknown): void {\n\t\tpostToSource(source, { type: \"push\", key, data })\n\t}\n\n\tconst handlers: readonly HostHandlerEntry[] = [\n\t\tdefineHandler(\n\t\t\tpluginMethods.readFile,\n\t\t\trequestSchemas[pluginMethods.readFile],\n\t\t\tasync (ctx, params) => {\n\t\t\t\treturn opts.files.readFile(ctx.resId, params.path, params.range)\n\t\t\t},\n\t\t),\n\n\t\tdefineHandler(pluginMethods.listFiles, async (ctx) => {\n\t\t\t// Production answers with the rows the plugin's own listFiles\n\t\t\t// hook produced; a backend that can obtain them serves those.\n\t\t\tconst entries = await opts.files.listFileEntries?.(ctx.resId)\n\t\t\tif (entries !== undefined) return entries\n\t\t\t// No hook (or no way to reach it): the server falls back to\n\t\t\t// bare, naturally sorted filenames — mirror that exactly, or\n\t\t\t// plugins typed against the fallback shape break here only.\n\t\t\tconst names = [...(await opts.files.listFiles(ctx.resId))]\n\t\t\tnames.sort((a, b) =>\n\t\t\t\ta.localeCompare(b, undefined, { sensitivity: \"base\", numeric: true }),\n\t\t\t)\n\t\t\treturn names\n\t\t}),\n\n\t\tdefineHandler(\n\t\t\tpluginMethods.logInfo,\n\t\t\trequestSchemas[pluginMethods.logInfo],\n\t\t\t(_ctx, params) => {\n\t\t\t\tlogger.info(logMessage(params), logData(params))\n\t\t\t},\n\t\t),\n\t\tdefineHandler(\n\t\t\tpluginMethods.logWarn,\n\t\t\trequestSchemas[pluginMethods.logWarn],\n\t\t\t(_ctx, params) => {\n\t\t\t\tlogger.warn(logMessage(params), logData(params))\n\t\t\t},\n\t\t),\n\t\tdefineHandler(\n\t\t\tpluginMethods.logError,\n\t\t\trequestSchemas[pluginMethods.logError],\n\t\t\t(_ctx, params) => {\n\t\t\t\tlogger.error(logMessage(params), logData(params))\n\t\t\t},\n\t\t),\n\n\t\tdefineHandler(pluginMethods.listMessages, async (ctx) => {\n\t\t\treturn messages.list(ctx.resId)\n\t\t}),\n\n\t\tdefineHandler(\n\t\t\tpluginMethods.createMessage,\n\t\t\trequestSchemas[pluginMethods.createMessage],\n\t\t\tasync (ctx, params) => {\n\t\t\t\tconst anchor =\n\t\t\t\t\tparams.anchor === undefined\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: { ...params.anchor, resId: ctx.resId }\n\t\t\t\treturn messages.create(ctx.resId, { body: params.body, anchor })\n\t\t\t},\n\t\t),\n\n\t\tdefineHandler(\n\t\t\tpluginMethods.listDanmaku,\n\t\t\trequestSchemas[pluginMethods.listDanmaku],\n\t\t\tasync (ctx, params) => {\n\t\t\t\tconst rows = danmaku.list(ctx.resId)\n\t\t\t\tconst filter = params.filter\n\t\t\t\tif (filter === undefined) return rows\n\t\t\t\treturn rows.filter((d) => matchesDanmakuFilter(d.anchor.data, filter))\n\t\t\t},\n\t\t),\n\n\t\tdefineHandler(\n\t\t\tpluginMethods.createDanmaku,\n\t\t\trequestSchemas[pluginMethods.createDanmaku],\n\t\t\tasync (ctx, params) => {\n\t\t\t\treturn danmaku.create(ctx.resId, {\n\t\t\t\t\ttext: params.text,\n\t\t\t\t\tanchor: { ...params.anchor, resId: ctx.resId },\n\t\t\t\t\tmode: params.mode,\n\t\t\t\t})\n\t\t\t},\n\t\t),\n\n\t\tdefineHandler(\n\t\t\tpluginMethods.setPref,\n\t\t\trequestSchemas[pluginMethods.setPref],\n\t\t\tasync (_ctx, params) => {\n\t\t\t\tprefs.set(params.key, params.value)\n\t\t\t\topts.onPrefChanged?.(params.key, params.value)\n\t\t\t},\n\t\t),\n\n\t\tdefineHandler(\n\t\t\tpluginMethods.setCache,\n\t\t\trequestSchemas[pluginMethods.setCache],\n\t\t\tasync (ctx, params) => {\n\t\t\t\t// A write from a never-bound iframe has nowhere to land —\n\t\t\t\t// drop it silently, same as the production host.\n\t\t\t\tif (ctx.resId === \"\") return\n\t\t\t\tcache.set(`${ctx.resId}:${params.key}`, params.value)\n\t\t\t\topts.onCacheChanged?.(ctx.resId, params.key, params.value)\n\t\t\t},\n\t\t),\n\n\t\tdefineHandler(\n\t\t\tpluginMethods.invalidate,\n\t\t\trequestSchemas[pluginMethods.invalidate],\n\t\t\tasync (ctx, params) => {\n\t\t\t\t// Notify the caller so its query hooks refetch — the mock's\n\t\t\t\t// stores are the source of truth, so the refetch returns the\n\t\t\t\t// updated data.\n\t\t\t\tpushToSource(ctx.source, invalidatePushKeys[params.target])\n\t\t\t},\n\t\t),\n\n\t\t// Without a wired asset vault the offline host answers\n\t\t// UNAVAILABLE — the exact vocabulary a plugin sees from a generic\n\t\t// mock. The workbench passes `assetVault` to run the real flow\n\t\t// (consent dialog + dev-server download into a local vault).\n\t\tdefineHandler(\n\t\t\tpluginMethods.download,\n\t\t\trequestSchemas[pluginMethods.download],\n\t\t\tasync (_ctx, params) => {\n\t\t\t\tconst vault = opts.assetVault\n\t\t\t\tif (vault === undefined) throw unavailableAssetError(\"download\")\n\t\t\t\treturn vault.download(params)\n\t\t\t},\n\t\t),\n\t\tdefineHandler(\n\t\t\tpluginMethods.deleteAsset,\n\t\t\trequestSchemas[pluginMethods.deleteAsset],\n\t\t\tasync (_ctx, params) => {\n\t\t\t\tconst vault = opts.assetVault\n\t\t\t\tif (vault === undefined) throw unavailableAssetError(\"deleteAsset\")\n\t\t\t\treturn vault.deleteAsset(params.path)\n\t\t\t},\n\t\t),\n\t]\n\n\tconst router = createHostRouter(handlers, {\n\t\tresolveSource(event) {\n\t\t\t// jsdom delivers same-window messages with source nulled out;\n\t\t\t// real iframes always carry their contentWindow.\n\t\t\tconst source = event.source === null ? targetWindow : event.source\n\t\t\tconst record = bindings.get(source)\n\t\t\tif (record === undefined) return undefined\n\t\t\treturn { source, record }\n\t\t},\n\t\trespond(source, response) {\n\t\t\tpostToSource(source, response)\n\t\t},\n\t\tsubscribe(source, key) {\n\t\t\tlet keys = subscriptions.get(source)\n\t\t\tif (keys === undefined) {\n\t\t\t\tkeys = new Set()\n\t\t\t\tsubscriptions.set(source, keys)\n\t\t\t}\n\t\t\tkeys.add(key)\n\t\t},\n\t})\n\n\tfunction onMessage(event: MessageEvent): void {\n\t\trouter(event)\n\t}\n\ttargetWindow.addEventListener(\"message\", onMessage)\n\n\treturn {\n\t\tregister(source, binding) {\n\t\t\tbindings.set(source, binding)\n\t\t},\n\t\tunregister(source) {\n\t\t\tbindings.delete(source)\n\t\t\tsubscriptions.delete(source)\n\t\t},\n\t\tpush(source, key, data) {\n\t\t\tpushToSource(source, key, data)\n\t\t},\n\t\tpushContext(source, ctx) {\n\t\t\tpushToSource(source, hostPushKeys.context, ctx)\n\t\t},\n\t\tsetVisibility(source, visible) {\n\t\t\tpushToSource(source, hostPushKeys.visibility, { visible })\n\t\t},\n\t\tmessages,\n\t\tdanmaku,\n\t\tprefs,\n\t\tcache,\n\t\tdispose() {\n\t\t\ttargetWindow.removeEventListener(\"message\", onMessage)\n\t\t\tbindings.clear()\n\t\t\tsubscriptions.clear()\n\t\t},\n\t}\n}\n\nfunction logMessage(params: unknown): string {\n\tconst p = params as { message?: unknown } | undefined\n\treturn typeof p?.message === \"string\" ? p.message : String(params)\n}\n\nfunction logData(params: unknown): unknown {\n\tconst p = params as { data?: unknown } | undefined\n\treturn p?.data\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value)\n}\n\n/**\n * Matches a danmaku against the plugin-declared filter: every declared\n * field must equal the value stored under the same key in the anchor's\n * `data` — identical semantics to the production host handler.\n */\nfunction matchesDanmakuFilter(\n\tdata: unknown,\n\tfilter: DanmakuListFilter,\n): boolean {\n\tif (!isRecord(data)) return false\n\tfor (const [key, value] of Object.entries(filter)) {\n\t\tif (value !== undefined && data[key] !== value) return false\n\t}\n\treturn true\n}\n\n/** Machine-readable asset-error rejection (the shared vocabulary). */\nfunction unavailableAssetError(method: string): Error {\n\tconst err = new Error(\n\t\t`${method}() is unavailable in the offline host — plugin asset downloads need the app server runtime`,\n\t)\n\terr.name = \"UNAVAILABLE\"\n\treturn err\n}\n"]}
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { M as MockFileBackend } from './file-backends-Dyy2BxUM.js';
|
|
2
|
+
import '@hoardodile/sdk-types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A mock file backend over a raw directory — a resource folder under a
|
|
6
|
+
* real storage root (bare files, the production shape) or a plain
|
|
7
|
+
* fixture directory. Read-only by construction — immutable versioned
|
|
8
|
+
* partitions make this bypass safe.
|
|
9
|
+
*/
|
|
10
|
+
declare function createDirectoryFileBackend(dir: string): MockFileBackend;
|
|
11
|
+
|
|
12
|
+
export { createDirectoryFileBackend };
|