@farcaster/snap 1.3.3 → 1.4.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.
@@ -60,6 +60,7 @@ export declare const PALETTE_COLOR: {
60
60
  readonly purple: "purple";
61
61
  readonly pink: "pink";
62
62
  };
63
+ export declare const PALETTE_COLOR_ACCENT: "accent";
63
64
  export declare const PALETTE_COLOR_VALUES: readonly ["gray", "blue", "red", "amber", "green", "teal", "purple", "pink"];
64
65
  export type PaletteColor = (typeof PALETTE_COLOR_VALUES)[number];
65
66
  /** Light-mode hex for each palette color (emulator / reference client). */
package/dist/constants.js CHANGED
@@ -75,6 +75,7 @@ export const PALETTE_COLOR = {
75
75
  purple: "purple",
76
76
  pink: "pink",
77
77
  };
78
+ export const PALETTE_COLOR_ACCENT = "accent";
78
79
  export const PALETTE_COLOR_VALUES = [
79
80
  PALETTE_COLOR.gray,
80
81
  PALETTE_COLOR.blue,
@@ -108,7 +109,7 @@ export const PALETTE_DARK_HEX = {
108
109
  pink: "#F12B82",
109
110
  };
110
111
  export const PROGRESS_COLOR_VALUES = [
111
- "accent",
112
+ PALETTE_COLOR_ACCENT,
112
113
  ...PALETTE_COLOR_VALUES,
113
114
  ];
114
115
  export const LIST_STYLE_VALUES = ["ordered", "unordered", "plain"];
@@ -148,7 +149,7 @@ export const BUTTON_STYLE_VALUES = [
148
149
  export const BUTTON_LAYOUT_VALUES = ["stack", "row", "grid"];
149
150
  export const DEFAULT_BUTTON_LAYOUT = BUTTON_LAYOUT_VALUES[0];
150
151
  export const BAR_CHART_COLOR_VALUES = [
151
- "accent",
152
+ PALETTE_COLOR_ACCENT,
152
153
  ...PALETTE_COLOR_VALUES,
153
154
  ];
154
155
  export const EFFECT_VALUES = ["confetti"];
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { POST_GRID_TAP_KEY, PAGE_ROOT_TYPE, ELEMENT_TYPE, MEDIA_TYPE, DEFAULT_THEME_ACCENT, DEFAULT_LIST_STYLE, DEFAULT_SLIDER_STEP, PALETTE_COLOR, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, type PaletteColor, } from "./constants.js";
2
- export { rootSchema, firstPageRootSchema, payloadSchema, type SnapAction, type SnapContext, type SnapResponse, type SnapResponseInput, type SnapFunction, type SnapPayload, } from "./schemas.js";
3
- export { validatePage, validateFirstPage, type ValidationResult, } from "./validator.js";
1
+ export { POST_GRID_TAP_KEY, PAGE_ROOT_TYPE, ELEMENT_TYPE, MEDIA_TYPE, DEFAULT_THEME_ACCENT, DEFAULT_LIST_STYLE, DEFAULT_SLIDER_STEP, PALETTE_COLOR, PALETTE_COLOR_ACCENT, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, type PaletteColor, } from "./constants.js";
2
+ export { snapResponseSchema, firstPageResponseSchema, payloadSchema, type Button, type Element, type Elements, type GroupChildElement, type SnapAction, type SnapPageElementInput, type SnapContext, type SnapResponse, type SnapHandlerResult, type SnapFunction, type SnapPayload, } from "./schemas.js";
3
+ export { validateSnapResponse, validateFirstPageResponse, type ValidationResult, } from "./validator.js";
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { POST_GRID_TAP_KEY, PAGE_ROOT_TYPE, ELEMENT_TYPE, MEDIA_TYPE, DEFAULT_THEME_ACCENT, DEFAULT_LIST_STYLE, DEFAULT_SLIDER_STEP, PALETTE_COLOR, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, } from "./constants.js";
2
- export { rootSchema, firstPageRootSchema, payloadSchema, } from "./schemas.js";
3
- export { validatePage, validateFirstPage, } from "./validator.js";
1
+ export { POST_GRID_TAP_KEY, PAGE_ROOT_TYPE, ELEMENT_TYPE, MEDIA_TYPE, DEFAULT_THEME_ACCENT, DEFAULT_LIST_STYLE, DEFAULT_SLIDER_STEP, PALETTE_COLOR, PALETTE_COLOR_ACCENT, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, } from "./constants.js";
2
+ export { snapResponseSchema, firstPageResponseSchema, payloadSchema, } from "./schemas.js";
3
+ export { validateSnapResponse, validateFirstPageResponse, } from "./validator.js";
package/dist/schemas.d.ts CHANGED
@@ -13,6 +13,147 @@ declare const buttonSchema: z.ZodObject<{
13
13
  secondary: "secondary";
14
14
  }>>;
15
15
  }, z.core.$strip>;
16
+ export type Button = z.infer<typeof buttonSchema>;
17
+ /** Child elements allowed inside `group` (no media, no nested group) */
18
+ declare const groupChildElementSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
19
+ type: z.ZodLiteral<"text">;
20
+ style: z.ZodEnum<{
21
+ title: "title";
22
+ body: "body";
23
+ caption: "caption";
24
+ label: "label";
25
+ }>;
26
+ content: z.ZodString;
27
+ align: z.ZodOptional<z.ZodEnum<{
28
+ left: "left";
29
+ center: "center";
30
+ right: "right";
31
+ }>>;
32
+ }, z.core.$strip>, z.ZodObject<{
33
+ type: z.ZodLiteral<"divider">;
34
+ }, z.core.$strip>, z.ZodObject<{
35
+ type: z.ZodLiteral<"spacer">;
36
+ size: z.ZodDefault<z.ZodEnum<{
37
+ small: "small";
38
+ medium: "medium";
39
+ large: "large";
40
+ }>>;
41
+ }, z.core.$strip>, z.ZodObject<{
42
+ type: z.ZodLiteral<"progress">;
43
+ value: z.ZodNumber;
44
+ max: z.ZodNumber;
45
+ label: z.ZodOptional<z.ZodString>;
46
+ color: z.ZodOptional<z.ZodEnum<{
47
+ gray: "gray";
48
+ blue: "blue";
49
+ red: "red";
50
+ amber: "amber";
51
+ green: "green";
52
+ teal: "teal";
53
+ purple: "purple";
54
+ pink: "pink";
55
+ accent: "accent";
56
+ }>>;
57
+ }, z.core.$strip>, z.ZodObject<{
58
+ type: z.ZodLiteral<"list">;
59
+ style: z.ZodDefault<z.ZodEnum<{
60
+ ordered: "ordered";
61
+ unordered: "unordered";
62
+ plain: "plain";
63
+ }>>;
64
+ items: z.ZodArray<z.ZodObject<{
65
+ content: z.ZodString;
66
+ trailing: z.ZodOptional<z.ZodString>;
67
+ }, z.core.$strip>>;
68
+ }, z.core.$strip>, z.ZodObject<{
69
+ type: z.ZodLiteral<"text_input">;
70
+ name: z.ZodString;
71
+ placeholder: z.ZodOptional<z.ZodString>;
72
+ maxLength: z.ZodOptional<z.ZodNumber>;
73
+ }, z.core.$strip>, z.ZodPipe<z.ZodObject<{
74
+ type: z.ZodLiteral<"slider">;
75
+ name: z.ZodString;
76
+ min: z.ZodNumber;
77
+ max: z.ZodNumber;
78
+ step: z.ZodDefault<z.ZodNumber>;
79
+ value: z.ZodOptional<z.ZodNumber>;
80
+ label: z.ZodOptional<z.ZodString>;
81
+ minLabel: z.ZodOptional<z.ZodString>;
82
+ maxLabel: z.ZodOptional<z.ZodString>;
83
+ }, z.core.$strip>, z.ZodTransform<{
84
+ value: number;
85
+ type: "slider";
86
+ name: string;
87
+ min: number;
88
+ max: number;
89
+ step: number;
90
+ label?: string | undefined;
91
+ minLabel?: string | undefined;
92
+ maxLabel?: string | undefined;
93
+ }, {
94
+ type: "slider";
95
+ name: string;
96
+ min: number;
97
+ max: number;
98
+ step: number;
99
+ value?: number | undefined;
100
+ label?: string | undefined;
101
+ minLabel?: string | undefined;
102
+ maxLabel?: string | undefined;
103
+ }>>, z.ZodPipe<z.ZodObject<{
104
+ type: z.ZodLiteral<"button_group">;
105
+ name: z.ZodString;
106
+ options: z.ZodArray<z.ZodString>;
107
+ style: z.ZodOptional<z.ZodEnum<{
108
+ row: "row";
109
+ stack: "stack";
110
+ grid: "grid";
111
+ }>>;
112
+ }, z.core.$strip>, z.ZodTransform<{
113
+ style: "row" | "stack" | "grid";
114
+ type: "button_group";
115
+ name: string;
116
+ options: string[];
117
+ }, {
118
+ type: "button_group";
119
+ name: string;
120
+ options: string[];
121
+ style?: "row" | "stack" | "grid" | undefined;
122
+ }>>, z.ZodObject<{
123
+ type: z.ZodLiteral<"toggle">;
124
+ name: z.ZodString;
125
+ label: z.ZodString;
126
+ value: z.ZodDefault<z.ZodBoolean>;
127
+ }, z.core.$strip>, z.ZodObject<{
128
+ type: z.ZodLiteral<"bar_chart">;
129
+ bars: z.ZodArray<z.ZodObject<{
130
+ label: z.ZodString;
131
+ value: z.ZodNumber;
132
+ color: z.ZodOptional<z.ZodEnum<{
133
+ gray: "gray";
134
+ blue: "blue";
135
+ red: "red";
136
+ amber: "amber";
137
+ green: "green";
138
+ teal: "teal";
139
+ purple: "purple";
140
+ pink: "pink";
141
+ }>>;
142
+ }, z.core.$strip>>;
143
+ max: z.ZodOptional<z.ZodNumber>;
144
+ color: z.ZodOptional<z.ZodEnum<{
145
+ gray: "gray";
146
+ blue: "blue";
147
+ red: "red";
148
+ amber: "amber";
149
+ green: "green";
150
+ teal: "teal";
151
+ purple: "purple";
152
+ pink: "pink";
153
+ accent: "accent";
154
+ }>>;
155
+ }, z.core.$strip>], "type">;
156
+ export type GroupChildElement = z.infer<typeof groupChildElementSchema>;
16
157
  /** Any single page element, including media and `group` */
