@farcaster/snap-hono 2.0.6 → 2.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.
package/dist/index.d.ts CHANGED
@@ -12,7 +12,8 @@ export type SnapHandlerOptions = {
12
12
  */
13
13
  path?: string;
14
14
  /**
15
- * When true, skip JFS signature verification only. POST bodies must still be JFS-shaped JSON.
15
+ * When true, skip JFS signature verification only. POST bodies must still be a JFS envelope:
16
+ * JSON `{ header, payload, signature }` or the same compact dot-separated string as GET’s `X-Snap-Payload`.
16
17
  * When omitted, default to {@link envSkipJFSVerification}.
17
18
  */
18
19
  skipJFSVerification?: boolean;
@@ -38,7 +39,7 @@ export type SnapHandlerOptions = {
38
39
  * Register GET and POST snap handlers on `app` at `options.path` (default `/`).
39
40
  *
40
41
  * - GET → calls `snapFn(ctx)` with `ctx.action.type === "get"` and returns the response.
41
- * - POST → parses the JFS-shaped JSON body; verifies it via {@link verifyJFSRequestBody} unless
42
+ * - POST → parses the JFS envelope (JSON object or compact string); verifies via {@link verifyJFSRequestBody} unless
42
43
  * `skipJFSVerification` is true, then calls `snapFn(ctx)` with the parsed post action and returns the response.
43
44
  *
44
45
  * All parsing, schema validation, signature verification, and error responses
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { cors } from "hono/cors";
2
- import { MEDIA_TYPE, ACTION_TYPE_GET, } from "@farcaster/snap";
2
+ import { MEDIA_TYPE, ACTION_TYPE_GET, SNAP_PAYLOAD_HEADER, } from "@farcaster/snap";
3
3
  import { parseRequest } from "@farcaster/snap/server";
4
4
  import { brandedFallbackHtml } from "./fallback.js";
5
5
  import { payloadToResponse, snapHeaders } from "./payloadToResponse.js";
@@ -9,7 +9,7 @@ import { renderSnapPageToPng, renderWithDedup, etagForPage, } from "./og-image.j
9
9
  * Register GET and POST snap handlers on `app` at `options.path` (default `/`).
10
10
  *
11
11
  * - GET → calls `snapFn(ctx)` with `ctx.action.type === "get"` and returns the response.
12
- * - POST → parses the JFS-shaped JSON body; verifies it via {@link verifyJFSRequestBody} unless
12
+ * - POST → parses the JFS envelope (JSON object or compact string); verifies via {@link verifyJFSRequestBody} unless
13
13
  * `skipJFSVerification` is true, then calls `snapFn(ctx)` with the parsed post action and returns the response.
14
14
  *
15
15
  * All parsing, schema validation, signature verification, and error responses
@@ -102,8 +102,21 @@ export function registerSnapHandler(app, snapFn, options = {}) {
102
102
  ]),
103
103
  });
104
104
  }
105
+ const skipJFSVerification = options.skipJFSVerification !== undefined
106
+ ? options.skipJFSVerification
107
+ : envSkipJFSVerification();
108
+ const parsed = await parseRequest(c.req.raw, {
109
+ skipJFSVerification,
110
+ requestOrigin: snapOriginFromRequest(c.req.raw),
111
+ });
112
+ if (!parsed.success) {
113
+ const msg = "message" in parsed.error
114
+ ? parsed.error.message
115
+ : "failed to parse request";
116
+ return c.json({ error: msg }, 400);
117
+ }
105
118
  const response = await snapFn({
106
- action: { type: ACTION_TYPE_GET },
119
+ action: parsed.action,
107
120
  request: c.req.raw,
108
121
  });
