@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.
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +3 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/schemas.d.ts +169 -12
- package/dist/schemas.js +16 -9
- package/dist/server/parseRequest.js +4 -7
- package/dist/ui/catalog.d.ts +1 -1
- package/dist/ui/catalog.js +1 -1
- package/dist/validator.d.ts +2 -2
- package/dist/validator.js +5 -5
- package/package.json +1 -1
- package/src/constants.ts +4 -2
- package/src/index.ts +11 -5
- package/src/schemas.ts +66 -44
- package/src/server/parseRequest.ts +9 -7
- package/src/ui/catalog.ts +1 -1
- package/src/validator.ts +7 -7
package/dist/constants.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 {
|
|
3
|
-
export {
|
|
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 {
|
|
3
|
-
export {
|
|
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
|
|
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
|
|
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
|
|
1380
|
-
type: "get"
|
|
1381
|
-
}
|
|
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<
|
|
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
|
|
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
|
|
406
|
-
const
|
|
407
|
-
const hasTextTitleOrBody =
|
|
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 =
|
|
417
|
-
const hasMedia =
|
|
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(
|
|
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:
|
|
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:
|
|
85
|
-
|
|
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
|
}
|
package/dist/ui/catalog.d.ts
CHANGED
|
@@ -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
|
-
|
|
243
|
+
button_index: z.ZodNumber;
|
|
244
244
|
target: z.ZodString;
|
|
245
245
|
label: z.ZodOptional<z.ZodString>;
|
|
246
246
|
style: z.ZodOptional<z.ZodEnum<{
|
package/dist/ui/catalog.js
CHANGED
|
@@ -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
|
-
|
|
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(),
|
package/dist/validator.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export type ValidationResult = {
|
|
|
3
3
|
valid: boolean;
|
|
4
4
|
issues: z.core.$ZodIssue[];
|
|
5
5
|
};
|
|
6
|
-
export declare function
|
|
7
|
-
export declare function
|
|
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 {
|
|
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
|
|
120
|
-
const parsed =
|
|
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
|
|
134
|
-
const parsed =
|
|
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
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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
|
|
28
|
+
type SnapHandlerResult,
|
|
23
29
|
type SnapFunction,
|
|
24
30
|
type SnapPayload,
|
|
25
31
|
} from "./schemas";
|
|
26
32
|
export {
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
(
|
|
490
|
-
el
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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(
|
|
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
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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<
|
|
586
|
+
export type SnapFunction = (ctx: SnapContext) => Promise<SnapHandlerResult>;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
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:
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
11
|
+
firstPageResponseSchema,
|
|
12
|
+
snapResponseSchema,
|
|
13
13
|
type Button,
|
|
14
14
|
type Element,
|
|
15
15
|
} from "./schemas";
|
|
16
16
|
|
|
17
|
-
type SnapRoot = z.infer<typeof
|
|
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
|
|
162
|
-
const parsed =
|
|
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
|
|
179
|
-
const parsed =
|
|
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,
|