17
158
  declare const elementSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
18
159
  type: z.ZodLiteral<"text">;
@@ -326,6 +467,8 @@ declare const elementSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
326
467
  accent: "accent";
327
468
  }>>;
328
469
  }, z.core.$strip>], "type">;
470
+ export type Element = z.infer<typeof elementSchema>;
471
+ export type SnapPageElementInput = z.input<typeof elementSchema>;
329
472
  declare const elementsSchema: z.ZodObject<{
330
473
  type: z.ZodLiteral<"stack">;
331
474
  children: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -641,10 +784,8 @@ declare const elementsSchema: z.ZodObject<{
641
784
  }>>;
642
785
  }, z.core.$strip>], "type">>;
643
786
  }, z.core.$strict>;
644
- export type Button = z.infer<typeof buttonSchema>;
645
- export type Element = z.infer<typeof elementSchema>;
646
787
  export type Elements = z.infer<typeof elementsSchema>;
647
- export declare const rootSchema: z.ZodObject<{
788
+ export declare const snapResponseSchema: z.ZodObject<{
648
789
  version: z.ZodLiteral<"1.0">;
649
790
  page: z.ZodObject<{
650
791
  theme: z.ZodDefault<z.ZodOptional<z.ZodObject<{
@@ -998,7 +1139,9 @@ export declare const rootSchema: z.ZodObject<{
998
1139
  }, z.core.$strip>>>;
999
1140
  }, z.core.$strict>;
1000
1141
  }, z.core.$strict>;
1001
- export declare const firstPageRootSchema: z.ZodObject<{
1142
+ export type SnapResponse = z.infer<typeof snapResponseSchema>;
1143
+ export type SnapHandlerResult = z.input<typeof snapResponseSchema>;
1144
+ export declare const firstPageResponseSchema: z.ZodObject<{
1002
1145
  version: z.ZodLiteral<"1.0">;
1003
1146
  page: z.ZodObject<{
1004
1147
  theme: z.ZodDefault<z.ZodOptional<z.ZodObject<{
@@ -1352,6 +1495,7 @@ export declare const firstPageRootSchema: z.ZodObject<{
1352
1495
  }, z.core.$strip>>>;
1353
1496
  }, z.core.$strict>;
1354
1497
  }, z.core.$strict>;
1498
+ export type FirstPageResponse = z.infer<typeof firstPageResponseSchema>;
1355
1499
  export declare const payloadSchema: z.ZodObject<{
1356
1500
  fid: z.ZodNumber;
1357
1501
  inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodObject<{
@@ -1361,27 +1505,40 @@ export declare const payloadSchema: z.ZodObject<{
1361
1505
  button_index: z.ZodNumber;
1362
1506
  timestamp: z.ZodNumber;
1363
1507
  }, z.core.$strict>;
1364
- export type SnapResponse = z.infer<typeof rootSchema>;
1365
- export type SnapResponseInput = z.input<typeof rootSchema>;
1366
- export type SnapPage = SnapResponse["page"];
1367
1508
  export type SnapPayload = z.infer<typeof payloadSchema>;
1509
+ export declare const ACTION_TYPE_GET: "get";
1510
+ export declare const ACTION_TYPE_POST: "post";
1511
+ declare const snapGetActionSchema: z.ZodObject<{
1512
+ type: z.ZodLiteral<"get">;
1513
+ }, z.core.$strip>;
1514
+ export type SnapGetAction = z.infer<typeof snapGetActionSchema>;
1368
1515
  declare const snapPostActionSchema: z.ZodObject<{
1369
1516
  fid: z.ZodNumber;
1370
1517
  inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodObject<{
1371
1518
  row: z.ZodNumber;
1372
1519
  col: z.ZodNumber;
1373
1520
  }, z.core.$strict>]>>>;
1521
+ button_index: z.ZodNumber;
1374
1522
  timestamp: z.ZodNumber;
1375
1523
  type: z.ZodLiteral<"post">;
1376
- buttonIndex: z.ZodNumber;
1377
1524
  }, z.core.$strict>;
1378
1525
  export type SnapPostAction = z.infer<typeof snapPostActionSchema>;
1379
- export type SnapAction = {
1380
- type: "get";
1381
- } | SnapPostAction;
1526
+ export declare const snapActionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1527
+ type: z.ZodLiteral<"get">;
1528
+ }, z.core.$strip>, z.ZodObject<{
1529
+ fid: z.ZodNumber;
1530
+ inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodObject<{
1531
+ row: z.ZodNumber;
1532
+ col: z.ZodNumber;
1533
+ }, z.core.$strict>]>>>;
1534
+ button_index: z.ZodNumber;
1535
+ timestamp: z.ZodNumber;
1536
+ type: z.ZodLiteral<"post">;
1537
+ }, z.core.$strict>], "type">;
1538
+ export type SnapAction = z.infer<typeof snapActionSchema>;
1382
1539
  export type SnapContext = {
1383
1540
  action: SnapAction;
1384
1541
  request: Request;
1385
1542
  };
1386
- export type SnapFunction = (ctx: SnapContext) => Promise<SnapResponseInput>;
1543
+ export type SnapFunction = (ctx: SnapContext) => Promise<SnapHandlerResult>;
1387
1544
  export {};
package/dist/schemas.js CHANGED
@@ -368,7 +368,7 @@ const elementsSchema = z
368
368
  }),