109
122
  return payloadToResponse(response, {
@@ -171,6 +184,7 @@ function stripAuthHeaders(request) {
171
184
  const headers = new Headers(request.headers);
172
185
  headers.delete("cookie");
173
186
  headers.delete("authorization");
187
+ headers.delete(SNAP_PAYLOAD_HEADER);
174
188
  return new Request(request.url, { method: request.method, headers });
175
189
  }
176
190
  function resourcePathFromRequest(url) {
package/dist/og-image.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DEFAULT_THEME_ACCENT, PALETTE_LIGHT_HEX } from "@farcaster/snap";
1
+ import { DEFAULT_THEME_ACCENT, PALETTE_LIGHT_HEX, resolveSnapColorHex, } from "@farcaster/snap";
2
2
  import satori from "satori";
3
3
  import { Resvg, initWasm } from "@resvg/resvg-wasm";
4
4
  /** Content width inside the OG card (`OG_CARD_OUTER_WIDTH_PX` − horizontal padding 24px×2). */
@@ -164,9 +164,7 @@ function accentHex(accent) {
164
164
  PALETTE_LIGHT_HEX[DEFAULT_THEME_ACCENT]);
165
165
  }
166
166
  function colorHex(color, accent) {
167
- if (!color || color === "accent")
168
- return accent;
169
- return PALETTE_LIGHT_HEX[color] ?? accent;
167
+ return resolveSnapColorHex(color, { accentHex: accent, appearance: "light" });
170
168
  }
171
169
  function mapText(el) {
172
170
  const size = String(el.size ?? "md");
@@ -1,4 +1,4 @@
1
- import { MEDIA_TYPE, validateSnapResponse, snapResponseSchema, } from "@farcaster/snap";
1
+ import { MEDIA_TYPE, validateSnapResponse, snapResponseSchema, SNAP_PAYLOAD_HEADER, } from "@farcaster/snap";
2
2
  import { snapJsonRenderCatalog } from "@farcaster/snap/ui";
3
3
  const DEFAULT_LINK_MEDIA_TYPES = [MEDIA_TYPE, "text/html"];
4
4
  export function payloadToResponse(payload, options = {}) {
@@ -32,7 +32,7 @@ function errorResponse(error, issues) {
32
32
  export function snapHeaders(resourcePath, currentMediaType, availableMediaTypes) {
33
33
  return {
34
34
  "Content-Type": `${currentMediaType}; charset=utf-8`,
35
- Vary: "Accept",
35
+ Vary: `Accept, ${SNAP_PAYLOAD_HEADER}`,
36
36
  Link: buildSnapAlternateLinkHeader(resourcePath, availableMediaTypes),
37
37
  };
38
38
  }
@@ -1,4 +1,4 @@
1
- import { DEFAULT_THEME_ACCENT, PALETTE_LIGHT_HEX, PALETTE_COLOR_ACCENT, } from "@farcaster/snap";
1
+ import { DEFAULT_THEME_ACCENT, PALETTE_LIGHT_HEX, resolveSnapColorHex, } from "@farcaster/snap";
2
2
  export function extractPageMeta(spec) {
3
3
  let title = "Farcaster Snap";
4
4
  let description = "";
@@ -82,9 +82,7 @@ function accentHex(accent) {
82
82
  : PALETTE_LIGHT_HEX[DEFAULT_THEME_ACCENT];
83
83
  }
84
84
  function colorHex(color, accent) {
85
- if (!color || color === PALETTE_COLOR_ACCENT)
86
- return accent;
87
- return PALETTE_LIGHT_HEX[color] ?? accent;
85
+ return resolveSnapColorHex(color, { accentHex: accent, appearance: "light" });
88
86
  }
89
87
  /** Readable foreground for a hex background (YIQ contrast check). */
90
88
  function fgForBg(hex) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farcaster/snap-hono",
3
- "version": "2.0.6",
3
+ "version": "2.1.0",
4
4
  "description": "Hono integration for Farcaster Snap servers",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,7 +28,7 @@
28
28
  "dependencies": {
29
29
  "@resvg/resvg-wasm": "^2.6.2",
30
30
  "satori": "^0.10.0",
31
- "@farcaster/snap": "2.1.2"
31
+ "@farcaster/snap": "2.2.0"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "hono": ">=4.0.0"
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  MEDIA_TYPE,
5
5
  type SnapFunction,
6
6
  ACTION_TYPE_GET,
7
+ SNAP_PAYLOAD_HEADER,
7
8
  } from "@farcaster/snap";
8
9
  import { parseRequest } from "@farcaster/snap/server";
9
10
  import { brandedFallbackHtml } from "./fallback";
@@ -29,7 +30,8 @@ export type SnapHandlerOptions = {
29
30
  path?: string;
30
31
 
31
32
  /**
32
- * When true, skip JFS signature verification only. POST bodies must still be JFS-shaped JSON.
33
+ * When true, skip JFS signature verification only. POST bodies must still be a JFS envelope:
34
+ * JSON `{ header, payload, signature }` or the same compact dot-separated string as GET’s `X-Snap-Payload`.
33
35
  * When omitted, default to {@link envSkipJFSVerification}.
34
36
  */
35
37
  skipJFSVerification?: boolean;
@@ -59,7 +61,7 @@ export type SnapHandlerOptions = {
59
61
  * Register GET and POST snap handlers on `app` at `options.path` (default `/`).
60
62
  *
61
63
  * - GET → calls `snapFn(ctx)` with `ctx.action.type === "get"` and returns the response.
62
- * - POST → parses the JFS-shaped JSON body; verifies it via {@link verifyJFSRequestBody} unless
64
+ * - POST → parses the JFS envelope (JSON object or compact string); verifies via {@link verifyJFSRequestBody} unless
63
65
  * `skipJFSVerification` is true, then calls `snapFn(ctx)` with the parsed post action and returns the response.
64
66
  *
65
67
  * All parsing, schema validation, signature verification, and error responses
@@ -175,8 +177,26 @@ export function registerSnapHandler(
175
177
  });
176
178
  }
177
179
 
180
+ const skipJFSVerification =
181
+ options.skipJFSVerification !== undefined
182
+ ? options.skipJFSVerification
183
+ : envSkipJFSVerification();
184
+
185
+ const parsed = await parseRequest(c.req.raw, {
186
+ skipJFSVerification,
187
+ requestOrigin: snapOriginFromRequest(c.req.raw),
188
+ });
189
+
190
+ if (!parsed.success) {
191
+ const msg =
192
+ "message" in parsed.error
193
+ ? parsed.error.message
194
+ : "failed to parse request";
195
+ return c.json({ error: msg }, 400);
196
+ }
197
+
178
198
  const response = await snapFn({
179
- action: { type: ACTION_TYPE_GET },
199
+ action: parsed.action,
180
200
  request: c.req.raw,
181
201
  });
182
202
 
@@ -261,6 +281,7 @@ function stripAuthHeaders(request: Request): Request {
261
281
  const headers = new Headers(request.headers);
262
282
  headers.delete("cookie");
263
283
  headers.delete("authorization");
284
+ headers.delete(SNAP_PAYLOAD_HEADER);
264
285
  return new Request(request.url, { method: request.method, headers });
265
286
  }
266
287
 
package/src/og-image.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import type { SnapHandlerResult, SnapSpec } from "@farcaster/snap";
2
- import { DEFAULT_THEME_ACCENT, PALETTE_LIGHT_HEX } from "@farcaster/snap";
2
+ import {
3
+ DEFAULT_THEME_ACCENT,
4
+ PALETTE_LIGHT_HEX,
5
+ resolveSnapColorHex,
6
+ } from "@farcaster/snap";
3
7
  import satori from "satori";
4
8
  import { Resvg, initWasm } from "@resvg/resvg-wasm";
5
9
 
@@ -250,8 +254,7 @@ function accentHex(accent: string | undefined): string {
250
254
  }
251
255
 
252
256
  function colorHex(color: string | undefined, accent: string): string {
253
- if (!color || color === "accent") return accent;
254
- return PALETTE_LIGHT_HEX[color as keyof typeof PALETTE_LIGHT_HEX] ?? accent;
257
+ return resolveSnapColorHex(color, { accentHex: accent, appearance: "light" });
255
258
  }
256
259
 
257
260
  function mapText(el: El): VNode {
@@ -3,6 +3,7 @@ import {
3
3
  type SnapHandlerResult,
4
4
  validateSnapResponse,
5
5
  snapResponseSchema,
6
+ SNAP_PAYLOAD_HEADER,
6
7
  } from "@farcaster/snap";
7
8
  import { snapJsonRenderCatalog } from "@farcaster/snap/ui";
8
9
 
@@ -43,13 +44,10 @@ export function payloadToResponse(
43
44
  }
44
45
 
45
46
  function errorResponse(error: string, issues: unknown[]): Response {
46
- return new Response(
47
- JSON.stringify({ error, issues }),
48
- {
49
- status: 400,
50
- headers: { "Content-Type": "application/json; charset=utf-8" },
51
- },
52
- );
47
+ return new Response(JSON.stringify({ error, issues }), {
48
+ status: 400,
49
+ headers: { "Content-Type": "application/json; charset=utf-8" },
50
+ });
53
51
  }
54
52
 
55
53
  export function snapHeaders(
@@ -59,7 +57,7 @@ export function snapHeaders(
59
57
  ) {
60
58
  return {
61
59
  "Content-Type": `${currentMediaType}; charset=utf-8`,
62
- Vary: "Accept",
60
+ Vary: `Accept, ${SNAP_PAYLOAD_HEADER}`,
63
61
  Link: buildSnapAlternateLinkHeader(resourcePath, availableMediaTypes),
64
62
  };
65
63
  }
@@ -7,7 +7,7 @@ import type {
7
7
  import {
8
8
  DEFAULT_THEME_ACCENT,
9
9
  PALETTE_LIGHT_HEX,
10
- PALETTE_COLOR_ACCENT,
10
+ resolveSnapColorHex,
11
11
  } from "@farcaster/snap";
12
12
 
13
13
  // ─── OG meta ────────────────────────────────────────────
@@ -130,8 +130,7 @@ function accentHex(accent: PaletteColor | undefined): string {
130
130
  }
131
131
 
132
132
  function colorHex(color: string | undefined, accent: string): string {
133
- if (!color || color === PALETTE_COLOR_ACCENT) return accent;
134
- return (PALETTE_LIGHT_HEX as Record<string, string>)[color] ?? accent;
133
+ return resolveSnapColorHex(color, { accentHex: accent, appearance: "light" });
135
134
  }
136
135
 
137
136
  /** Readable foreground for a hex background (YIQ contrast check). */