slackmark-node 0.1.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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +168 -0
  3. package/THIRD_PARTY_NOTICES.md +14 -0
  4. package/dist/create-node-converter.d.ts +72 -0
  5. package/dist/create-node-converter.d.ts.map +1 -0
  6. package/dist/errors.d.ts +60 -0
  7. package/dist/errors.d.ts.map +1 -0
  8. package/dist/image-output.d.ts +9 -0
  9. package/dist/image-output.d.ts.map +1 -0
  10. package/dist/index.d.ts +9 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +1792 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/katex-assets.d.ts +19 -0
  15. package/dist/katex-assets.d.ts.map +1 -0
  16. package/dist/katex-renderer.d.ts +39 -0
  17. package/dist/katex-renderer.d.ts.map +1 -0
  18. package/dist/mermaid-assets.d.ts +9 -0
  19. package/dist/mermaid-assets.d.ts.map +1 -0
  20. package/dist/mermaid-renderer.d.ts +33 -0
  21. package/dist/mermaid-renderer.d.ts.map +1 -0
  22. package/dist/node-options.d.ts +6 -0
  23. package/dist/node-options.d.ts.map +1 -0
  24. package/dist/operation-control.d.ts +18 -0
  25. package/dist/operation-control.d.ts.map +1 -0
  26. package/dist/puppeteer-render-runtime.d.ts +76 -0
  27. package/dist/puppeteer-render-runtime.d.ts.map +1 -0
  28. package/dist/puppeteer.d.ts +7 -0
  29. package/dist/puppeteer.d.ts.map +1 -0
  30. package/dist/puppeteer.js +723 -0
  31. package/dist/puppeteer.js.map +1 -0
  32. package/dist/renderers.d.ts +5 -0
  33. package/dist/renderers.d.ts.map +1 -0
  34. package/dist/renderers.js +551 -0
  35. package/dist/renderers.js.map +1 -0
  36. package/dist/status.d.ts +20 -0
  37. package/dist/status.d.ts.map +1 -0
  38. package/dist/upload-scheduler.d.ts +32 -0
  39. package/dist/upload-scheduler.d.ts.map +1 -0
  40. package/package.json +72 -0
