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