@chidchanun/bcp 0.1.10 → 0.1.12

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.
@@ -0,0 +1,271 @@
1
+ import {
2
+ copySetCookieHeaders,
3
+ } from "./navigation-response.js";
4
+
5
+ import {
6
+ applyResponseCookies,
7
+ } from "./request-context.js";
8
+
9
+ import type {
10
+ PageActionExecution,
11
+ } from "./form-action.js";
12
+
13
+ export interface CompletedPageActionData {
14
+ kind: "data";
15
+ data: unknown;
16
+ setCookies: string[];
17
+ }
18
+
19
+ export interface CompletedPageActionResponse {
20
+ kind: "response";
21
+ response: Response;
22
+ }
23
+
24
+ export type CompletedPageAction =
25
+ | CompletedPageActionData
26
+ | CompletedPageActionResponse;
27
+
28
+ export interface FormActionDataPayload {
29
+ kind: "data";
30
+ data: unknown;
31
+ }
32
+
33
+ export interface FormActionRedirectPayload {
34
+ kind: "redirect";
35
+ redirect: {
36
+ location: string;
37
+ status: number;
38
+ };
39
+ }
40
+
41
+ export interface FormActionErrorPayload {
42
+ kind: "error";
43
+ error: {
44
+ message: string;
45
+ };
46
+ }
47
+
48
+ export function completePageActionExecution(
49
+ execution: PageActionExecution
50
+ ): CompletedPageAction {
51
+ if (
52
+ execution.response
53
+ ) {
54
+ return {
55
+ kind:
56
+ "response",
57
+ response:
58
+ applyResponseCookies(
59
+ execution.response
60
+ ),
61
+ };
62
+ }
63
+
64
+ const carrier =
65
+ applyResponseCookies(
66
+ new Response(
67
+ null,
68
+ {
69
+ status: 204,
70
+ }
71
+ )
72
+ );
73
+
74
+ return {
75
+ kind:
76
+ "data",
77
+ data:
78
+ execution.data,
79
+ setCookies:
80
+ carrier.headers
81
+ .getSetCookie(),
82
+ };
83
+ }
84
+
85
+ export function createFormActionTransportResponse(
86
+ result: CompletedPageAction,
87
+ target: URL,
88
+ enhanced: boolean
89
+ ): Response {
90
+ if (
91
+ result.kind ===
92
+ "response"
93
+ ) {
94
+ if (enhanced) {
95
+ const redirect =
96
+ createRedirectPayloadResponse(
97
+ result.response
98
+ );
99
+
100
+ if (redirect) {
101
+ return redirect;
102
+ }
103
+ }
104
+
105
+ return result.response;
106
+ }
107
+
108
+ if (enhanced) {
109
+ const headers =
110
+ new Headers();
111
+
112
+ for (
113
+ const cookie
114
+ of result.setCookies
115
+ ) {
116
+ headers.append(
117
+ "Set-Cookie",
118
+ cookie
119
+ );
120
+ }
121
+
122
+ return createFormActionJsonResponse(
123
+ {
124
+ kind:
125
+ "data",
126
+ data:
127
+ result.data,
128
+ } satisfies FormActionDataPayload,
129
+ {
130
+ status: 200,
131
+ headers,
132
+ }
133
+ );
134
+ }
135
+
136
+ const headers =
137
+ new Headers({
138
+ Location:
139
+ `${target.pathname}${target.search}${target.hash}`,
140
+ });
141
+
142
+ for (
143
+ const cookie
144
+ of result.setCookies
145
+ ) {
146
+ headers.append(
147
+ "Set-Cookie",
148
+ cookie
149
+ );
150
+ }
151
+
152
+ return new Response(
153
+ null,
154
+ {
155
+ status: 303,
156
+ headers,
157
+ }
158
+ );
159
+ }
160
+
161
+ export function createFormActionErrorResponse(
162
+ message: string,
163
+ status = 500
164
+ ): Response {
165
+ return createFormActionJsonResponse(
166
+ {
167
+ kind:
168
+ "error",
169
+ error: {
170
+ message,
171
+ },
172
+ } satisfies FormActionErrorPayload,
173
+ {
174
+ status,
175
+ }
176
+ );
177
+ }
178
+
179
+ export function createFormActionJsonResponse(
180
+ value: unknown,
181
+ init: ResponseInit = {}
182
+ ): Response {
183
+ const headers =
184
+ new Headers(
185
+ init.headers
186
+ );
187
+
188
+ headers.set(
189
+ "Cache-Control",
190
+ "no-store"
191
+ );
192
+ headers.set(
193
+ "X-Content-Type-Options",
194
+ "nosniff"
195
+ );
196
+
197
+ return Response.json(
198
+ value,
199
+ {
200
+ ...init,
201
+ headers,
202
+ }
203
+ );
204
+ }
205
+
206
+ export function isEnhancedFormActionRequest(
207
+ headers: Headers
208
+ ): boolean {
209
+ return (
210
+ headers.get(
211
+ "x-bcp-action"
212
+ ) === "1"
213
+ );
214
+ }
215
+
216
+ function createRedirectPayloadResponse(
217
+ response: Response
218
+ ): Response | null {
219
+ if (
220
+ !isRedirectStatus(
221
+ response.status
222
+ )
223
+ ) {
224
+ return null;
225
+ }
226
+
227
+ const location =
228
+ response.headers.get(
229
+ "location"
230
+ );
231
+
232
+ if (!location) {
233
+ return null;
234
+ }
235
+
236
+ const headers =
237
+ new Headers();
238
+
239
+ copySetCookieHeaders(
240
+ response.headers,
241
+ headers
242
+ );
243
+
244
+ return createFormActionJsonResponse(
245
+ {
246
+ kind:
247
+ "redirect",
248
+ redirect: {
249
+ location,
250
+ status:
251
+ response.status,
252
+ },
253
+ } satisfies FormActionRedirectPayload,
254
+ {
255
+ status: 200,
256
+ headers,
257
+ }
258
+ );
259
+ }
260
+
261
+ function isRedirectStatus(
262
+ status: number
263
+ ): boolean {
264
+ return (
265
+ status === 301 ||
266
+ status === 302 ||
267
+ status === 303 ||
268
+ status === 307 ||
269
+ status === 308
270
+ );
271
+ }