@jon49/sw 0.15.1 → 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/bin/lib/esbuild-plugins.ts +1 -1
- package/lib/routes.middleware.ts +236 -231
- package/package.json +1 -1
|
@@ -25,7 +25,7 @@ async function setPagesToReturn(targetDirectory: string) {
|
|
|
25
25
|
await Promise.all(files.map(async (file: string) => {
|
|
26
26
|
let filename = path.join(targetDirectory, file)
|
|
27
27
|
let content = await readFile(filename, "utf-8")
|
|
28
|
-
let matched = content.match(/,(\w*)=(\w*);export/)
|
|
28
|
+
let matched = content.match(/,(\w*)=([\w|\$]*);export/)
|
|
29
29
|
if (matched) {
|
|
30
30
|
let [, name, value] = matched
|
|
31
31
|
content = content.replace(`,${name}=${value}`, "")
|
package/lib/routes.middleware.ts
CHANGED
|
@@ -2,241 +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
|
-
...result,
|
|
188
|
-
type: "text/html",
|
|
189
|
-
}
|
|
190
|
-
} else {
|
|
191
|
-
if ("json" in result) {
|
|
192
|
-
result.body = JSON.stringify(result.json)
|
|
193
|
-
result.headers = {
|
|
194
|
-
...result.headers,
|
|
195
|
-
}
|
|
196
|
-
} else if (result.messages?.length > 0) {
|
|
197
|
-
return {
|
|
198
|
-
...result,
|
|
199
|
-
status: result.status || 204,
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return {
|
|
203
|
-
...result,
|
|
204
|
-
type: "application/json"
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
} catch (error) {
|
|
208
|
-
console.error(`"${method}" error:`, error, "\nURL:", url);
|
|
209
|
-
if (isPost) {
|
|
210
|
-
return { error: "An error occurred while processing your request." }
|
|
211
|
-
} else {
|
|
212
|
-
return { error: "Not found!" }
|
|
213
|
-
}
|
|
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,
|
|
214
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
|
+
}
|
|
215
219
|
}
|
|
220
|
+
}
|
|
216
221
|
|
|
217
|
-
|
|
222
|
+
return { error: "Not Found!" }
|
|
218
223
|
}
|
|
219
224
|
|
|
220
225
|
async function getData(req: Request) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
} else {
|
|
233
|
-
o[key] = val
|
|
234
|
-
}
|
|
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]
|
|
235
236
|
}
|
|
236
|
-
|
|
237
|
-
o =
|
|
237
|
+
} else {
|
|
238
|
+
o[key] = val
|
|
239
|
+
}
|
|
238
240
|
}
|
|
239
|
-
|
|
241
|
+
} else if (req.headers.get("Content-Type")?.includes("json")) {
|
|
242
|
+
o = await req.json()
|
|
243
|
+
}
|
|
244
|
+
return o
|
|
240
245
|
}
|
|
241
246
|
|
|
242
247
|
/**
|
|
@@ -244,88 +249,88 @@ async function getData(req: Request) {
|
|
|
244
249
|
* /my/script.js -> /my/script.js
|
|
245
250
|
*/
|
|
246
251
|
function normalizeUrl(url: string): URL {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
252
|
+
let uri = new URL(url)
|
|
253
|
+
let path = uri.pathname
|
|
254
|
+
!uri.pathname.endsWith("/") && (uri.pathname = isFile(path) ? path : path + "/")
|
|
255
|
+
return uri
|
|
251
256
|
}
|
|
252
257
|
|
|
253
258
|
function isFile(s: string) {
|
|
254
|
-
|
|
259
|
+
return s.lastIndexOf("/") < s.lastIndexOf(".")
|
|
255
260
|
}
|
|
256
261
|
|
|
257
262
|
async function cacheResponse(url: string, req?: Request | undefined): Promise<Response> {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
|
270
275
|
}
|
|
271
276
|
|
|
272
277
|
export interface RouteGetArgs {
|
|
273
|
-
|
|
274
|
-
|
|
278
|
+
req: Request
|
|
279
|
+
query: any
|
|
275
280
|
}
|
|
276
281
|
|
|
277
282
|
export interface RoutePostArgs {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
283
|
+
query: any
|
|
284
|
+
data: any
|
|
285
|
+
req: Request
|
|
281
286
|
}
|
|
282
287
|
|
|
283
288
|
export interface RouteObjectReturn {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
289
|
+
body?: string | AsyncGenerator | null
|
|
290
|
+
status?: number
|
|
291
|
+
headers?: any
|
|
292
|
+
events?: any
|
|
293
|
+
message?: string
|
|
294
|
+
messages?: string[]
|
|
295
|
+
json?: any
|
|
291
296
|
}
|
|
292
297
|
|
|
293
298
|
export type RouteHandler<T> = {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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)
|
|
307
312
|
}
|
|
308
313
|
|
|
309
314
|
export interface RoutePostHandler {
|
|
310
|
-
|
|
315
|
+
[handler: string]: RouteHandler<RoutePostArgs>
|
|
311
316
|
}
|
|
312
317
|
|
|
313
318
|
export interface RouteGetHandler {
|
|
314
|
-
|
|
319
|
+
[handler: string]: RouteHandler<RouteGetArgs>
|
|
315
320
|
}
|
|
316
321
|
|
|
317
322
|
interface Route_ {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
323
|
+
route: RegExp | ((a: URL) => boolean)
|
|
324
|
+
file?: string
|
|
325
|
+
get?: RouteHandler<RouteGetArgs> | RouteGetHandler
|
|
326
|
+
post?: RouteHandler<RoutePostArgs> | RoutePostHandler
|
|
322
327
|
}
|
|
323
328
|
|
|
324
329
|
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
330
|
+
Pick<T, Exclude<keyof T, Keys>>
|
|
331
|
+
& {
|
|
332
|
+
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
|
|
333
|
+
}[Keys]
|
|
329
334
|
|
|
330
335
|
export type Route = RequireAtLeastOne<Route_, "file" | "get" | "post">
|
|
331
336
|
export type RoutePage = Pick<Route_, "get" | "post">
|