@@ -0,0 +1,551 @@
1
+ import { ConfigurationError, isOperationError } from "slackmark";
2
+ import { createHash } from "node:crypto";
3
+ //#region src/errors.ts
4
+ const NodeRenderErrorCode = {
5
+ AssetMissing: "asset_missing",
6
+ BrowserBinaryMissing: "browser_binary_missing",
7
+ BrowserDisconnected: "browser_disconnected",
8
+ BrowserFailed: "browser_failed",
9
+ CleanupFailed: "cleanup_failed",
10
+ CloseFailed: "close_failed",
11
+ ContextFailed: "context_failed",
12
+ DimensionLimit: "dimension_limit",
13
+ OutputLimit: "output_limit",
14
+ QueueFull: "queue_full",
15
+ RenderFailed: "render_failed",
16
+ SourceLimit: "source_limit"
17
+ };
18
+ const NodeRenderStage = {
19
+ Admission: "admission",
20
+ AssetLoad: "asset_load",
21
+ Browser: "browser",
22
+ BrowserCleanup: "browser_cleanup",
23
+ BrowserClose: "browser_close",
24
+ BrowserLaunch: "browser_launch",
25
+ ContextCleanup: "context_cleanup",
26
+ ContextCreate: "context_create",
27
+ Layout: "layout",
28
+ OutputValidation: "output_validation",
29
+ Render: "render",
30
+ SourceValidation: "source_validation"
31
+ };
32
+ /**
33
+ * Sanitized failure from the Node rendering boundary.
34
+ */
35
+ var NodeRenderError = class extends Error {
36
+ code;
37
+ stage;
38
+ retryable;
39
+ rendererId;
40
+ constructor(code, message, options = {
41
+ stage: "render",
42
+ retryable: false
43
+ }) {
44
+ super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
45
+ this.name = "NodeRenderError";
46
+ this.code = code;
47
+ this.stage = options.stage;
48
+ this.retryable = options.retryable;
49
+ this.rendererId = options.rendererId;
50
+ }
51
+ };
52
+ //#endregion
53
+ //#region src/image-output.ts
54
+ const PNG_SIGNATURE = new Uint8Array([
55
+ 137,
56
+ 80,
57
+ 78,
58
+ 71,
59
+ 13,
60
+ 10,
61
+ 26,
62
+ 10
63
+ ]);
64
+ const PNG_WIDTH_OFFSET = 16;
65
+ const PNG_HEIGHT_OFFSET = 20;
66
+ const PNG_HEADER_BYTES = 24;
67
+ const PNG_IHDR = new Uint8Array([
68
+ 73,
69
+ 72,
70
+ 68,
71
+ 82
72
+ ]);
73
+ const PNG_IHDR_OFFSET = 12;
74
+ function deterministicPngFilename(prefix, source, optionsFingerprint) {
75
+ return `${prefix}-${createHash("sha256").update(prefix).update("\0").update(optionsFingerprint).update("\0").update(source).digest("hex").slice(0, 16)}.png`;
76
+ }
77
+ function assertSourceLimit(source, maxSourceChars) {
78
+ if (source.length > maxSourceChars) throw new NodeRenderError(NodeRenderErrorCode.SourceLimit, `Render source exceeds ${String(maxSourceChars)} characters`, {
79
+ stage: NodeRenderStage.SourceValidation,
80
+ retryable: false
81
+ });
82
+ }
83
+ function assertPngLimits(data, limits) {
84
+ if (data.byteLength > limits.maxOutputBytes) throw new NodeRenderError(NodeRenderErrorCode.OutputLimit, `Rendered image exceeds ${String(limits.maxOutputBytes)} bytes`, {
85
+ stage: NodeRenderStage.OutputValidation,
86
+ retryable: false
87
+ });
88
+ const dimensions = readPngDimensions(data);
89
+ if (dimensions === void 0) throw new NodeRenderError(NodeRenderErrorCode.RenderFailed, "Renderer returned invalid PNG data", {
90
+ stage: NodeRenderStage.OutputValidation,
91
+ retryable: false
92
+ });
93
+ if (dimensions.width > limits.maxWidth || dimensions.height > limits.maxHeight) throw new NodeRenderError(NodeRenderErrorCode.DimensionLimit, `Rendered image exceeds ${String(limits.maxWidth)}x${String(limits.maxHeight)} pixels`, {
94
+ stage: NodeRenderStage.Layout,
95
+ retryable: false
96
+ });
97
+ }
98
+ function readPngDimensions(data) {
99
+ if (data.byteLength < PNG_HEADER_BYTES) return;
100
+ for (const [index, byte] of PNG_SIGNATURE.entries()) if (data[index] !== byte) return;
101
+ for (const [index, byte] of PNG_IHDR.entries()) if (data[PNG_IHDR_OFFSET + index] !== byte) return;
102
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
103
+ const width = view.getUint32(PNG_WIDTH_OFFSET);
104
+ const height = view.getUint32(PNG_HEIGHT_OFFSET);
105
+ return width > 0 && height > 0 ? {
106
+ width,
107
+ height
108
+ } : void 0;
109
+ }
110
+ //#endregion
111
+ //#region src/node-options.ts
112
+ function snapshotNodeOptions(options, allowed, label) {
113
+ if (!isPlainNodeObject(options)) throw invalidNodeOptions(`${label} options must be a plain object`);
114
+ const allowedKeys = new Set(allowed);
115
+ const snapshot = {};
116
+ let keys;
117
+ try {
118
+ keys = Object.keys(options);
119
+ } catch (cause) {
120
+ throw invalidNodeOptions(`${label} options could not be inspected`, cause);
121
+ }
122
+ for (const key of keys) {
123
+ if (!allowedKeys.has(key)) throw invalidNodeOptions(`Unknown ${label} option: ${key}`);
124
+ try {
125
+ snapshot[key] = Reflect.get(options, key);
126
+ } catch (cause) {
127
+ throw invalidNodeOptions(`${label}.${key} could not be read`, cause);
128
+ }
129
+ }
130
+ return Object.freeze(snapshot);
131
+ }
132
+ function assertNodeMethod(collaborator, method, label) {
133
+ try {
134
+ if (typeof collaborator !== "object" && typeof collaborator !== "function" || collaborator === null || typeof Reflect.get(collaborator, method) !== "function") throw new TypeError(`${method} must be a function`);
135
+ } catch (cause) {
136
+ throw invalidNodeOptions(`${label} collaborator is invalid`, cause);
137
+ }
138
+ }
139
+ function invalidNodeOptions(message, cause) {
140
+ return new ConfigurationError(message, {
141
+ code: "invalid_node_configuration",
142
+ stage: "configuration",
143
+ cause
144
+ });
145
+ }
146
+ function isPlainNodeObject(value) {
147
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
148
+ let prototype;
149
+ try {
150
+ prototype = Object.getPrototypeOf(value);
151
+ } catch {
152
+ return false;
153
+ }
154
+ return prototype === Object.prototype || prototype === null;
155
+ }
156
+ //#endregion
157
+ //#region src/katex-renderer.ts
158
+ const DEFAULT_MAX_SOURCE_CHARS$1 = 2e4;
159
+ const DEFAULT_MAX_OUTPUT_BYTES$1 = 10 * 1024 * 1024;
160
+ const DEFAULT_MAX_WIDTH$1 = 4096;
161
+ const DEFAULT_MAX_HEIGHT$1 = 4096;
162
+ const DEFAULT_MAX_EXPAND = 1e3;
163
+ const DEFAULT_MAX_SIZE = 50;
164
+ const DEFAULT_FONT_SIZE_PX = 20;
165
+ const DEFAULT_PADDING_PX = 16;
166
+ const DEFAULT_DEVICE_SCALE_FACTOR$1 = 2;
167
+ const DEFAULT_BACKGROUND_COLOR$1 = "white";
168
+ const DEFAULT_VIEWPORT_WIDTH$1 = 1280;
169
+ const DEFAULT_VIEWPORT_HEIGHT$1 = 720;
170
+ const KATEX_LANGUAGES = /* @__PURE__ */ new Set([
171
+ "katex",
172
+ "latex",
173
+ "math",
174
+ "tex"
175
+ ]);
176
+ const KATEX_SHELL = "<!doctype html><html><head><meta charset=\"utf-8\"><meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; font-src data:; img-src 'none'; connect-src 'none'; worker-src 'none';\"></head><body><div id=\"math\"></div></body></html>";
177
+ const KATEX_LAYOUT_CSS = "html,body{margin:0;padding:0}#math{display:inline-block;box-sizing:border-box}.katex-display{margin:0}";
178
+ /**
179
+ * Renders display TeX with KaTeX, local fonts, and an isolated browser screenshot.
180
+ */
181
+ var KatexRenderer = class {
182
+ id;
183
+ output;
184
+ runtime;
185
+ assets;
186
+ backgroundColor;
187
+ deviceScaleFactor;
188
+ fontSizePx;
189
+ paddingPx;
190
+ maxExpand;
191
+ maxSize;
192
+ maxSourceChars;
193
+ imageLimits;
194
+ optionsFingerprint;
195
+ constructor(options) {
196
+ const validated = snapshotNodeOptions(options, [
197
+ "runtime",
198
+ "assets",
199
+ "backgroundColor",
200
+ "deviceScaleFactor",
201
+ "fontSizePx",
202
+ "paddingPx",
203
+ "maxExpand",
204
+ "maxSize",
205
+ "maxSourceChars",
206
+ "maxOutputBytes",
207
+ "maxWidth",
208
+ "maxHeight"
209
+ ], "KatexRenderer");
210
+ assertNodeMethod(validated.runtime, "run", "RenderRuntime");
211
+ assertNodeMethod(validated.assets, "loadScript", "KatexAssetLoader");
212
+ assertNodeMethod(validated.assets, "loadCss", "KatexAssetLoader");
213
+ this.id = "katex";
214
+ this.output = "bytes";
215
+ this.runtime = validated.runtime;
216
+ this.assets = validated.assets;
217
+ this.backgroundColor = validated.backgroundColor ?? DEFAULT_BACKGROUND_COLOR$1;
218
+ this.deviceScaleFactor = validated.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR$1;
219
+ this.fontSizePx = validated.fontSizePx ?? DEFAULT_FONT_SIZE_PX;
220
+ this.paddingPx = validated.paddingPx ?? DEFAULT_PADDING_PX;
221
+ this.maxExpand = validated.maxExpand ?? DEFAULT_MAX_EXPAND;
222
+ this.maxSize = validated.maxSize ?? DEFAULT_MAX_SIZE;
223
+ this.maxSourceChars = validated.maxSourceChars ?? DEFAULT_MAX_SOURCE_CHARS$1;
224
+ this.imageLimits = {
225
+ maxOutputBytes: validated.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES$1,
226
+ maxWidth: validated.maxWidth ?? DEFAULT_MAX_WIDTH$1,
227
+ maxHeight: validated.maxHeight ?? DEFAULT_MAX_HEIGHT$1
228
+ };
229
+ this.optionsFingerprint = [
230
+ this.backgroundColor,
231
+ String(this.deviceScaleFactor),
232
+ String(this.fontSizePx),
233
+ String(this.paddingPx),
234
+ String(this.maxExpand),
235
+ String(this.maxSize),
236
+ String(this.maxSourceChars),
237
+ String(this.imageLimits.maxOutputBytes),
238
+ String(this.imageLimits.maxWidth),
239
+ String(this.imageLimits.maxHeight)
240
+ ].join(":");
241
+ validateOptions$1({
242
+ backgroundColor: this.backgroundColor,
243
+ deviceScaleFactor: this.deviceScaleFactor,
244
+ fontSizePx: this.fontSizePx,
245
+ paddingPx: this.paddingPx,
246
+ maxExpand: this.maxExpand,
247
+ maxSize: this.maxSize,
248
+ maxSourceChars: this.maxSourceChars,
249
+ imageLimits: this.imageLimits
250
+ });
251
+ }
252
+ canRender(request) {
253
+ return KATEX_LANGUAGES.has(request.lang.trim().toLowerCase());
254
+ }
255
+ async render(request, context) {
256
+ if (!this.canRender(request)) return null;
257
+ assertSourceLimit(request.source, this.maxSourceChars);
258
+ let data;
259
+ try {
260
+ data = await this.runtime.run(async (context) => {
261
+ const script = await this.assets.loadScript();
262
+ const css = await this.assets.loadCss();
263
+ const page = await context.newPage();
264
+ await page.setViewport({
265
+ width: DEFAULT_VIEWPORT_WIDTH$1,
266
+ height: DEFAULT_VIEWPORT_HEIGHT$1,
267
+ deviceScaleFactor: this.deviceScaleFactor
268
+ });
269
+ await page.setContent(KATEX_SHELL, { waitUntil: "domcontentloaded" });
270
+ await page.addStyleTag({ content: `${css}\n${KATEX_LAYOUT_CSS}` });
271
+ await page.addScriptTag({ content: script });
272
+ await page.$eval("body", (body, backgroundColor) => {
273
+ body.style.background = backgroundColor;
274
+ }, this.backgroundColor);
275
+ await page.$eval("#math", (container, values) => {
276
+ const katex = globalThis.katex;
277
+ if (katex === void 0) throw new Error("KaTeX browser harness failed to initialize");
278
+ container.innerHTML = katex.renderToString(values.source, {
279
+ displayMode: true,
280
+ globalGroup: false,
281
+ maxExpand: values.maxExpand,
282
+ maxSize: values.maxSize,
283
+ output: "html",
284
+ strict: "ignore",
285
+ throwOnError: true,
286
+ trust: false
287
+ });
288
+ container.setAttribute("style", `font-size:${String(values.fontSizePx)}px;padding:${String(values.paddingPx)}px`);
289
+ }, {
290
+ source: request.source,
291
+ maxExpand: this.maxExpand,
292
+ maxSize: this.maxSize,
293
+ fontSizePx: this.fontSizePx,
294
+ paddingPx: this.paddingPx
295
+ });
296
+ await page.evaluate(async () => {
297
+ await document.fonts.ready;
298
+ });
299
+ const element = await page.$("#math");
300
+ if (element === null) throw new Error("KaTeX screenshot element is missing");
301
+ const box = await element.boundingBox();
302
+ if (box === null) throw new Error("KaTeX screenshot element has no dimensions");
303
+ const pixelWidth = Math.ceil(box.width * this.deviceScaleFactor);
304
+ const pixelHeight = Math.ceil(box.height * this.deviceScaleFactor);
305
+ if (pixelWidth > this.imageLimits.maxWidth || pixelHeight > this.imageLimits.maxHeight) throw new NodeRenderError(NodeRenderErrorCode.DimensionLimit, `Rendered image exceeds ${String(this.imageLimits.maxWidth)}x${String(this.imageLimits.maxHeight)} pixels`, {
306
+ stage: NodeRenderStage.Layout,
307
+ retryable: false
308
+ });
309
+ return element.screenshot({
310
+ type: "png",
311
+ omitBackground: this.backgroundColor === "transparent"
312
+ });
313
+ }, context);
314
+ } catch (error) {
315
+ if (isOperationError(error)) throw error;
316
+ if (error instanceof NodeRenderError) {
317
+ const sanitizedRenderFailure = error.code === NodeRenderErrorCode.RenderFailed && error.cause === void 0;
318
+ if (error.code !== NodeRenderErrorCode.RenderFailed || sanitizedRenderFailure) throw error;
319
+ }
320
+ throw new NodeRenderError(NodeRenderErrorCode.RenderFailed, "KaTeX rendering failed", {
321
+ stage: "render",
322
+ retryable: false
323
+ });
324
+ }
325
+ assertPngLimits(data, this.imageLimits);
326
+ return {
327
+ kind: "bytes",
328
+ data,
329
+ mimeType: "image/png",
330
+ filename: deterministicPngFilename("math", request.source, this.optionsFingerprint),
331
+ altText: request.altText
332
+ };
333
+ }
334
+ };
335
+ function validateOptions$1(renderer) {
336
+ if (typeof renderer.backgroundColor !== "string" || renderer.backgroundColor.length === 0) throw invalidNodeOptions("backgroundColor must be a nonempty string");
337
+ validatePositiveFinite$1(renderer.deviceScaleFactor, "deviceScaleFactor");
338
+ validatePositiveFinite$1(renderer.fontSizePx, "fontSizePx");
339
+ validateNonNegativeFinite(renderer.paddingPx, "paddingPx");
340
+ validatePositiveInteger$1(renderer.maxExpand, "maxExpand");
341
+ validatePositiveFinite$1(renderer.maxSize, "maxSize");
342
+ validatePositiveInteger$1(renderer.maxSourceChars, "maxSourceChars");
343
+ validatePositiveInteger$1(renderer.imageLimits.maxOutputBytes, "maxOutputBytes");
344
+ validatePositiveInteger$1(renderer.imageLimits.maxWidth, "maxWidth");
345
+ validatePositiveInteger$1(renderer.imageLimits.maxHeight, "maxHeight");
346
+ }
347
+ function validatePositiveInteger$1(value, name) {
348
+ if (!Number.isInteger(value) || value <= 0) throw invalidNodeOptions(`${name} must be a positive integer`);
349
+ }
350
+ function validatePositiveFinite$1(value, name) {
351
+ if (!Number.isFinite(value) || value <= 0) throw invalidNodeOptions(`${name} must be positive`);
352
+ }
353
+ function validateNonNegativeFinite(value, name) {
354
+ if (!Number.isFinite(value) || value < 0) throw invalidNodeOptions(`${name} must be non-negative`);
355
+ }
356
+ //#endregion
357
+ //#region src/mermaid-renderer.ts
358
+ const DEFAULT_MAX_SOURCE_CHARS = 5e4;
359
+ const DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
360
+ const DEFAULT_MAX_WIDTH = 4096;
361
+ const DEFAULT_MAX_HEIGHT = 4096;
362
+ const DEFAULT_MAX_EDGES = 500;
363
+ const DEFAULT_DEVICE_SCALE_FACTOR = 2;
364
+ const DEFAULT_BACKGROUND_COLOR = "white";
365
+ const DEFAULT_VIEWPORT_WIDTH = 1280;
366
+ const DEFAULT_VIEWPORT_HEIGHT = 720;
367
+ const MERMAID_SHELL = "<!doctype html><html><head><meta charset=\"utf-8\"><meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src 'none'; font-src 'none'; connect-src 'none'; worker-src 'none'; object-src 'none'; frame-src 'none';\"></head><body><div id=\"diagram\"></div></body></html>";
368
+ const SECURE_MERMAID_KEYS = [
369
+ "secure",
370
+ "securityLevel",
371
+ "startOnLoad",
372
+ "maxTextSize",
373
+ "suppressErrorRendering",
374
+ "maxEdges",
375
+ "deterministicIds",
376
+ "deterministicIDSeed",
377
+ "layout"
378
+ ];
379
+ const MERMAID_THEMES = /* @__PURE__ */ new Set([
380
+ "base",
381
+ "dark",
382
+ "default",
383
+ "forest",
384
+ "neutral"
385
+ ]);
386
+ var MermaidRenderer = class {
387
+ id;
388
+ output;
389
+ runtime;
390
+ assets;
391
+ theme;
392
+ backgroundColor;
393
+ deviceScaleFactor;
394
+ maxSourceChars;
395
+ maxEdges;
396
+ imageLimits;
397
+ optionsFingerprint;
398
+ constructor(options) {
399
+ const validated = snapshotNodeOptions(options, [
400
+ "runtime",
401
+ "assets",
402
+ "theme",
403
+ "backgroundColor",
404
+ "deviceScaleFactor",
405
+ "maxSourceChars",
406
+ "maxEdges",
407
+ "maxOutputBytes",
408
+ "maxWidth",
409
+ "maxHeight"
410
+ ], "MermaidRenderer");
411
+ assertNodeMethod(validated.runtime, "run", "RenderRuntime");
412
+ assertNodeMethod(validated.assets, "loadScript", "MermaidAssetLoader");
413
+ this.id = "mermaid";
414
+ this.output = "bytes";
415
+ this.runtime = validated.runtime;
416
+ this.assets = validated.assets;
417
+ this.theme = validated.theme ?? "default";
418
+ this.backgroundColor = validated.backgroundColor ?? DEFAULT_BACKGROUND_COLOR;
419
+ this.deviceScaleFactor = validated.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR;
420
+ this.maxSourceChars = validated.maxSourceChars ?? DEFAULT_MAX_SOURCE_CHARS;
421
+ this.maxEdges = validated.maxEdges ?? DEFAULT_MAX_EDGES;
422
+ this.imageLimits = {
423
+ maxOutputBytes: validated.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
424
+ maxWidth: validated.maxWidth ?? DEFAULT_MAX_WIDTH,
425
+ maxHeight: validated.maxHeight ?? DEFAULT_MAX_HEIGHT
426
+ };
427
+ this.optionsFingerprint = [
428
+ this.theme,
429
+ this.backgroundColor,
430
+ String(this.deviceScaleFactor),
431
+ String(this.maxSourceChars),
432
+ String(this.maxEdges),
433
+ String(this.imageLimits.maxOutputBytes),
434
+ String(this.imageLimits.maxWidth),
435
+ String(this.imageLimits.maxHeight)
436
+ ].join(":");
437
+ validateOptions({
438
+ theme: this.theme,
439
+ backgroundColor: this.backgroundColor,
440
+ deviceScaleFactor: this.deviceScaleFactor,
441
+ maxSourceChars: this.maxSourceChars,
442
+ maxEdges: this.maxEdges,
443
+ imageLimits: this.imageLimits
444
+ });
445
+ }
446
+ canRender(request) {
447
+ return request.lang.trim().toLowerCase() === "mermaid";
448
+ }
449
+ async render(request, context) {
450
+ if (!this.canRender(request)) return null;
451
+ assertSourceLimit(request.source, this.maxSourceChars);
452
+ let data;
453
+ try {
454
+ data = await this.runtime.run(async (context) => {
455
+ const script = await this.assets.loadScript();
456
+ const page = await context.newPage();
457
+ await page.setRequestInterception(true);
458
+ page.on("request", blockRequest);
459
+ await page.setViewport({
460
+ width: DEFAULT_VIEWPORT_WIDTH,
461
+ height: DEFAULT_VIEWPORT_HEIGHT,
462
+ deviceScaleFactor: this.deviceScaleFactor
463
+ });
464
+ await page.setContent(MERMAID_SHELL, { waitUntil: "domcontentloaded" });
465
+ await page.addScriptTag({ content: script });
466
+ await page.evaluate(async (input) => {
467
+ const mermaid = globalThis.mermaid;
468
+ const container = document.querySelector("#diagram");
469
+ if (mermaid === void 0 || container === null) throw new Error("Mermaid browser harness failed to initialize");
470
+ mermaid.initialize({
471
+ deterministicIds: true,
472
+ deterministicIDSeed: "slackmark-node",
473
+ layout: "dagre",
474
+ logLevel: "fatal",
475
+ maxEdges: input.maxEdges,
476
+ maxTextSize: input.maxSourceChars,
477
+ secure: input.secureKeys,
478
+ securityLevel: "strict",
479
+ startOnLoad: false,
480
+ suppressErrorRendering: true,
481
+ theme: input.theme
482
+ });
483
+ await mermaid.parse(input.source, { suppressErrors: false });
484
+ container.innerHTML = (await mermaid.render("slackmark-diagram", input.source, container)).svg;
485
+ document.body.style.background = input.backgroundColor;
486
+ }, {
487
+ source: request.source,
488
+ theme: this.theme,
489
+ backgroundColor: this.backgroundColor,
490
+ maxSourceChars: this.maxSourceChars,
491
+ maxEdges: this.maxEdges,
492
+ secureKeys: SECURE_MERMAID_KEYS
493
+ });
494
+ const element = await page.$("#diagram svg");
495
+ if (element === null) throw new Error("Mermaid produced no SVG");
496
+ const box = await element.boundingBox();
497
+ if (box === null) throw new Error("Mermaid SVG has no dimensions");
498
+ const pixelWidth = Math.ceil(box.width * this.deviceScaleFactor);
499
+ const pixelHeight = Math.ceil(box.height * this.deviceScaleFactor);
500
+ if (pixelWidth > this.imageLimits.maxWidth || pixelHeight > this.imageLimits.maxHeight) throw new NodeRenderError(NodeRenderErrorCode.DimensionLimit, "Rendered Mermaid image exceeds configured dimensions", {
501
+ stage: "layout",
502
+ retryable: false
503
+ });
504
+ return element.screenshot({
505
+ type: "png",
506
+ omitBackground: this.backgroundColor === "transparent"
507
+ });
508
+ }, context);
509
+ } catch (error) {
510
+ if (isOperationError(error)) throw error;
511
+ if (error instanceof NodeRenderError) {
512
+ if (error.code !== NodeRenderErrorCode.RenderFailed) throw error;
513
+ }
514
+ throw new NodeRenderError(NodeRenderErrorCode.RenderFailed, "Mermaid rendering failed", {
515
+ stage: "render",
516
+ retryable: false
517
+ });
518
+ }
519
+ assertPngLimits(data, this.imageLimits);
520
+ return {
521
+ kind: "bytes",
522
+ data,
523
+ mimeType: "image/png",
524
+ filename: deterministicPngFilename("mermaid", request.source, this.optionsFingerprint),
525
+ altText: request.altText
526
+ };
527
+ }
528
+ };
529
+ function blockRequest(request) {
530
+ request.abort("blockedbyclient");
531
+ }
532
+ function validateOptions(renderer) {
533
+ if (typeof renderer.theme !== "string" || !MERMAID_THEMES.has(renderer.theme)) throw invalidNodeOptions("theme must be base, dark, default, forest, or neutral");
534
+ if (typeof renderer.backgroundColor !== "string" || renderer.backgroundColor.length === 0) throw invalidNodeOptions("backgroundColor must be a nonempty string");
535
+ validatePositiveFinite(renderer.deviceScaleFactor, "deviceScaleFactor");
536
+ validatePositiveInteger(renderer.maxSourceChars, "maxSourceChars");
537
+ validatePositiveInteger(renderer.maxEdges, "maxEdges");
538
+ validatePositiveInteger(renderer.imageLimits.maxOutputBytes, "maxOutputBytes");
539
+ validatePositiveInteger(renderer.imageLimits.maxWidth, "maxWidth");
540
+ validatePositiveInteger(renderer.imageLimits.maxHeight, "maxHeight");
541
+ }
542
+ function validatePositiveInteger(value, name) {
543
+ if (!Number.isInteger(value) || value <= 0) throw invalidNodeOptions(`${name} must be a positive integer`);
544
+ }
545
+ function validatePositiveFinite(value, name) {
546
+ if (!Number.isFinite(value) || value <= 0) throw invalidNodeOptions(`${name} must be positive`);
547
+ }
548
+ //#endregion
549
+ export { KatexRenderer, MermaidRenderer };
550
+
551
+ //# sourceMappingURL=renderers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderers.js","names":["DEFAULT_MAX_SOURCE_CHARS","DEFAULT_MAX_OUTPUT_BYTES","DEFAULT_MAX_WIDTH","DEFAULT_MAX_HEIGHT","DEFAULT_DEVICE_SCALE_FACTOR","DEFAULT_BACKGROUND_COLOR","DEFAULT_VIEWPORT_WIDTH","DEFAULT_VIEWPORT_HEIGHT","browserGlobal","validateOptions","validatePositiveInteger","validatePositiveFinite","browserGlobal"],"sources":["../src/errors.ts","../src/image-output.ts","../src/node-options.ts","../src/katex-renderer.ts","../src/mermaid-renderer.ts"],"sourcesContent":["import {\n isOperationError,\n type OperationError,\n type StructuredRendererError,\n type UploadReceipt,\n} from \"slackmark\";\n\nimport type { NodeConverterStatus } from \"./status.js\";\n\nexport const NodeRenderErrorCode = {\n AssetMissing: \"asset_missing\",\n BrowserBinaryMissing: \"browser_binary_missing\",\n BrowserDisconnected: \"browser_disconnected\",\n BrowserFailed: \"browser_failed\",\n CleanupFailed: \"cleanup_failed\",\n CloseFailed: \"close_failed\",\n ContextFailed: \"context_failed\",\n DimensionLimit: \"dimension_limit\",\n OutputLimit: \"output_limit\",\n QueueFull: \"queue_full\",\n RenderFailed: \"render_failed\",\n SourceLimit: \"source_limit\",\n} as const;\n\nexport type NodeRenderErrorCode = (typeof NodeRenderErrorCode)[keyof typeof NodeRenderErrorCode];\n\nexport const NodeRenderStage = {\n Admission: \"admission\",\n AssetLoad: \"asset_load\",\n Browser: \"browser\",\n BrowserCleanup: \"browser_cleanup\",\n BrowserClose: \"browser_close\",\n BrowserLaunch: \"browser_launch\",\n ContextCleanup: \"context_cleanup\",\n ContextCreate: \"context_create\",\n Layout: \"layout\",\n OutputValidation: \"output_validation\",\n Render: \"render\",\n SourceValidation: \"source_validation\",\n} as const;\n\nexport type NodeRenderStage = (typeof NodeRenderStage)[keyof typeof NodeRenderStage];\n\nexport interface NodeRenderErrorOptions {\n readonly stage: NodeRenderStage;\n readonly retryable: boolean;\n readonly rendererId?: string | undefined;\n readonly cause?: unknown;\n}\n\nexport class NodeShutdownTimeoutError extends Error {\n override readonly name: string;\n readonly code: \"shutdown_timeout\";\n readonly stage: \"close\";\n readonly retryable: true;\n readonly status: NodeConverterStatus;\n readonly receipts: ReadonlyArray<UploadReceipt>;\n readonly settledReceipts: Promise<ReadonlyArray<UploadReceipt>>;\n\n constructor(\n status: NodeConverterStatus,\n receipts: ReadonlyArray<UploadReceipt>,\n settledReceipts: Promise<ReadonlyArray<UploadReceipt>>,\n ) {\n super(\"Node converter shutdown timed out\");\n this.name = \"NodeShutdownTimeoutError\";\n this.code = \"shutdown_timeout\";\n this.stage = \"close\";\n this.retryable = true;\n this.status = status;\n this.receipts = Object.freeze([...receipts]);\n this.settledReceipts = settledReceipts;\n }\n}\n\n/**\n * Sanitized failure from the Node rendering boundary.\n */\nexport class NodeRenderError extends Error implements StructuredRendererError {\n readonly code: NodeRenderErrorCode;\n readonly stage: NodeRenderStage;\n readonly retryable: boolean;\n readonly rendererId: string | undefined;\n\n constructor(\n code: NodeRenderErrorCode,\n message: string,\n options: NodeRenderErrorOptions = {\n stage: \"render\",\n retryable: false,\n },\n ) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = \"NodeRenderError\";\n this.code = code;\n this.stage = options.stage;\n this.retryable = options.retryable;\n this.rendererId = options.rendererId;\n }\n}\n\nexport function normalizeNodeRenderError(\n error: unknown,\n code: NodeRenderErrorCode,\n message: string,\n stage: NodeRenderStage = NodeRenderStage.Render,\n retryable = false,\n): NodeRenderError | OperationError {\n return error instanceof NodeRenderError || isOperationError(error)\n ? error\n : new NodeRenderError(code, message, { stage, retryable, cause: error });\n}\n","import { createHash } from \"node:crypto\";\n\nimport { NodeRenderError, NodeRenderErrorCode, NodeRenderStage } from \"./errors.js\";\n\nconst PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);\nconst PNG_WIDTH_OFFSET = 16;\nconst PNG_HEIGHT_OFFSET = 20;\nconst PNG_HEADER_BYTES = 24;\nconst PNG_IHDR = new Uint8Array([73, 72, 68, 82]);\nconst PNG_IHDR_OFFSET = 12;\n\nexport interface ImageLimits {\n readonly maxOutputBytes: number;\n readonly maxWidth: number;\n readonly maxHeight: number;\n}\n\nexport function deterministicPngFilename(\n prefix: string,\n source: string,\n optionsFingerprint: string,\n): string {\n const digest = createHash(\"sha256\")\n .update(prefix)\n .update(\"\\0\")\n .update(optionsFingerprint)\n .update(\"\\0\")\n .update(source)\n .digest(\"hex\")\n .slice(0, 16);\n return `${prefix}-${digest}.png`;\n}\n\nexport function assertSourceLimit(source: string, maxSourceChars: number): void {\n if (source.length > maxSourceChars) {\n throw new NodeRenderError(\n NodeRenderErrorCode.SourceLimit,\n `Render source exceeds ${String(maxSourceChars)} characters`,\n { stage: NodeRenderStage.SourceValidation, retryable: false },\n );\n }\n}\n\nexport function assertPngLimits(data: Uint8Array, limits: ImageLimits): void {\n if (data.byteLength > limits.maxOutputBytes) {\n throw new NodeRenderError(\n NodeRenderErrorCode.OutputLimit,\n `Rendered image exceeds ${String(limits.maxOutputBytes)} bytes`,\n { stage: NodeRenderStage.OutputValidation, retryable: false },\n );\n }\n const dimensions = readPngDimensions(data);\n if (dimensions === undefined) {\n throw new NodeRenderError(\n NodeRenderErrorCode.RenderFailed,\n \"Renderer returned invalid PNG data\",\n { stage: NodeRenderStage.OutputValidation, retryable: false },\n );\n }\n if (dimensions.width > limits.maxWidth || dimensions.height > limits.maxHeight) {\n throw new NodeRenderError(\n NodeRenderErrorCode.DimensionLimit,\n `Rendered image exceeds ${String(limits.maxWidth)}x${String(limits.maxHeight)} pixels`,\n { stage: NodeRenderStage.Layout, retryable: false },\n );\n }\n}\n\nfunction readPngDimensions(\n data: Uint8Array,\n): { readonly width: number; readonly height: number } | undefined {\n if (data.byteLength < PNG_HEADER_BYTES) {\n return undefined;\n }\n for (const [index, byte] of PNG_SIGNATURE.entries()) {\n if (data[index] !== byte) {\n return undefined;\n }\n }\n for (const [index, byte] of PNG_IHDR.entries()) {\n if (data[PNG_IHDR_OFFSET + index] !== byte) {\n return undefined;\n }\n }\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n const width = view.getUint32(PNG_WIDTH_OFFSET);\n const height = view.getUint32(PNG_HEIGHT_OFFSET);\n return width > 0 && height > 0 ? { width, height } : undefined;\n}\n","import { ConfigurationError } from \"slackmark\";\n\nexport function snapshotNodeOptions<T extends object>(\n options: T,\n allowed: ReadonlyArray<string>,\n label: string,\n): T {\n if (!isPlainNodeObject(options)) {\n throw invalidNodeOptions(`${label} options must be a plain object`);\n }\n const allowedKeys = new Set(allowed);\n const snapshot: Record<string, unknown> = {};\n let keys: ReadonlyArray<string>;\n try {\n keys = Object.keys(options);\n } catch (cause) {\n throw invalidNodeOptions(`${label} options could not be inspected`, cause);\n }\n for (const key of keys) {\n if (!allowedKeys.has(key)) {\n throw invalidNodeOptions(`Unknown ${label} option: ${key}`);\n }\n try {\n snapshot[key] = Reflect.get(options, key);\n } catch (cause) {\n throw invalidNodeOptions(`${label}.${key} could not be read`, cause);\n }\n }\n return Object.freeze(snapshot) as T;\n}\n\nexport function snapshotOpenNodeOptions<T extends object>(options: T, label: string): T {\n let keys: ReadonlyArray<string>;\n try {\n keys = Object.keys(options);\n } catch (cause) {\n throw invalidNodeOptions(`${label} could not be inspected`, cause);\n }\n return snapshotNodeOptions(options, keys, label);\n}\n\nexport function assertNodeMethod(collaborator: unknown, method: string, label: string): void {\n try {\n if (\n (typeof collaborator !== \"object\" && typeof collaborator !== \"function\") ||\n collaborator === null ||\n typeof Reflect.get(collaborator, method) !== \"function\"\n ) {\n throw new TypeError(`${method} must be a function`);\n }\n } catch (cause) {\n throw invalidNodeOptions(`${label} collaborator is invalid`, cause);\n }\n}\n\nexport function invalidNodeOptions(message: string, cause?: unknown): ConfigurationError {\n return new ConfigurationError(message, {\n code: \"invalid_node_configuration\",\n stage: \"configuration\",\n cause,\n });\n}\n\nfunction isPlainNodeObject(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n let prototype: object | null;\n try {\n prototype = Object.getPrototypeOf(value);\n } catch {\n return false;\n }\n return prototype === Object.prototype || prototype === null;\n}\n","import {\n isOperationError,\n type BytesImage,\n type BytesImageRenderer,\n type OperationContext,\n type RenderRequest,\n} from \"slackmark\";\n\nimport { NodeRenderError, NodeRenderErrorCode, NodeRenderStage } from \"./errors.js\";\nimport { assertPngLimits, assertSourceLimit, deterministicPngFilename } from \"./image-output.js\";\nimport type { ImageLimits } from \"./image-output.js\";\nimport type { KatexAssetLoader } from \"./katex-assets.js\";\nimport { assertNodeMethod, invalidNodeOptions, snapshotNodeOptions } from \"./node-options.js\";\nimport type { RenderRuntime } from \"./puppeteer-render-runtime.js\";\n\nconst DEFAULT_MAX_SOURCE_CHARS = 20_000;\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;\nconst DEFAULT_MAX_WIDTH = 4096;\nconst DEFAULT_MAX_HEIGHT = 4096;\nconst DEFAULT_MAX_EXPAND = 1000;\nconst DEFAULT_MAX_SIZE = 50;\nconst DEFAULT_FONT_SIZE_PX = 20;\nconst DEFAULT_PADDING_PX = 16;\nconst DEFAULT_DEVICE_SCALE_FACTOR = 2;\nconst DEFAULT_BACKGROUND_COLOR = \"white\";\nconst DEFAULT_VIEWPORT_WIDTH = 1280;\nconst DEFAULT_VIEWPORT_HEIGHT = 720;\nconst KATEX_LANGUAGES: ReadonlySet<string> = new Set([\"katex\", \"latex\", \"math\", \"tex\"]);\nconst KATEX_SHELL =\n \"<!doctype html><html><head><meta charset=\\\"utf-8\\\"><meta http-equiv=\\\"Content-Security-Policy\\\" content=\\\"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; font-src data:; img-src 'none'; connect-src 'none'; worker-src 'none';\\\"></head><body><div id=\\\"math\\\"></div></body></html>\";\nconst KATEX_LAYOUT_CSS =\n \"html,body{margin:0;padding:0}#math{display:inline-block;box-sizing:border-box}.katex-display{margin:0}\";\n\nexport interface KatexRendererOptions {\n readonly runtime: RenderRuntime;\n readonly assets: KatexAssetLoader;\n readonly backgroundColor?: string | undefined;\n readonly deviceScaleFactor?: number | undefined;\n readonly fontSizePx?: number | undefined;\n readonly paddingPx?: number | undefined;\n readonly maxExpand?: number | undefined;\n readonly maxSize?: number | undefined;\n readonly maxSourceChars?: number | undefined;\n readonly maxOutputBytes?: number | undefined;\n readonly maxWidth?: number | undefined;\n readonly maxHeight?: number | undefined;\n}\n\n/**\n * Renders display TeX with KaTeX, local fonts, and an isolated browser screenshot.\n */\nexport class KatexRenderer implements BytesImageRenderer {\n readonly id: string;\n readonly output: \"bytes\";\n private readonly runtime: RenderRuntime;\n private readonly assets: KatexAssetLoader;\n private readonly backgroundColor: string;\n private readonly deviceScaleFactor: number;\n private readonly fontSizePx: number;\n private readonly paddingPx: number;\n private readonly maxExpand: number;\n private readonly maxSize: number;\n private readonly maxSourceChars: number;\n private readonly imageLimits: ImageLimits;\n private readonly optionsFingerprint: string;\n\n constructor(options: KatexRendererOptions) {\n const validated = snapshotNodeOptions(\n options,\n [\n \"runtime\",\n \"assets\",\n \"backgroundColor\",\n \"deviceScaleFactor\",\n \"fontSizePx\",\n \"paddingPx\",\n \"maxExpand\",\n \"maxSize\",\n \"maxSourceChars\",\n \"maxOutputBytes\",\n \"maxWidth\",\n \"maxHeight\",\n ],\n \"KatexRenderer\",\n );\n assertNodeMethod(validated.runtime, \"run\", \"RenderRuntime\");\n assertNodeMethod(validated.assets, \"loadScript\", \"KatexAssetLoader\");\n assertNodeMethod(validated.assets, \"loadCss\", \"KatexAssetLoader\");\n this.id = \"katex\";\n this.output = \"bytes\";\n this.runtime = validated.runtime;\n this.assets = validated.assets;\n this.backgroundColor = validated.backgroundColor ?? DEFAULT_BACKGROUND_COLOR;\n this.deviceScaleFactor = validated.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR;\n this.fontSizePx = validated.fontSizePx ?? DEFAULT_FONT_SIZE_PX;\n this.paddingPx = validated.paddingPx ?? DEFAULT_PADDING_PX;\n this.maxExpand = validated.maxExpand ?? DEFAULT_MAX_EXPAND;\n this.maxSize = validated.maxSize ?? DEFAULT_MAX_SIZE;\n this.maxSourceChars = validated.maxSourceChars ?? DEFAULT_MAX_SOURCE_CHARS;\n this.imageLimits = {\n maxOutputBytes: validated.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,\n maxWidth: validated.maxWidth ?? DEFAULT_MAX_WIDTH,\n maxHeight: validated.maxHeight ?? DEFAULT_MAX_HEIGHT,\n };\n this.optionsFingerprint = [\n this.backgroundColor,\n String(this.deviceScaleFactor),\n String(this.fontSizePx),\n String(this.paddingPx),\n String(this.maxExpand),\n String(this.maxSize),\n String(this.maxSourceChars),\n String(this.imageLimits.maxOutputBytes),\n String(this.imageLimits.maxWidth),\n String(this.imageLimits.maxHeight),\n ].join(\":\");\n\n validateOptions({\n backgroundColor: this.backgroundColor,\n deviceScaleFactor: this.deviceScaleFactor,\n fontSizePx: this.fontSizePx,\n paddingPx: this.paddingPx,\n maxExpand: this.maxExpand,\n maxSize: this.maxSize,\n maxSourceChars: this.maxSourceChars,\n imageLimits: this.imageLimits,\n });\n }\n\n canRender(request: RenderRequest): boolean {\n return KATEX_LANGUAGES.has(request.lang.trim().toLowerCase());\n }\n\n async render(request: RenderRequest, context: OperationContext): Promise<BytesImage | null> {\n if (!this.canRender(request)) {\n return null;\n }\n assertSourceLimit(request.source, this.maxSourceChars);\n\n let data: Uint8Array;\n try {\n data = await this.runtime.run(async (context) => {\n const script = await this.assets.loadScript();\n const css = await this.assets.loadCss();\n const page = await context.newPage();\n await page.setViewport({\n width: DEFAULT_VIEWPORT_WIDTH,\n height: DEFAULT_VIEWPORT_HEIGHT,\n deviceScaleFactor: this.deviceScaleFactor,\n });\n await page.setContent(KATEX_SHELL, { waitUntil: \"domcontentloaded\" });\n await page.addStyleTag({ content: `${css}\\n${KATEX_LAYOUT_CSS}` });\n await page.addScriptTag({ content: script });\n await page.$eval(\n \"body\",\n (body, backgroundColor) => {\n body.style.background = backgroundColor;\n },\n this.backgroundColor,\n );\n await page.$eval(\n \"#math\",\n (container, values) => {\n const browserGlobal = globalThis as typeof globalThis & {\n katex?: {\n renderToString(source: string, options: Readonly<Record<string, unknown>>): string;\n };\n };\n const katex = browserGlobal.katex;\n if (katex === undefined) {\n throw new Error(\"KaTeX browser harness failed to initialize\");\n }\n container.innerHTML = katex.renderToString(values.source, {\n displayMode: true,\n globalGroup: false,\n maxExpand: values.maxExpand,\n maxSize: values.maxSize,\n output: \"html\",\n strict: \"ignore\",\n throwOnError: true,\n trust: false,\n });\n container.setAttribute(\n \"style\",\n `font-size:${String(values.fontSizePx)}px;padding:${String(values.paddingPx)}px`,\n );\n },\n {\n source: request.source,\n maxExpand: this.maxExpand,\n maxSize: this.maxSize,\n fontSizePx: this.fontSizePx,\n paddingPx: this.paddingPx,\n },\n );\n await page.evaluate(async () => {\n await document.fonts.ready;\n });\n\n const element = await page.$(\"#math\");\n if (element === null) {\n throw new Error(\"KaTeX screenshot element is missing\");\n }\n const box = await element.boundingBox();\n if (box === null) {\n throw new Error(\"KaTeX screenshot element has no dimensions\");\n }\n const pixelWidth = Math.ceil(box.width * this.deviceScaleFactor);\n const pixelHeight = Math.ceil(box.height * this.deviceScaleFactor);\n if (pixelWidth > this.imageLimits.maxWidth || pixelHeight > this.imageLimits.maxHeight) {\n throw new NodeRenderError(\n NodeRenderErrorCode.DimensionLimit,\n `Rendered image exceeds ${String(this.imageLimits.maxWidth)}x${String(this.imageLimits.maxHeight)} pixels`,\n { stage: NodeRenderStage.Layout, retryable: false },\n );\n }\n return element.screenshot({\n type: \"png\",\n omitBackground: this.backgroundColor === \"transparent\",\n });\n }, context);\n } catch (error) {\n if (isOperationError(error)) {\n throw error;\n }\n if (error instanceof NodeRenderError) {\n const sanitizedRenderFailure =\n error.code === NodeRenderErrorCode.RenderFailed && error.cause === undefined;\n if (error.code !== NodeRenderErrorCode.RenderFailed || sanitizedRenderFailure) {\n throw error;\n }\n }\n throw new NodeRenderError(NodeRenderErrorCode.RenderFailed, \"KaTeX rendering failed\", {\n stage: \"render\",\n retryable: false,\n });\n }\n assertPngLimits(data, this.imageLimits);\n\n return {\n kind: \"bytes\",\n data,\n mimeType: \"image/png\",\n filename: deterministicPngFilename(\"math\", request.source, this.optionsFingerprint),\n altText: request.altText,\n };\n }\n}\n\nfunction validateOptions(renderer: {\n readonly backgroundColor: string;\n readonly deviceScaleFactor: number;\n readonly fontSizePx: number;\n readonly paddingPx: number;\n readonly maxExpand: number;\n readonly maxSize: number;\n readonly maxSourceChars: number;\n readonly imageLimits: ImageLimits;\n}): void {\n if (typeof renderer.backgroundColor !== \"string\" || renderer.backgroundColor.length === 0) {\n throw invalidNodeOptions(\"backgroundColor must be a nonempty string\");\n }\n validatePositiveFinite(renderer.deviceScaleFactor, \"deviceScaleFactor\");\n validatePositiveFinite(renderer.fontSizePx, \"fontSizePx\");\n validateNonNegativeFinite(renderer.paddingPx, \"paddingPx\");\n validatePositiveInteger(renderer.maxExpand, \"maxExpand\");\n validatePositiveFinite(renderer.maxSize, \"maxSize\");\n validatePositiveInteger(renderer.maxSourceChars, \"maxSourceChars\");\n validatePositiveInteger(renderer.imageLimits.maxOutputBytes, \"maxOutputBytes\");\n validatePositiveInteger(renderer.imageLimits.maxWidth, \"maxWidth\");\n validatePositiveInteger(renderer.imageLimits.maxHeight, \"maxHeight\");\n}\n\nfunction validatePositiveInteger(value: number, name: string): void {\n if (!Number.isInteger(value) || value <= 0) {\n throw invalidNodeOptions(`${name} must be a positive integer`);\n }\n}\n\nfunction validatePositiveFinite(value: number, name: string): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw invalidNodeOptions(`${name} must be positive`);\n }\n}\n\nfunction validateNonNegativeFinite(value: number, name: string): void {\n if (!Number.isFinite(value) || value < 0) {\n throw invalidNodeOptions(`${name} must be non-negative`);\n }\n}\n","import type { HTTPRequest } from \"puppeteer\";\nimport {\n isOperationError,\n type BytesImage,\n type BytesImageRenderer,\n type OperationContext,\n type RenderRequest,\n} from \"slackmark\";\n\nimport { NodeRenderError, NodeRenderErrorCode } from \"./errors.js\";\nimport { assertPngLimits, assertSourceLimit, deterministicPngFilename } from \"./image-output.js\";\nimport type { ImageLimits } from \"./image-output.js\";\nimport type { MermaidAssetLoader } from \"./mermaid-assets.js\";\nimport { assertNodeMethod, invalidNodeOptions, snapshotNodeOptions } from \"./node-options.js\";\nimport type { RenderRuntime } from \"./puppeteer-render-runtime.js\";\n\nconst DEFAULT_MAX_SOURCE_CHARS = 50_000;\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;\nconst DEFAULT_MAX_WIDTH = 4096;\nconst DEFAULT_MAX_HEIGHT = 4096;\nconst DEFAULT_MAX_EDGES = 500;\nconst DEFAULT_DEVICE_SCALE_FACTOR = 2;\nconst DEFAULT_BACKGROUND_COLOR = \"white\";\nconst DEFAULT_VIEWPORT_WIDTH = 1280;\nconst DEFAULT_VIEWPORT_HEIGHT = 720;\nconst MERMAID_SHELL =\n \"<!doctype html><html><head><meta charset=\\\"utf-8\\\"><meta http-equiv=\\\"Content-Security-Policy\\\" content=\\\"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src 'none'; font-src 'none'; connect-src 'none'; worker-src 'none'; object-src 'none'; frame-src 'none';\\\"></head><body><div id=\\\"diagram\\\"></div></body></html>\";\nconst SECURE_MERMAID_KEYS = [\n \"secure\",\n \"securityLevel\",\n \"startOnLoad\",\n \"maxTextSize\",\n \"suppressErrorRendering\",\n \"maxEdges\",\n \"deterministicIds\",\n \"deterministicIDSeed\",\n \"layout\",\n];\n\nexport type MermaidTheme = \"base\" | \"dark\" | \"default\" | \"forest\" | \"neutral\";\nconst MERMAID_THEMES: ReadonlySet<string> = new Set([\n \"base\",\n \"dark\",\n \"default\",\n \"forest\",\n \"neutral\",\n]);\n\nexport interface MermaidRendererOptions {\n readonly runtime: RenderRuntime;\n readonly assets: MermaidAssetLoader;\n readonly theme?: MermaidTheme | undefined;\n readonly backgroundColor?: string | undefined;\n readonly deviceScaleFactor?: number | undefined;\n readonly maxSourceChars?: number | undefined;\n readonly maxEdges?: number | undefined;\n readonly maxOutputBytes?: number | undefined;\n readonly maxWidth?: number | undefined;\n readonly maxHeight?: number | undefined;\n}\n\nexport class MermaidRenderer implements BytesImageRenderer {\n readonly id: string;\n readonly output: \"bytes\";\n private readonly runtime: RenderRuntime;\n private readonly assets: MermaidAssetLoader;\n private readonly theme: MermaidTheme;\n private readonly backgroundColor: string;\n private readonly deviceScaleFactor: number;\n private readonly maxSourceChars: number;\n private readonly maxEdges: number;\n private readonly imageLimits: ImageLimits;\n private readonly optionsFingerprint: string;\n\n constructor(options: MermaidRendererOptions) {\n const validated = snapshotNodeOptions(\n options,\n [\n \"runtime\",\n \"assets\",\n \"theme\",\n \"backgroundColor\",\n \"deviceScaleFactor\",\n \"maxSourceChars\",\n \"maxEdges\",\n \"maxOutputBytes\",\n \"maxWidth\",\n \"maxHeight\",\n ],\n \"MermaidRenderer\",\n );\n assertNodeMethod(validated.runtime, \"run\", \"RenderRuntime\");\n assertNodeMethod(validated.assets, \"loadScript\", \"MermaidAssetLoader\");\n this.id = \"mermaid\";\n this.output = \"bytes\";\n this.runtime = validated.runtime;\n this.assets = validated.assets;\n this.theme = validated.theme ?? \"default\";\n this.backgroundColor = validated.backgroundColor ?? DEFAULT_BACKGROUND_COLOR;\n this.deviceScaleFactor = validated.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR;\n this.maxSourceChars = validated.maxSourceChars ?? DEFAULT_MAX_SOURCE_CHARS;\n this.maxEdges = validated.maxEdges ?? DEFAULT_MAX_EDGES;\n this.imageLimits = {\n maxOutputBytes: validated.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,\n maxWidth: validated.maxWidth ?? DEFAULT_MAX_WIDTH,\n maxHeight: validated.maxHeight ?? DEFAULT_MAX_HEIGHT,\n };\n this.optionsFingerprint = [\n this.theme,\n this.backgroundColor,\n String(this.deviceScaleFactor),\n String(this.maxSourceChars),\n String(this.maxEdges),\n String(this.imageLimits.maxOutputBytes),\n String(this.imageLimits.maxWidth),\n String(this.imageLimits.maxHeight),\n ].join(\":\");\n validateOptions({\n theme: this.theme,\n backgroundColor: this.backgroundColor,\n deviceScaleFactor: this.deviceScaleFactor,\n maxSourceChars: this.maxSourceChars,\n maxEdges: this.maxEdges,\n imageLimits: this.imageLimits,\n });\n }\n\n canRender(request: RenderRequest): boolean {\n return request.lang.trim().toLowerCase() === \"mermaid\";\n }\n\n async render(request: RenderRequest, context: OperationContext): Promise<BytesImage | null> {\n if (!this.canRender(request)) {\n return null;\n }\n assertSourceLimit(request.source, this.maxSourceChars);\n\n let data: Uint8Array;\n try {\n data = await this.runtime.run(async (context) => {\n const script = await this.assets.loadScript();\n const page = await context.newPage();\n await page.setRequestInterception(true);\n page.on(\"request\", blockRequest);\n await page.setViewport({\n width: DEFAULT_VIEWPORT_WIDTH,\n height: DEFAULT_VIEWPORT_HEIGHT,\n deviceScaleFactor: this.deviceScaleFactor,\n });\n await page.setContent(MERMAID_SHELL, { waitUntil: \"domcontentloaded\" });\n await page.addScriptTag({ content: script });\n await page.evaluate(\n async (input) => {\n const browserGlobal = globalThis as typeof globalThis & {\n mermaid?: {\n initialize(config: Readonly<Record<string, unknown>>): void;\n parse(source: string, options: { suppressErrors: boolean }): Promise<unknown>;\n render(\n id: string,\n source: string,\n container: Element,\n ): Promise<{ readonly svg: string }>;\n };\n };\n const mermaid = browserGlobal.mermaid;\n const container = document.querySelector(\"#diagram\");\n if (mermaid === undefined || container === null) {\n throw new Error(\"Mermaid browser harness failed to initialize\");\n }\n mermaid.initialize({\n deterministicIds: true,\n deterministicIDSeed: \"slackmark-node\",\n layout: \"dagre\",\n logLevel: \"fatal\",\n maxEdges: input.maxEdges,\n maxTextSize: input.maxSourceChars,\n secure: input.secureKeys,\n securityLevel: \"strict\",\n startOnLoad: false,\n suppressErrorRendering: true,\n theme: input.theme,\n });\n await mermaid.parse(input.source, { suppressErrors: false });\n const rendered = await mermaid.render(\"slackmark-diagram\", input.source, container);\n container.innerHTML = rendered.svg;\n document.body.style.background = input.backgroundColor;\n },\n {\n source: request.source,\n theme: this.theme,\n backgroundColor: this.backgroundColor,\n maxSourceChars: this.maxSourceChars,\n maxEdges: this.maxEdges,\n secureKeys: SECURE_MERMAID_KEYS,\n },\n );\n const element = await page.$(\"#diagram svg\");\n if (element === null) {\n throw new Error(\"Mermaid produced no SVG\");\n }\n const box = await element.boundingBox();\n if (box === null) {\n throw new Error(\"Mermaid SVG has no dimensions\");\n }\n const pixelWidth = Math.ceil(box.width * this.deviceScaleFactor);\n const pixelHeight = Math.ceil(box.height * this.deviceScaleFactor);\n if (pixelWidth > this.imageLimits.maxWidth || pixelHeight > this.imageLimits.maxHeight) {\n throw new NodeRenderError(\n NodeRenderErrorCode.DimensionLimit,\n \"Rendered Mermaid image exceeds configured dimensions\",\n { stage: \"layout\", retryable: false },\n );\n }\n return element.screenshot({\n type: \"png\",\n omitBackground: this.backgroundColor === \"transparent\",\n });\n }, context);\n } catch (error) {\n if (isOperationError(error)) {\n throw error;\n }\n if (error instanceof NodeRenderError) {\n if (error.code !== NodeRenderErrorCode.RenderFailed) {\n throw error;\n }\n }\n throw new NodeRenderError(NodeRenderErrorCode.RenderFailed, \"Mermaid rendering failed\", {\n stage: \"render\",\n retryable: false,\n });\n }\n assertPngLimits(data, this.imageLimits);\n return {\n kind: \"bytes\",\n data,\n mimeType: \"image/png\",\n filename: deterministicPngFilename(\"mermaid\", request.source, this.optionsFingerprint),\n altText: request.altText,\n };\n }\n}\n\nfunction blockRequest(request: HTTPRequest): void {\n void request.abort(\"blockedbyclient\");\n}\n\nfunction validateOptions(renderer: {\n readonly theme: MermaidTheme;\n readonly backgroundColor: string;\n readonly deviceScaleFactor: number;\n readonly maxSourceChars: number;\n readonly maxEdges: number;\n readonly imageLimits: ImageLimits;\n}): void {\n if (typeof renderer.theme !== \"string\" || !MERMAID_THEMES.has(renderer.theme)) {\n throw invalidNodeOptions(\"theme must be base, dark, default, forest, or neutral\");\n }\n if (typeof renderer.backgroundColor !== \"string\" || renderer.backgroundColor.length === 0) {\n throw invalidNodeOptions(\"backgroundColor must be a nonempty string\");\n }\n validatePositiveFinite(renderer.deviceScaleFactor, \"deviceScaleFactor\");\n validatePositiveInteger(renderer.maxSourceChars, \"maxSourceChars\");\n validatePositiveInteger(renderer.maxEdges, \"maxEdges\");\n validatePositiveInteger(renderer.imageLimits.maxOutputBytes, \"maxOutputBytes\");\n validatePositiveInteger(renderer.imageLimits.maxWidth, \"maxWidth\");\n validatePositiveInteger(renderer.imageLimits.maxHeight, \"maxHeight\");\n}\n\nfunction validatePositiveInteger(value: number, name: string): void {\n if (!Number.isInteger(value) || value <= 0) {\n throw invalidNodeOptions(`${name} must be a positive integer`);\n }\n}\n\nfunction validatePositiveFinite(value: number, name: string): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw invalidNodeOptions(`${name} must be positive`);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export declare const NodeConverterState: {
2
+ readonly Open: "open";
3
+ readonly Closing: "closing";
4
+ readonly Closed: "closed";
5
+ readonly CloseFailed: "close-failed";
6
+ };
7
+ export type NodeConverterState = (typeof NodeConverterState)[keyof typeof NodeConverterState];
8
+ export interface NodeWorkStatus {
9
+ readonly accepting: boolean;
10
+ readonly active: number;
11
+ readonly queued: number;
12
+ readonly retainedBytes: number;
13
+ }
14
+ export interface NodeConverterStatus {
15
+ readonly state: NodeConverterState;
16
+ readonly activeOperations: number;
17
+ readonly render: NodeWorkStatus;
18
+ readonly upload: NodeWorkStatus;
19
+ }
20
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB;aAC7B,IAAI,EAAE,MAAM;aACZ,OAAO,EAAE,SAAS;aAClB,MAAM,EAAE,QAAQ;aAChB,WAAW,EAAE,cAAc;CACnB,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC;AAE9F,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;CACjC"}