@jon49/sw 0.15.2 → 0.15.3
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/lib/routes.middleware.ts +236 -230
- package/package.json +1 -1
package/lib/routes.middleware.ts
CHANGED
|
@@ -2,240 +2,246 @@ import { SwResponse } from "./sw-framework.js"
|
|
|
2
2
|
import { isHtml } from "./utils.js"
|
|
3
3
|
|
|
4
4
|
let { links, globalDb } =
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
self.sw as { links: { file: string, url: string }[], html: Function, db: any, globalDb: any }
|
|
7
7
|
|
|
8
8
|
if (!links) {
|
|
9
|
-
|
|
9
|
+
console.error("Expecting links defined with `self.sw.links`, but found none.")
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function redirect(req: Request) {
|
|
13
|
-
|
|
13
|
+
return Response.redirect(req.referrer, 303)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const searchParamsHandler = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
return obj.searchParams.get(prop)
|
|
17
|
+
get(obj: any, prop: string) {
|
|
18
|
+
if (prop === "_url") {
|
|
19
|
+
return obj
|
|
22
20
|
}
|
|
21
|
+
return obj.searchParams.get(prop)
|
|
22
|
+
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function searchParams<TReturn>(url: URL): TReturn & { _url: URL } {
|
|
26
|
-
|
|
26
|
+
return new Proxy(url, searchParamsHandler)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
interface ResponseOptions {
|
|
30
|
-
|
|
30
|
+
handleErrors?: Function
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export let options: ResponseOptions = {}
|
|
34
34
|
|
|
35
35
|
// @ts-ignore
|
|
36
36
|
export async function useRoutes(req: Request, res: SwResponse, ctx: any): Promise<void> {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error("Get Response Error", error)
|
|
46
|
-
res.error = "Oops something happened which shouldn't have!"
|
|
37
|
+
try {
|
|
38
|
+
const url = normalizeUrl(req.url)
|
|
39
|
+
if (!url.pathname.startsWith("/web/")) {
|
|
40
|
+
res.response = await fetch(req)
|
|
41
|
+
} else {
|
|
42
|
+
Object.assign(res, await executeHandler({ url, req, ctx }))
|
|
47
43
|
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error("Get Response Error", error)
|
|
46
|
+
res.error = "Oops something happened which shouldn't have!"
|
|
47
|
+
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function call(fn: Function | undefined, args: any) {
|
|
51
|
-
|
|
51
|
+
return fn instanceof Function && fn(args)
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
const methodTypes = ['get', 'post'] as const
|
|
55
55
|
type MethodTypes = typeof methodTypes[number] | null
|
|
56
56
|
|
|
57
57
|
function isMethod(method: unknown) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
if (typeof method === "string" && methodTypes.includes(<any>method)) {
|
|
59
|
+
return method as MethodTypes
|
|
60
|
+
}
|
|
61
|
+
return null
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
let cache = new Map<string, any>()
|
|
65
65
|
export async function findRoute(url: URL, method: unknown) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
let validMethod: MethodTypes = isMethod(method)
|
|
67
|
+
if (validMethod) {
|
|
68
|
+
// @ts-ignore
|
|
69
|
+
if (!self.sw?.routes) {
|
|
70
|
+
console.error("Expecting routes defined with `self.sw.routes`, but found none.")
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
for (const r of self.sw.routes) {
|
|
75
|
+
// @ts-ignore
|
|
76
|
+
if (r.file
|
|
77
|
+
&& (r.route instanceof RegExp && r.route.test(url.pathname)
|
|
78
|
+
|| (r.route instanceof Function && r.route(url)))) {
|
|
79
|
+
let file = links?.find(x => x.url === r.file)?.file
|
|
80
|
+
// Load file
|
|
81
|
+
if (!file) {
|
|
82
|
+
console.error(`"${r.file}" not found in links!`)
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
if (!cache.has(file)) {
|
|
86
|
+
let cachedResponse = await cacheResponse(file)
|
|
87
|
+
if (!cacheResponse) {
|
|
88
|
+
console.error(`"${file}" not found in cache!`)
|
|
71
89
|
return null
|
|
90
|
+
}
|
|
91
|
+
let text = await cachedResponse.text()
|
|
92
|
+
let func = new Function(text)()
|
|
93
|
+
cache.set(file, func)
|
|
72
94
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
&& (r.route instanceof RegExp && r.route.test(url.pathname)
|
|
78
|
-
|| (r.route instanceof Function && r.route(url)))) {
|
|
79
|
-
let file = links?.find(x => x.url === r.file)?.file
|
|
80
|
-
// Load file
|
|
81
|
-
if (!file) {
|
|
82
|
-
console.error(`"${r.file}" not found in links!`)
|
|
83
|
-
return null
|
|
84
|
-
}
|
|
85
|
-
if (!cache.has(file)) {
|
|
86
|
-
let cachedResponse = await cacheResponse(file)
|
|
87
|
-
if (!cacheResponse) {
|
|
88
|
-
console.error(`"${file}" not found in cache!`)
|
|
89
|
-
return null
|
|
90
|
-
}
|
|
91
|
-
let text = await cachedResponse.text()
|
|
92
|
-
let func = new Function(text)()
|
|
93
|
-
cache.set(file, func)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let routeDef = cache.get(file)
|
|
97
|
-
if (routeDef[validMethod]) {
|
|
98
|
-
return routeDef[validMethod]
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (r[validMethod]
|
|
103
|
-
&& (r.route instanceof RegExp && r.route.test(url.pathname)
|
|
104
|
-
|| (r.route instanceof Function && r.route(url)))) {
|
|
105
|
-
return r[validMethod]
|
|
106
|
-
}
|
|
95
|
+
|
|
96
|
+
let routeDef = cache.get(file)
|
|
97
|
+
if (routeDef[validMethod]) {
|
|
98
|
+
return routeDef[validMethod]
|
|
107
99
|
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (r[validMethod]
|
|
103
|
+
&& (r.route instanceof RegExp && r.route.test(url.pathname)
|
|
104
|
+
|| (r.route instanceof Function && r.route(url)))) {
|
|
105
|
+
return r[validMethod]
|
|
106
|
+
}
|
|
108
107
|
}
|
|
109
|
-
|
|
108
|
+
}
|
|
109
|
+
return null
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
interface ExectuteHandlerOptions {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
url: URL
|
|
114
|
+
req: Request
|
|
115
|
+
ctx: any
|
|
116
116
|
}
|
|
117
117
|
async function executeHandler({ url, req, ctx }: ExectuteHandlerOptions): Promise<SwResponse> {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
let method = req.method.toLowerCase()
|
|
119
|
+
let isPost = method === "post"
|
|
120
|
+
if (!isPost) {
|
|
121
|
+
if (!url.pathname.endsWith("/")) {
|
|
122
|
+
return { response: await cacheResponse(url.pathname, req) }
|
|
123
|
+
}
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
125
|
+
if (url.searchParams.get("login") === "success") {
|
|
126
|
+
await globalDb.setLoggedIn(true)
|
|
128
127
|
}
|
|
128
|
+
}
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
let handlers =
|
|
131
|
+
<RouteHandler<RouteGetArgs | RoutePostArgs> | null>
|
|
132
|
+
await findRoute(url, method)
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
type: "text/html",
|
|
188
|
-
}
|
|
189
|
-
} else {
|
|
190
|
-
if ("json" in result) {
|
|
191
|
-
result.body = JSON.stringify(result.json)
|
|
192
|
-
result.headers = {
|
|
193
|
-
...result.headers,
|
|
194
|
-
}
|
|
195
|
-
} else if (result.messages?.length > 0) {
|
|
196
|
-
return {
|
|
197
|
-
...result,
|
|
198
|
-
status: result.status || 204,
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return {
|
|
202
|
-
...result,
|
|
203
|
-
type: "application/json"
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
} catch (error) {
|
|
207
|
-
console.error(`"${method}" error:`, error, "\nURL:", url);
|
|
208
|
-
if (isPost) {
|
|
209
|
-
return { error: "An error occurred while processing your request." }
|
|
210
|
-
} else {
|
|
211
|
-
return { error: "Not found!" }
|
|
212
|
-
}
|
|
134
|
+
// @ts-ignore
|
|
135
|
+
if (handlers) {
|
|
136
|
+
try {
|
|
137
|
+
let messages: string[] = []
|
|
138
|
+
const data = await getData(req)
|
|
139
|
+
let query = searchParams<{ handler?: string }>(url)
|
|
140
|
+
let args = { req, data, query }
|
|
141
|
+
let result
|
|
142
|
+
try {
|
|
143
|
+
result = await (
|
|
144
|
+
handlers instanceof Function
|
|
145
|
+
? handlers(args)
|
|
146
|
+
: (call(handlers[query.handler ?? ""], args)
|
|
147
|
+
|| call(handlers[method], args)
|
|
148
|
+
|| Promise.reject("I'm sorry, I didn't understand where to route your request.")))
|
|
149
|
+
} catch (e: any) {
|
|
150
|
+
if (typeof e === "string") {
|
|
151
|
+
e = [e]
|
|
152
|
+
}
|
|
153
|
+
if ("message" in e) {
|
|
154
|
+
e = [e.message]
|
|
155
|
+
}
|
|
156
|
+
if (Array.isArray(e)) {
|
|
157
|
+
ctx.messages = e
|
|
158
|
+
return {
|
|
159
|
+
status: 204,
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
throw e
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!result) {
|
|
167
|
+
return isPost
|
|
168
|
+
? { response: redirect(req) }
|
|
169
|
+
: { error: "Not found!" }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (isPost && result.message == null) {
|
|
173
|
+
messages.push("Saved!")
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (result.message?.length > 0) {
|
|
177
|
+
messages.push(result.message)
|
|
178
|
+
} else if (result.messages?.length > 0) {
|
|
179
|
+
messages.push(...result.messages)
|
|
180
|
+
}
|
|
181
|
+
ctx.messages = result.messages = messages
|
|
182
|
+
ctx.events = result.events
|
|
183
|
+
|
|
184
|
+
if (isHtml(result)) {
|
|
185
|
+
return {
|
|
186
|
+
body: result,
|
|
213
187
|
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (isHtml(result.body)) {
|
|
191
|
+
return {
|
|
192
|
+
...result,
|
|
193
|
+
type: "text/html",
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
if ("json" in result) {
|
|
197
|
+
result.body = JSON.stringify(result.json)
|
|
198
|
+
result.headers = {
|
|
199
|
+
...result.headers,
|
|
200
|
+
}
|
|
201
|
+
} else if (result.messages?.length > 0) {
|
|
202
|
+
return {
|
|
203
|
+
...result,
|
|
204
|
+
status: result.status || 204,
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
...result,
|
|
209
|
+
type: "application/json"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(`"${method}" error:`, error, "\nURL:", url);
|
|
214
|
+
if (isPost) {
|
|
215
|
+
return { error: "An error occurred while processing your request." }
|
|
216
|
+
} else {
|
|
217
|
+
return { error: "Not found!" }
|
|
218
|
+
}
|
|
214
219
|
}
|
|
220
|
+
}
|
|
215
221
|
|
|
216
|
-
|
|
222
|
+
return { error: "Not Found!" }
|
|
217
223
|
}
|
|
218
224
|
|
|
219
225
|
async function getData(req: Request) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
} else {
|
|
232
|
-
o[key] = val
|
|
233
|
-
}
|
|
226
|
+
let o: any = {}
|
|
227
|
+
if (req.headers.get("content-type")?.includes("application/x-www-form-urlencoded")) {
|
|
228
|
+
const formData = await req.formData()
|
|
229
|
+
for (let [key, val] of formData.entries()) {
|
|
230
|
+
if (key.endsWith("[]")) {
|
|
231
|
+
key = key.slice(0, -2)
|
|
232
|
+
if (key in o) {
|
|
233
|
+
o[key].push(val)
|
|
234
|
+
} else {
|
|
235
|
+
o[key] = [val]
|
|
234
236
|
}
|
|
235
|
-
|
|
236
|
-
o =
|
|
237
|
+
} else {
|
|
238
|
+
o[key] = val
|
|
239
|
+
}
|
|
237
240
|
}
|
|
238
|
-
|
|
241
|
+
} else if (req.headers.get("Content-Type")?.includes("json")) {
|
|
242
|
+
o = await req.json()
|
|
243
|
+
}
|
|
244
|
+
return o
|
|
239
245
|
}
|
|
240
246
|
|
|
241
247
|
/**
|
|
@@ -243,88 +249,88 @@ async function getData(req: Request) {
|
|
|
243
249
|
* /my/script.js -> /my/script.js
|
|
244
250
|
*/
|
|
245
251
|
function normalizeUrl(url: string): URL {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
252
|
+
let uri = new URL(url)
|
|
253
|
+
let path = uri.pathname
|
|
254
|
+
!uri.pathname.endsWith("/") && (uri.pathname = isFile(path) ? path : path + "/")
|
|
255
|
+
return uri
|
|
250
256
|
}
|
|
251
257
|
|
|
252
258
|
function isFile(s: string) {
|
|
253
|
-
|
|
259
|
+
return s.lastIndexOf("/") < s.lastIndexOf(".")
|
|
254
260
|
}
|
|
255
261
|
|
|
256
262
|
async function cacheResponse(url: string, req?: Request | undefined): Promise<Response> {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
263
|
+
url = links?.find(x => x.url === url)?.file || url
|
|
264
|
+
const match = await caches.match(url)
|
|
265
|
+
if (match) return match
|
|
266
|
+
const res = await fetch(req || url)
|
|
267
|
+
if (!res || res.status !== 200 || res.type !== "basic") return res
|
|
268
|
+
const responseToCache = res.clone()
|
|
269
|
+
// @ts-ignore
|
|
270
|
+
let version: string = self.sw?.version
|
|
271
|
+
?? (console.warn("The version number is not available, expected glboal value `self.sw.version`."), "")
|
|
272
|
+
const cache = await caches.open(version)
|
|
273
|
+
cache.put(url, responseToCache)
|
|
274
|
+
return res
|
|
269
275
|
}
|
|
270
276
|
|
|
271
277
|
export interface RouteGetArgs {
|
|
272
|
-
|
|
273
|
-
|
|
278
|
+
req: Request
|
|
279
|
+
query: any
|
|
274
280
|
}
|
|
275
281
|
|
|
276
282
|
export interface RoutePostArgs {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
283
|
+
query: any
|
|
284
|
+
data: any
|
|
285
|
+
req: Request
|
|
280
286
|
}
|
|
281
287
|
|
|
282
288
|
export interface RouteObjectReturn {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
289
|
+
body?: string | AsyncGenerator | null
|
|
290
|
+
status?: number
|
|
291
|
+
headers?: any
|
|
292
|
+
events?: any
|
|
293
|
+
message?: string
|
|
294
|
+
messages?: string[]
|
|
295
|
+
json?: any
|
|
290
296
|
}
|
|
291
297
|
|
|
292
298
|
export type RouteHandler<T> = {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
299
|
+
(options: T): Promise<
|
|
300
|
+
AsyncGenerator
|
|
301
|
+
| Response
|
|
302
|
+
| RouteObjectReturn
|
|
303
|
+
| undefined
|
|
304
|
+
| null
|
|
305
|
+
| void> | (
|
|
306
|
+
AsyncGenerator
|
|
307
|
+
| Response
|
|
308
|
+
| RouteObjectReturn
|
|
309
|
+
| undefined
|
|
310
|
+
| null
|
|
311
|
+
| void)
|
|
306
312
|
}
|
|
307
313
|
|
|
308
314
|
export interface RoutePostHandler {
|
|
309
|
-
|
|
315
|
+
[handler: string]: RouteHandler<RoutePostArgs>
|
|
310
316
|
}
|
|
311
317
|
|
|
312
318
|
export interface RouteGetHandler {
|
|
313
|
-
|
|
319
|
+
[handler: string]: RouteHandler<RouteGetArgs>
|
|
314
320
|
}
|
|
315
321
|
|
|
316
322
|
interface Route_ {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
323
|
+
route: RegExp | ((a: URL) => boolean)
|
|
324
|
+
file?: string
|
|
325
|
+
get?: RouteHandler<RouteGetArgs> | RouteGetHandler
|
|
326
|
+
post?: RouteHandler<RoutePostArgs> | RoutePostHandler
|
|
321
327
|
}
|
|
322
328
|
|
|
323
329
|
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
330
|
+
Pick<T, Exclude<keyof T, Keys>>
|
|
331
|
+
& {
|
|
332
|
+
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
|
|
333
|
+
}[Keys]
|
|
328
334
|
|
|
329
335
|
export type Route = RequireAtLeastOne<Route_, "file" | "get" | "post">
|
|
330
336
|
export type RoutePage = Pick<Route_, "get" | "post">
|