369
369
  })
370
370
  .strict();
371
- export const rootSchema = z
371
+ export const snapResponseSchema = z
372
372
  .object({
373
373
  version: z.literal(SPEC_VERSION),
374
374
  page: z
@@ -402,9 +402,9 @@ export const rootSchema = z
402
402
  })
403
403
  .strict();
404
404
  // extra constraints for the first page to make it look nicer
405
- export const firstPageRootSchema = rootSchema.superRefine((root, ctx) => {
406
- const body = root.page.elements.children;
407
- const hasTextTitleOrBody = body.some((el) => el.type === ELEMENT_TYPE.text &&
405
+ export const firstPageResponseSchema = snapResponseSchema.superRefine((response, ctx) => {
406
+ const elements = response.page.elements.children;
407
+ const hasTextTitleOrBody = elements.some((el) => el.type === ELEMENT_TYPE.text &&
408
408
  (el.style === TEXT_STYLE.title || el.style === TEXT_STYLE.body));
409
409
  if (!hasTextTitleOrBody) {
410
410
  ctx.addIssue({
@@ -413,8 +413,8 @@ export const firstPageRootSchema = rootSchema.superRefine((root, ctx) => {
413
413
  path: ["page", "elements", "children"],
414
414
  });
415
415
  }
416
- const hasInteractive = body.some((el) => INTERACTIVE_ELEMENT_TYPES.includes(el.type));
417
- const hasMedia = body.some((el) => MEDIA_ELEMENT_TYPES.includes(el.type));
416
+ const hasInteractive = elements.some((el) => INTERACTIVE_ELEMENT_TYPES.includes(el.type));
417
+ const hasMedia = elements.some((el) => MEDIA_ELEMENT_TYPES.includes(el.type));
418
418
  if (!hasInteractive && !hasMedia) {
419
419
  ctx.addIssue({
420
420
  code: "custom",
@@ -443,10 +443,17 @@ export const payloadSchema = z
443
443
  timestamp: z.number().int(),
444
444
  })
445
445
  .strict();
446
+ export const ACTION_TYPE_GET = "get";
447
+ export const ACTION_TYPE_POST = "post";
448
+ const snapGetActionSchema = z.object({
449
+ type: z.literal(ACTION_TYPE_GET),
450
+ });
446
451
  const snapPostActionSchema = payloadSchema
447
- .omit({ button_index: true })
448
452
  .extend({
449
- type: z.literal("post"),
450
- buttonIndex: payloadSchema.shape.button_index,
453
+ type: z.literal(ACTION_TYPE_POST),
451
454
  })
452
455
  .strict();
456
+ export const snapActionSchema = z.discriminatedUnion("type", [
457
+ snapGetActionSchema,
458
+ snapPostActionSchema,
459
+ ]);
@@ -1,4 +1,4 @@
1
- import { payloadSchema } from "../schemas.js";
1
+ import { ACTION_TYPE_GET, ACTION_TYPE_POST, payloadSchema, } from "../schemas.js";
2
2
  import { decodePayload, verifyJFSRequestBody } from "./verify.js";
3
3
  import { z } from "zod";
4
4
  /** Default replay window per SPEC.md § Replay Protection (5 minutes). */
@@ -26,7 +26,7 @@ export async function parseRequest(request, options = {}) {
26
26
  if (request.method === "GET") {
27
27
  return {
28
28
  success: true,
29
- action: { type: "get" },
29
+ action: { type: ACTION_TYPE_GET },
30
30
  };
31
31
  }
32
32
  const maxSkew = DEFAULT_SNAP_POST_MAX_SKEW_SECONDS;
@@ -81,11 +81,8 @@ export async function parseRequest(request, options = {}) {
81
81
  return {
82
82
  success: true,
83
83
  action: {
84
- type: "post",
85
- fid: body.fid,
86
- inputs: body.inputs,
87
- buttonIndex: body.button_index,
88
- timestamp: body.timestamp,
84
+ type: ACTION_TYPE_POST,
85
+ ...body,
89
86
  },
90
87
  };
91
88
  }
@@ -240,7 +240,7 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
240
240
  snap_post: {
241
241
  description: string;
242
242
  params: z.ZodObject<{
243
- buttonIndex: z.ZodNumber;
243
+ button_index: z.ZodNumber;
244
244
  target: z.ZodString;
245
245
  label: z.ZodOptional<z.ZodString>;
246
246
  style: z.ZodOptional<z.ZodEnum<{
@@ -18,7 +18,7 @@ import { groupProps } from "./group.js";
18
18
  import { stackProps } from "./stack.js";
19
19
  import { actionButtonProps } from "./button.js";
20
20
  const snapPostParams = z.object({
21
- buttonIndex: z.number().int().nonnegative(),
21
+ button_index: z.number().int().nonnegative(),
22
22
  target: z.string(),
23
23
  label: z.string().optional(),
24
24
  style: z.enum(BUTTON_STYLE_VALUES).optional(),
@@ -3,5 +3,5 @@ export type ValidationResult = {
3
3
  valid: boolean;
4
4
  issues: z.core.$ZodIssue[];
5
5
  };
6
- export declare function validatePage(json: unknown): ValidationResult;
7
- export declare function validateFirstPage(json: unknown): ValidationResult;
6
+ export declare function validateSnapResponse(json: unknown): ValidationResult;
7
+ export declare function validateFirstPageResponse(json: unknown): ValidationResult;
package/dist/validator.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { BUTTON_GROUP_STYLE, DEFAULT_BUTTON_LAYOUT, ELEMENT_TYPE, LIMITS, SPACER_SIZE, TEXT_STYLE, } from "./constants.js";
2
- import { firstPageRootSchema, rootSchema, } from "./schemas.js";
2
+ import { firstPageResponseSchema, snapResponseSchema, } from "./schemas.js";
3
3
  /** Not in SPEC — rough px used only for {@link measureHeightBudget}. */
4
4
  const PAGE_HEIGHT_HEURISTIC = {
5
5
  baseChromePx: 32,
@@ -116,8 +116,8 @@ function heightBudgetValidationErrorForRoot(root) {
116
116
  : DEFAULT_BUTTON_LAYOUT;
117
117
  return heightBudgetValidationError(root.page.elements.children, root.page.buttons ?? [], layout);
118
118
  }
119
- export function validatePage(json) {
120
- const parsed = rootSchema.safeParse(json);
119
+ export function validateSnapResponse(json) {
120
+ const parsed = snapResponseSchema.safeParse(json);
121
121
  if (!parsed.success) {
122
122
  return {
123
123
  valid: false,
@@ -130,8 +130,8 @@ export function validatePage(json) {
130
130
  }
131
131
  return { valid: true, issues: [] };
132
132
  }
133
- export function validateFirstPage(json) {
134
- const parsed = firstPageRootSchema.safeParse(json);
133
+ export function validateFirstPageResponse(json) {
134
+ const parsed = firstPageResponseSchema.safeParse(json);
135
135
  if (!parsed.success) {
136
136
  return {
137
137
  valid: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farcaster/snap",
3
- "version": "1.3.3",
3
+ "version": "1.4.1",
4
4
  "description": "Farcaster Snaps 🫰",
5
5
  "repository": {
6
6
  "type": "git",
package/src/constants.ts CHANGED
@@ -85,6 +85,8 @@ export const PALETTE_COLOR = {
85
85
  pink: "pink",
86
86
  } as const;
87
87
 
88
+ export const PALETTE_COLOR_ACCENT = "accent" as const;
89
+
88
90
  export const PALETTE_COLOR_VALUES = [
89
91
  PALETTE_COLOR.gray,
90
92
  PALETTE_COLOR.blue,
@@ -123,7 +125,7 @@ export const PALETTE_DARK_HEX: Record<PaletteColor, string> = {
123
125
  };
124
126
 
125
127
  export const PROGRESS_COLOR_VALUES = [
126
- "accent",
128
+ PALETTE_COLOR_ACCENT,
127
129
  ...PALETTE_COLOR_VALUES,
128
130
  ] as const;
129
131
 
@@ -174,7 +176,7 @@ export const BUTTON_LAYOUT_VALUES = ["stack", "row", "grid"] as const;
174
176
  export const DEFAULT_BUTTON_LAYOUT = BUTTON_LAYOUT_VALUES[0];
175
177
 
176
178
  export const BAR_CHART_COLOR_VALUES = [
177
- "accent",
179
+ PALETTE_COLOR_ACCENT,
178
180
  ...PALETTE_COLOR_VALUES,
179
181
  ] as const;
180
182
 
package/src/index.ts CHANGED
@@ -7,24 +7,30 @@ export {
7
7
  DEFAULT_LIST_STYLE,
8
8
  DEFAULT_SLIDER_STEP,
9
9
  PALETTE_COLOR,
10
+ PALETTE_COLOR_ACCENT,
10
11
  PALETTE_COLOR_VALUES,
11
12
  PALETTE_LIGHT_HEX,
12
13
  PALETTE_DARK_HEX,
13
14
  type PaletteColor,
14
15
  } from "./constants";
15
16
  export {
16
- rootSchema,
17
- firstPageRootSchema,
17
+ snapResponseSchema,
18
+ firstPageResponseSchema,
18
19
  payloadSchema,
20
+ type Button,
21
+ type Element,
22
+ type Elements,
23
+ type GroupChildElement,
19
24
  type SnapAction,
25
+ type SnapPageElementInput,
20
26
  type SnapContext,
21
27
  type SnapResponse,
22
- type SnapResponseInput,
28
+ type SnapHandlerResult,
23
29
  type SnapFunction,
24
30
  type SnapPayload,
25
31
  } from "./schemas";
26
32
  export {
27
- validatePage,
28
- validateFirstPage,
33
+ validateSnapResponse,
34
+ validateFirstPageResponse,
29
35
  type ValidationResult,
30
36
  } from "./validator";
package/src/schemas.ts CHANGED
@@ -391,6 +391,8 @@ const buttonSchema = z
391
391
  }
392
392
  });
393
393
 
394
+ export type Button = z.infer<typeof buttonSchema>;
395
+
394
396
  /** Child elements allowed inside `group` (no media, no nested group) */
395
397
  const groupChildElementSchema = z.discriminatedUnion("type", [
396
398
  textElementSchema,
@@ -405,6 +407,8 @@ const groupChildElementSchema = z.discriminatedUnion("type", [
405
407
  barChartElementSchema,
406
408
  ]);
407
409
 
410
+ export type GroupChildElement = z.infer<typeof groupChildElementSchema>;
411
+
408
412
  const groupElementSchema = z.object({
409
413
  type: z.literal(ELEMENT_TYPE.group),
410
414
  layout: z.enum(GROUP_LAYOUT_VALUES),
@@ -431,6 +435,10 @@ const elementSchema = z.discriminatedUnion("type", [
431
435
  barChartElementSchema,
432
436
  ]);
433
437
 
438
+ export type Element = z.infer<typeof elementSchema>;
439
+
440
+ export type SnapPageElementInput = z.input<typeof elementSchema>;
441
+
434
442
  const elementsSchema = z
435
443
  .object({
436
444
  type: z.literal(PAGE_ROOT_TYPE.stack),
@@ -443,11 +451,9 @@ const elementsSchema = z
443
451
  })
444
452
  .strict();
445
453
 
446
- export type Button = z.infer<typeof buttonSchema>;
447
- export type Element = z.infer<typeof elementSchema>;
448
454
  export type Elements = z.infer<typeof elementsSchema>;
449
455
 
450
- export const rootSchema = z
456
+ export const snapResponseSchema = z
451
457
  .object({
452
458
  version: z.literal(SPEC_VERSION),
453
459
  page: z
@@ -481,37 +487,48 @@ export const rootSchema = z
481
487
  })
482
488
  .strict();
483
489
 
490
+ // canonical snap response type
491
+ export type SnapResponse = z.infer<typeof snapResponseSchema>;
492
+ // what snap handlers may return (keeps optional fields optional)
493
+ export type SnapHandlerResult = z.input<typeof snapResponseSchema>;
494
+
484
495
  // extra constraints for the first page to make it look nicer
485
- export const firstPageRootSchema = rootSchema.superRefine((root, ctx) => {
486
- const body = root.page.elements.children;
487
-
488
- const hasTextTitleOrBody = body.some(
489
- (el) =>
490
- el.type === ELEMENT_TYPE.text &&
491
- (el.style === TEXT_STYLE.title || el.style === TEXT_STYLE.body),
492
- );
493
- if (!hasTextTitleOrBody) {
494
- ctx.addIssue({
495
- code: "custom",
496
- message:
497
- 'first page must have at least one text element with style "title" or "body"',
498
- path: ["page", "elements", "children"],
499
- });
500
- }
496
+ export const firstPageResponseSchema = snapResponseSchema.superRefine(
497
+ (response, ctx) => {
498
+ const elements = response.page.elements.children;
499
+
500
+ const hasTextTitleOrBody = elements.some(
501
+ (el) =>
502
+ el.type === ELEMENT_TYPE.text &&
503
+ (el.style === TEXT_STYLE.title || el.style === TEXT_STYLE.body),
504
+ );
505
+ if (!hasTextTitleOrBody) {
506
+ ctx.addIssue({
507
+ code: "custom",
508
+ message:
509
+ 'first page must have at least one text element with style "title" or "body"',
510
+ path: ["page", "elements", "children"],
511
+ });
512
+ }
501
513
 
502
- const hasInteractive = body.some((el) =>
503
- INTERACTIVE_ELEMENT_TYPES.includes(el.type),
504
- );
505
- const hasMedia = body.some((el) => MEDIA_ELEMENT_TYPES.includes(el.type));
506
- if (!hasInteractive && !hasMedia) {
507
- ctx.addIssue({
508
- code: "custom",
509
- message:
510
- "first page must have at least one interactive element (button_group, slider, text_input, toggle) or media element (image, grid)",
511
- path: ["page", "elements", "children"],
512
- });
513
- }
514
- });
514
+ const hasInteractive = elements.some((el) =>
515
+ INTERACTIVE_ELEMENT_TYPES.includes(el.type),
516
+ );
517
+ const hasMedia = elements.some((el) =>
518
+ MEDIA_ELEMENT_TYPES.includes(el.type),
519
+ );
520
+ if (!hasInteractive && !hasMedia) {
521
+ ctx.addIssue({
522
+ code: "custom",
523
+ message:
524
+ "first page must have at least one interactive element (button_group, slider, text_input, toggle) or media element (image, grid)",
525
+ path: ["page", "elements", "children"],
526
+ });
527
+ }
528
+ },
529
+ );
530
+
531
+ export type FirstPageResponse = z.infer<typeof firstPageResponseSchema>;
515
532
 
516
533
  const postInputValueSchema = z.union([
517
534
  z.string(),
@@ -535,30 +552,35 @@ export const payloadSchema = z
535
552
  })
536
553
  .strict();
537
554
 
538
- export type SnapResponse = z.infer<typeof rootSchema>;
539
- export type SnapResponseInput = z.input<typeof rootSchema>;
540
- export type SnapPage = SnapResponse["page"];
541
555
  export type SnapPayload = z.infer<typeof payloadSchema>;
542
556
 
557
+ export const ACTION_TYPE_GET = "get" as const;
558
+ export const ACTION_TYPE_POST = "post" as const;
559
+
560
+ const snapGetActionSchema = z.object({
561
+ type: z.literal(ACTION_TYPE_GET),
562
+ });
563
+
564
+ export type SnapGetAction = z.infer<typeof snapGetActionSchema>;
565
+
543
566
  const snapPostActionSchema = payloadSchema
544
- .omit({ button_index: true })
545
567
  .extend({
546
- type: z.literal("post"),
547
- buttonIndex: payloadSchema.shape.button_index,
568
+ type: z.literal(ACTION_TYPE_POST),
548
569
  })
549
570
  .strict();
550
571
 
551
572
  export type SnapPostAction = z.infer<typeof snapPostActionSchema>;
552
573
 
553
- export type SnapAction =
554
- | {
555
- type: "get";
556
- }
557
- | SnapPostAction;
574
+ export const snapActionSchema = z.discriminatedUnion("type", [
575
+ snapGetActionSchema,
576
+ snapPostActionSchema,
577
+ ]);
578
+
579
+ export type SnapAction = z.infer<typeof snapActionSchema>;
558
580
 
559
581
  export type SnapContext = {
560
582
  action: SnapAction;
561
583
  request: Request;
562
584
  };
563
585
 
564
- export type SnapFunction = (ctx: SnapContext) => Promise<SnapResponseInput>;
586
+ export type SnapFunction = (ctx: SnapContext) => Promise<SnapHandlerResult>;
@@ -1,4 +1,9 @@
1
- import { payloadSchema, type SnapAction } from "../schemas";
1
+ import {
2
+ ACTION_TYPE_GET,
3
+ ACTION_TYPE_POST,
4
+ payloadSchema,
5
+ type SnapAction,
6
+ } from "../schemas";
2
7
  import { decodePayload, verifyJFSRequestBody } from "./verify";
3
8
  import { z } from "zod";
4
9
 
@@ -66,7 +71,7 @@ export async function parseRequest(
66
71
  if (request.method === "GET") {
67
72
  return {
68
73
  success: true,
69
- action: { type: "get" },
74
+ action: { type: ACTION_TYPE_GET },
70
75
  };
71
76
  }
72
77
 
@@ -129,11 +134,8 @@ export async function parseRequest(
129
134
  return {
130
135
  success: true,
131
136
  action: {
132
- type: "post",
133
- fid: body.fid,
134
- inputs: body.inputs,
135
- buttonIndex: body.button_index,
136
- timestamp: body.timestamp,
137
+ type: ACTION_TYPE_POST,
138
+ ...body,
137
139
  },
138
140
  };
139
141
  }
package/src/ui/catalog.ts CHANGED
@@ -19,7 +19,7 @@ import { stackProps } from "./stack.js";
19
19
  import { actionButtonProps } from "./button.js";
20
20
 
21
21
  const snapPostParams = z.object({
22
- buttonIndex: z.number().int().nonnegative(),
22
+ button_index: z.number().int().nonnegative(),
23
23
  target: z.string(),
24
24
  label: z.string().optional(),
25
25
  style: z.enum(BUTTON_STYLE_VALUES).optional(),
package/src/validator.ts CHANGED
@@ -8,13 +8,13 @@ import {
8
8
  TEXT_STYLE,
9
9
  } from "./constants";
10
10
  import {
11
- firstPageRootSchema,
12
- rootSchema,
11
+ firstPageResponseSchema,
12
+ snapResponseSchema,
13
13
  type Button,
14
14
  type Element,
15
15
  } from "./schemas";
16
16
 
17
- type SnapRoot = z.infer<typeof rootSchema>;
17
+ type SnapRoot = z.infer<typeof snapResponseSchema>;
18
18
 
19
19
  export type ValidationResult = {
20
20
  valid: boolean;
@@ -158,8 +158,8 @@ function heightBudgetValidationErrorForRoot(
158
158
  );
159
159
  }
160
160
 
161
- export function validatePage(json: unknown): ValidationResult {
162
- const parsed = rootSchema.safeParse(json);
161
+ export function validateSnapResponse(json: unknown): ValidationResult {
162
+ const parsed = snapResponseSchema.safeParse(json);
163
163
  if (!parsed.success) {
164
164
  return {
165
165
  valid: false,
@@ -175,8 +175,8 @@ export function validatePage(json: unknown): ValidationResult {
175
175
  return { valid: true, issues: [] };
176
176
  }
177
177
 
178
- export function validateFirstPage(json: unknown): ValidationResult {
179
- const parsed = firstPageRootSchema.safeParse(json);
178
+ export function validateFirstPageResponse(json: unknown): ValidationResult {
179
+ const parsed = firstPageResponseSchema.safeParse(json);
180
180
  if (!parsed.success) {
181
181
  return {
182
182
  valid: false,