@farcaster/snap-hono 1.1.8 → 1.2.1
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/dist/fallback.d.ts +5 -0
- package/dist/fallback.js +66 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +107 -47
- package/dist/og-image.d.ts +46 -0
- package/dist/og-image.js +628 -0
- package/dist/payloadToResponse.d.ts +2 -2
- package/dist/payloadToResponse.js +3 -3
- package/dist/renderSnapPage.d.ts +19 -2
- package/dist/renderSnapPage.js +98 -46
- package/package.json +4 -2
- package/src/fallback.ts +81 -0
- package/src/index.ts +142 -49
- package/src/og-image.ts +878 -0
- package/src/payloadToResponse.ts +6 -6
- package/src/renderSnapPage.ts +225 -105
package/dist/fallback.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const FARCASTER_ICON_SVG = `<svg aria-hidden="true" focusable="false" viewBox="0 0 520 457" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M519.801 0V61.6809H458.172V123.31H477.054V123.331H519.801V456.795H416.57L416.507 456.49L363.832 207.03C358.81 183.251 345.667 161.736 326.827 146.434C307.988 131.133 284.255 122.71 260.006 122.71H259.8C235.551 122.71 211.818 131.133 192.979 146.434C174.139 161.736 160.996 183.259 155.974 207.03L103.239 456.795H0V123.323H42.7471V123.31H61.6262V61.6809H0V0H519.801Z" fill="currentColor"/></svg>`;
|
|
2
|
+
export function brandedFallbackHtml(snapOrigin, og) {
|
|
3
|
+
const snapUrl = encodeURIComponent(snapOrigin + "/");
|
|
4
|
+
const testUrl = `https://farcaster.xyz/~/developers/snaps?url=${snapUrl}`;
|
|
5
|
+
const pageUrl = snapOrigin + (og?.resourcePath ?? "/");
|
|
6
|
+
const ogLines = [
|
|
7
|
+
`<meta name="description" content="An interactive embed for Farcaster.">`,
|
|
8
|
+
`<meta property="og:title" content="Farcaster Snap">`,
|
|
9
|
+
`<meta property="og:description" content="An interactive embed for Farcaster.">`,
|
|
10
|
+
`<meta property="og:url" content="${escHtml(pageUrl)}">`,
|
|
11
|
+
`<meta property="og:type" content="website">`,
|
|
12
|
+
`<meta property="og:locale" content="en_US">`,
|
|
13
|
+
];
|
|
14
|
+
if (og?.siteName) {
|
|
15
|
+
ogLines.push(`<meta property="og:site_name" content="${escHtml(og.siteName)}">`);
|
|
16
|
+
}
|
|
17
|
+
if (og?.ogImageUrl) {
|
|
18
|
+
ogLines.push(`<meta property="og:image" content="${escHtml(og.ogImageUrl)}">`, `<meta name="twitter:image" content="${escHtml(og.ogImageUrl)}">`, `<meta name="twitter:card" content="summary_large_image">`);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
ogLines.push(`<meta name="twitter:card" content="summary">`);
|
|
22
|
+
}
|
|
23
|
+
ogLines.push(`<meta name="twitter:title" content="Farcaster Snap">`, `<meta name="twitter:description" content="An interactive embed for Farcaster.">`);
|
|
24
|
+
return `<!DOCTYPE html>
|
|
25
|
+
<html lang="en">
|
|
26
|
+
<head>
|
|
27
|
+
<meta charset="utf-8">
|
|
28
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
29
|
+
<title>Farcaster Snap</title>
|
|
30
|
+
${ogLines.join("\n")}
|
|
31
|
+
<style>
|
|
32
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
33
|
+
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0A0A0A;color:#FAFAFA;min-height:100vh;display:flex;align-items:center;justify-content:center}
|
|
34
|
+
.c{text-align:center;max-width:400px;padding:48px 32px}
|
|
35
|
+
.logo{color:#8B5CF6;margin-bottom:24px}
|
|
36
|
+
.logo svg{width:48px;height:42px}
|
|
37
|
+
h1{font-size:24px;font-weight:700;margin-bottom:8px}
|
|
38
|
+
p{color:#A1A1AA;font-size:15px;line-height:1.5;margin-bottom:32px}
|
|
39
|
+
.btns{display:flex;flex-direction:column;gap:12px}
|
|
40
|
+
a{display:flex;align-items:center;justify-content:center;gap:8px;padding:12px 24px;border-radius:12px;font-size:15px;font-weight:600;text-decoration:none;transition:opacity .15s}
|
|
41
|
+
a:hover{opacity:.85}
|
|
42
|
+
.p{background:#8B5CF6;color:#fff}
|
|
43
|
+
.s{background:#1A1A2E;color:#FAFAFA;border:1px solid #2D2D44}
|
|
44
|
+
.s svg{width:20px;height:18px}
|
|
45
|
+
</style>
|
|
46
|
+
</head>
|
|
47
|
+
<body>
|
|
48
|
+
<div class="c">
|
|
49
|
+
<div class="logo">${FARCASTER_ICON_SVG}</div>
|
|
50
|
+
<h1>Farcaster Snap</h1>
|
|
51
|
+
<p>This is a Farcaster Snap — an interactive embed that lives in the feed.</p>
|
|
52
|
+
<div class="btns">
|
|
53
|
+
<a href="${testUrl}" class="p">Test this snap</a>
|
|
54
|
+
<a href="https://farcaster.xyz" class="s">${FARCASTER_ICON_SVG} Sign up for Farcaster</a>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</body>
|
|
58
|
+
</html>`;
|
|
59
|
+
}
|
|
60
|
+
function escHtml(s) {
|
|
61
|
+
return s
|
|
62
|
+
.replace(/&/g, "&")
|
|
63
|
+
.replace(/</g, "<")
|
|
64
|
+
.replace(/>/g, ">")
|
|
65
|
+
.replace(/"/g, """);
|
|
66
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Hono } from "hono";
|
|
2
2
|
import { type SnapFunction } from "@farcaster/snap";
|
|
3
|
+
import { type OgOptions } from "./og-image.js";
|
|
3
4
|
export type SnapHandlerOptions = {
|
|
4
5
|
/**
|
|
5
6
|
* Route path to register GET and POST handlers on.
|
|
@@ -16,16 +17,22 @@ export type SnapHandlerOptions = {
|
|
|
16
17
|
* over the default branded fallback.
|
|
17
18
|
*/
|
|
18
19
|
fallbackHtml?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Open Graph configuration. Set to `false` to disable OG tag injection and
|
|
22
|
+
* the `/~/og-image` route. Pass an `OgOptions` object to customize rendering.
|
|
23
|
+
* @default true
|
|
24
|
+
*/
|
|
25
|
+
og?: boolean | OgOptions;
|
|
19
26
|
};
|
|
20
27
|
/**
|
|
21
28
|
* Register GET and POST snap handlers on `app` at `options.path` (default `/`).
|
|
22
29
|
*
|
|
23
|
-
* - GET → calls `snapFn(
|
|
30
|
+
* - GET → calls `snapFn(ctx)` with `ctx.action.type === "get"` and returns the response.
|
|
24
31
|
* - POST → parses the JFS-shaped JSON body; verifies it via {@link verifyJFSRequestBody} unless
|
|
25
|
-
* `skipJFSVerification` is true, then calls `snapFn(
|
|
32
|
+
* `skipJFSVerification` is true, then calls `snapFn(ctx)` with the parsed post action and returns the response.
|
|
26
33
|
*
|
|
27
34
|
* All parsing, schema validation, signature verification, and error responses
|
|
28
|
-
* are handled automatically. `
|
|
35
|
+
* are handled automatically. `ctx.request` is the raw `Request` so handlers
|
|
29
36
|
* can read query params, headers, or the URL when needed.
|
|
30
37
|
*/
|
|
31
38
|
export declare function registerSnapHandler(app: Hono, snapFn: SnapFunction, options?: SnapHandlerOptions): void;
|
package/dist/index.js
CHANGED
|
@@ -1,27 +1,99 @@
|
|
|
1
1
|
import { cors } from "hono/cors";
|
|
2
2
|
import { MEDIA_TYPE } from "@farcaster/snap";
|
|
3
3
|
import { parseRequest } from "@farcaster/snap/server";
|
|
4
|
+
import { brandedFallbackHtml } from "./fallback.js";
|
|
4
5
|
import { payloadToResponse, snapHeaders } from "./payloadToResponse.js";
|
|
5
6
|
import { renderSnapPage } from "./renderSnapPage.js";
|
|
7
|
+
import { renderSnapPageToPng, renderWithDedup, etagForPage, } from "./og-image.js";
|
|
6
8
|
/**
|
|
7
9
|
* Register GET and POST snap handlers on `app` at `options.path` (default `/`).
|
|
8
10
|
*
|
|
9
|
-
* - GET → calls `snapFn(
|
|
11
|
+
* - GET → calls `snapFn(ctx)` with `ctx.action.type === "get"` and returns the response.
|
|
10
12
|
* - POST → parses the JFS-shaped JSON body; verifies it via {@link verifyJFSRequestBody} unless
|
|
11
|
-
* `skipJFSVerification` is true, then calls `snapFn(
|
|
13
|
+
* `skipJFSVerification` is true, then calls `snapFn(ctx)` with the parsed post action and returns the response.
|
|
12
14
|
*
|
|
13
15
|
* All parsing, schema validation, signature verification, and error responses
|
|
14
|
-
* are handled automatically. `
|
|
16
|
+
* are handled automatically. `ctx.request` is the raw `Request` so handlers
|
|
15
17
|
* can read query params, headers, or the URL when needed.
|
|
16
18
|
*/
|
|
17
19
|
export function registerSnapHandler(app, snapFn, options = {}) {
|
|
18
20
|
const path = options.path ?? "/";
|
|
21
|
+
const ogEnabled = options.og !== false;
|
|
22
|
+
const ogOptions = typeof options.og === "object" ? options.og : ogEnabled ? {} : undefined;
|
|
19
23
|
app.use(path, cors({ origin: "*" }));
|
|
24
|
+
// ─── /~/og-image PNG route ────────────────────────────
|
|
25
|
+
if (ogEnabled && ogOptions) {
|
|
26
|
+
const imgPath = ogImagePath(path);
|
|
27
|
+
app.get(imgPath, async (c) => {
|
|
28
|
+
const resourcePath = resourcePathFromRequest(c.req.url);
|
|
29
|
+
const key = resourcePath;
|
|
30
|
+
const renderFn = async () => {
|
|
31
|
+
const snap = await snapFn({
|
|
32
|
+
action: { type: "get" },
|
|
33
|
+
request: stripAuthHeaders(c.req.raw),
|
|
34
|
+
});
|
|
35
|
+
const snapJson = JSON.stringify(snap);
|
|
36
|
+
const etag = etagForPage(snapJson);
|
|
37
|
+
const t0 = Date.now();
|
|
38
|
+
const png = await renderSnapPageToPng(snap, ogOptions);
|
|
39
|
+
const elapsed = Date.now() - t0;
|
|
40
|
+
return { png, etag, elapsed };
|
|
41
|
+
};
|
|
42
|
+
try {
|
|
43
|
+
// Adapter cache check
|
|
44
|
+
const adapter = ogOptions.cache;
|
|
45
|
+
if (adapter) {
|
|
46
|
+
const hit = await adapter.get(key);
|
|
47
|
+
if (hit) {
|
|
48
|
+
return new Response(hit.png, {
|
|
49
|
+
status: 200,
|
|
50
|
+
headers: {
|
|
51
|
+
"Content-Type": "image/png",
|
|
52
|
+
ETag: hit.etag,
|
|
53
|
+
"X-OG-Cache": "HIT",
|
|
54
|
+
...ogCacheHeaders(ogOptions),
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const result = await renderWithDedup(key, async () => {
|
|
60
|
+
const r = await renderFn();
|
|
61
|
+
return r;
|
|
62
|
+
});
|
|
63
|
+
const { png, etag } = result;
|
|
64
|
+
const elapsed = result
|
|
65
|
+
.elapsed;
|
|
66
|
+
if (adapter) {
|
|
67
|
+
await adapter
|
|
68
|
+
.set(key, { png, etag }, ogOptions.cdnMaxAge ?? 86400)
|
|
69
|
+
.catch(() => undefined);
|
|
70
|
+
}
|
|
71
|
+
return new Response(png, {
|
|
72
|
+
status: 200,
|
|
73
|
+
headers: {
|
|
74
|
+
"Content-Type": "image/png",
|
|
75
|
+
ETag: etag,
|
|
76
|
+
"X-OG-Cache": "MISS",
|
|
77
|
+
...(elapsed != null ? { "X-OG-Render-Ms": String(elapsed) } : {}),
|
|
78
|
+
...ogCacheHeaders(ogOptions),
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return new Response(null, {
|
|
84
|
+
status: 500,
|
|
85
|
+
headers: { "Cache-Control": "no-store" },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// ─── Main snap route ───────────────────────────────────
|
|
20
91
|
app.get(path, async (c) => {
|
|
21
92
|
const resourcePath = resourcePathFromRequest(c.req.url);
|
|
22
93
|
const accept = c.req.header("Accept");
|
|
23
94
|
if (!clientWantsSnapResponse(accept)) {
|
|
24
|
-
const fallbackHtml = options.fallbackHtml ??
|
|
95
|
+
const fallbackHtml = options.fallbackHtml ??
|
|
96
|
+
(await getFallbackHtml(c.req.raw, snapFn, ogEnabled ? buildOgImageUrl(c.req.raw, path) : undefined));
|
|
25
97
|
return new Response(fallbackHtml, {
|
|
26
98
|
status: 200,
|
|
27
99
|
headers: snapHeaders(resourcePath, "text/html", [
|
|
@@ -71,23 +143,49 @@ export function registerSnapHandler(app, snapFn, options = {}) {
|
|
|
71
143
|
});
|
|
72
144
|
});
|
|
73
145
|
}
|
|
146
|
+
// ─── Helpers ──────────────────────────────────────────────
|
|
147
|
+
function ogImagePath(snapPath) {
|
|
148
|
+
const p = snapPath.replace(/\/+$/, "") || "/";
|
|
149
|
+
return p === "/" ? "/~/og-image" : `${p}/~/og-image`;
|
|
150
|
+
}
|
|
151
|
+
function buildOgImageUrl(request, snapPath) {
|
|
152
|
+
const origin = snapOriginFromRequest(request);
|
|
153
|
+
return origin + ogImagePath(snapPath);
|
|
154
|
+
}
|
|
155
|
+
function ogCacheHeaders(opts) {
|
|
156
|
+
const cdnMaxAge = opts.cdnMaxAge ?? 86400;
|
|
157
|
+
const browserMaxAge = opts.browserMaxAge ?? 60;
|
|
158
|
+
return {
|
|
159
|
+
"Cache-Control": `public, max-age=${browserMaxAge}, s-maxage=${cdnMaxAge}, stale-while-revalidate=604800`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function stripAuthHeaders(request) {
|
|
163
|
+
const headers = new Headers(request.headers);
|
|
164
|
+
headers.delete("cookie");
|
|
165
|
+
headers.delete("authorization");
|
|
166
|
+
return new Request(request.url, { method: request.method, headers });
|
|
167
|
+
}
|
|
74
168
|
function resourcePathFromRequest(url) {
|
|
75
169
|
const u = new URL(url);
|
|
76
170
|
return u.pathname + u.search;
|
|
77
171
|
}
|
|
78
|
-
async function getFallbackHtml(request, snapFn) {
|
|
172
|
+
async function getFallbackHtml(request, snapFn, ogImageUrl) {
|
|
173
|
+
const origin = snapOriginFromRequest(request);
|
|
174
|
+
const siteName = process.env.SNAP_OG_SITE_NAME?.trim() ||
|
|
175
|
+
process.env.OG_SITE_NAME?.trim() ||
|
|
176
|
+
undefined;
|
|
177
|
+
const resourcePath = resourcePathFromRequest(request.url);
|
|
79
178
|
try {
|
|
80
179
|
const snap = await snapFn({
|
|
81
180
|
action: { type: "get" },
|
|
82
|
-
request,
|
|
181
|
+
request: stripAuthHeaders(request),
|
|
83
182
|
});
|
|
84
|
-
return renderSnapPage(snap,
|
|
183
|
+
return renderSnapPage(snap, origin, { ogImageUrl, resourcePath, siteName });
|
|
85
184
|
}
|
|
86
185
|
catch {
|
|
87
|
-
return brandedFallbackHtml(
|
|
186
|
+
return brandedFallbackHtml(origin, { ogImageUrl, resourcePath, siteName });
|
|
88
187
|
}
|
|
89
188
|
}
|
|
90
|
-
const FARCASTER_ICON_SVG = `<svg aria-hidden="true" focusable="false" viewBox="0 0 520 457" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M519.801 0V61.6809H458.172V123.31H477.054V123.331H519.801V456.795H416.57L416.507 456.49L363.832 207.03C358.81 183.251 345.667 161.736 326.827 146.434C307.988 131.133 284.255 122.71 260.006 122.71H259.8C235.551 122.71 211.818 131.133 192.979 146.434C174.139 161.736 160.996 183.259 155.974 207.03L103.239 456.795H0V123.323H42.7471V123.31H61.6262V61.6809H0V0H519.801Z" fill="currentColor"/></svg>`;
|
|
91
189
|
function snapOriginFromRequest(request) {
|
|
92
190
|
const fromEnv = process.env.SNAP_PUBLIC_BASE_URL?.trim();
|
|
93
191
|
if (fromEnv)
|
|
@@ -99,44 +197,6 @@ function snapOriginFromRequest(request) {
|
|
|
99
197
|
return `${proto}://${host}`.replace(/\/$/, "");
|
|
100
198
|
return "https://snap.farcaster.xyz";
|
|
101
199
|
}
|
|
102
|
-
function brandedFallbackHtml(snapOrigin) {
|
|
103
|
-
const snapUrl = encodeURIComponent(snapOrigin + "/");
|
|
104
|
-
const testUrl = `https://farcaster.xyz/~/developers/snaps?url=${snapUrl}`;
|
|
105
|
-
return `<!DOCTYPE html>
|
|
106
|
-
<html lang="en">
|
|
107
|
-
<head>
|
|
108
|
-
<meta charset="utf-8">
|
|
109
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
110
|
-
<title>Farcaster Snap</title>
|
|
111
|
-
<style>
|
|
112
|
-
*{margin:0;padding:0;box-sizing:border-box}
|
|
113
|
-
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0A0A0A;color:#FAFAFA;min-height:100vh;display:flex;align-items:center;justify-content:center}
|
|
114
|
-
.c{text-align:center;max-width:400px;padding:48px 32px}
|
|
115
|
-
.logo{color:#8B5CF6;margin-bottom:24px}
|
|
116
|
-
.logo svg{width:48px;height:42px}
|
|
117
|
-
h1{font-size:24px;font-weight:700;margin-bottom:8px}
|
|
118
|
-
p{color:#A1A1AA;font-size:15px;line-height:1.5;margin-bottom:32px}
|
|
119
|
-
.btns{display:flex;flex-direction:column;gap:12px}
|
|
120
|
-
a{display:flex;align-items:center;justify-content:center;gap:8px;padding:12px 24px;border-radius:12px;font-size:15px;font-weight:600;text-decoration:none;transition:opacity .15s}
|
|
121
|
-
a:hover{opacity:.85}
|
|
122
|
-
.p{background:#8B5CF6;color:#fff}
|
|
123
|
-
.s{background:#1A1A2E;color:#FAFAFA;border:1px solid #2D2D44}
|
|
124
|
-
.s svg{width:20px;height:18px}
|
|
125
|
-
</style>
|
|
126
|
-
</head>
|
|
127
|
-
<body>
|
|
128
|
-
<div class="c">
|
|
129
|
-
<div class="logo">${FARCASTER_ICON_SVG}</div>
|
|
130
|
-
<h1>Farcaster Snap</h1>
|
|
131
|
-
<p>This is a Farcaster Snap — an interactive embed that lives in the feed.</p>
|
|
132
|
-
<div class="btns">
|
|
133
|
-
<a href="${testUrl}" class="p">Test this snap</a>
|
|
134
|
-
<a href="https://farcaster.xyz" class="s">${FARCASTER_ICON_SVG} Sign up for Farcaster</a>
|
|
135
|
-
</div>
|
|
136
|
-
</div>
|
|
137
|
-
</body>
|
|
138
|
-
</html>`;
|
|
139
|
-
}
|
|
140
200
|
function clientWantsSnapResponse(accept) {
|
|
141
201
|
if (!accept || accept.trim() === "")
|
|
142
202
|
return false;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { SnapHandlerResult } from "@farcaster/snap";
|
|
2
|
+
export type OgFontSpec = {
|
|
3
|
+
/** Absolute path to a .woff2 (or .woff / .ttf) file on disk. */
|
|
4
|
+
path: string;
|
|
5
|
+
weight: 400 | 700;
|
|
6
|
+
style?: "normal" | "italic";
|
|
7
|
+
};
|
|
8
|
+
export type OgCacheAdapter = {
|
|
9
|
+
get(key: string): Promise<{
|
|
10
|
+
png: Uint8Array;
|
|
11
|
+
etag: string;
|
|
12
|
+
} | null>;
|
|
13
|
+
set(key: string, value: {
|
|
14
|
+
png: Uint8Array;
|
|
15
|
+
etag: string;
|
|
16
|
+
}, ttlSeconds: number): Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
export type OgOptions = {
|
|
19
|
+
/** OG image width in pixels. @default card width + outer margin (~508) */
|
|
20
|
+
width?: number;
|
|
21
|
+
/** OG image height in pixels. @default derived from snap content + margins */
|
|
22
|
+
height?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Font files to use for OG image rendering. Pass absolute disk paths to
|
|
25
|
+
* woff2/ttf files. Falls back to a CDN-loaded Inter if omitted or unavailable.
|
|
26
|
+
*/
|
|
27
|
+
fonts?: OgFontSpec[];
|
|
28
|
+
/**
|
|
29
|
+
* Optional distributed cache adapter (e.g. Upstash Redis).
|
|
30
|
+
* When omitted the function relies entirely on CDN Cache-Control headers.
|
|
31
|
+
*/
|
|
32
|
+
cache?: OgCacheAdapter;
|
|
33
|
+
/** CDN s-maxage in seconds. @default 86400 */
|
|
34
|
+
cdnMaxAge?: number;
|
|
35
|
+
/** Browser max-age in seconds. @default 60 */
|
|
36
|
+
browserMaxAge?: number;
|
|
37
|
+
};
|
|
38
|
+
export declare function etagForPage(snapJson: string): string;
|
|
39
|
+
export declare function renderWithDedup(key: string, render: () => Promise<{
|
|
40
|
+
png: Uint8Array;
|
|
41
|
+
etag: string;
|
|
42
|
+
}>): Promise<{
|
|
43
|
+
png: Uint8Array;
|
|
44
|
+
etag: string;
|
|
45
|
+
}>;
|
|
46
|
+
export declare function renderSnapPageToPng(snap: SnapHandlerResult, options?: OgOptions): Promise<Uint8Array>;
|