@jon49/sw 0.14.7 → 0.15.0
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/file-mapper.ts +69 -70
- package/lib/new-app-notifier.ts +19 -19
- package/lib/routes.middleware.ts +7 -7
- package/package.json +1 -1
package/bin/lib/file-mapper.ts
CHANGED
|
@@ -4,84 +4,83 @@ import path from "node:path"
|
|
|
4
4
|
import { getHash, glob, write } from "./system.ts"
|
|
5
5
|
|
|
6
6
|
export interface FileMapper {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
url: string
|
|
8
|
+
file: string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
function isFileMapper(x: any | undefined): x is FileMapper {
|
|
12
|
-
|
|
12
|
+
return x?.url && x?.file
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
let option: { isRunning: boolean, isWaiting: NodeJS.Timeout | null } = {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
isRunning: false,
|
|
17
|
+
isWaiting: null
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export async function fileMapper(targetDirectory: string, force = false) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
option.isWaiting = setTimeout(() => {
|
|
26
|
-
option.isWaiting = null
|
|
27
|
-
fileMapper(targetDirectory, true)
|
|
28
|
-
}, 30)
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
option.isRunning = true
|
|
33
|
-
console.time("File Mapper")
|
|
34
|
-
|
|
35
|
-
let oldFileMapperFiles = await glob("**/file-map.*.js", targetDirectory)
|
|
36
|
-
Promise.all(oldFileMapperFiles.map(async x => {
|
|
37
|
-
await rm(`${targetDirectory}/${x}`)
|
|
38
|
-
}))
|
|
39
|
-
|
|
40
|
-
let files: string[] = await glob("**/*.{js,css,json,ico,svg,png}", targetDirectory)
|
|
41
|
-
|
|
42
|
-
let mapper = files.map(x => {
|
|
43
|
-
let parsed = path.parse(x)
|
|
44
|
-
let parsed2 = path.parse(parsed.name)
|
|
45
|
-
let url = `/${parsed.dir}/${parsed2.name}${parsed.ext}`
|
|
46
|
-
let file = `/${x}`
|
|
47
|
-
return {
|
|
48
|
-
url,
|
|
49
|
-
file,
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
.filter(isFileMapper)
|
|
53
|
-
|
|
54
|
-
if (option.isWaiting) return
|
|
55
|
-
// Write mapper to file in src/web/file-map.js
|
|
56
|
-
let fileMapJsonContent = JSON.stringify(mapper)
|
|
57
|
-
let hash = getHash(fileMapJsonContent)
|
|
58
|
-
let fileMapUrl = `/web/file-map.${hash}.js`
|
|
59
|
-
|
|
60
|
-
fileMapJsonContent = `${fileMapJsonContent.slice(0, -1)},{"url":"/web/file-map.js","file":"${fileMapUrl}"}]`
|
|
61
|
-
|
|
62
|
-
let fileMapContent = `(() => { self.app = { links: ${fileMapJsonContent} } })()`
|
|
63
|
-
await write(`${targetDirectory}${fileMapUrl}`, fileMapContent)
|
|
64
|
-
|
|
65
|
-
let globalFiles =
|
|
66
|
-
mapper
|
|
67
|
-
.filter(x => x.url.includes(".global."))
|
|
68
|
-
.map(x => x.file)
|
|
69
|
-
.join(`","`)
|
|
70
|
-
|
|
71
|
-
let swFile = mapper.find(x => x.url === "/web/sw.js")?.file
|
|
72
|
-
|
|
73
|
-
let globals = `${globalFiles && `"`}${globalFiles}${globalFiles && `",`}`
|
|
74
|
-
// Create service worker central file
|
|
75
|
-
if (option.isWaiting) return
|
|
76
|
-
await write(
|
|
77
|
-
`${targetDirectory}/web/sw.js`,
|
|
78
|
-
`importScripts("${fileMapUrl}",${globals}"${swFile}")`)
|
|
79
|
-
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error(error)
|
|
82
|
-
} finally {
|
|
83
|
-
option.isRunning = false
|
|
84
|
-
console.timeEnd("File Mapper")
|
|
21
|
+
if (option.isRunning || option.isWaiting || !force) {
|
|
22
|
+
if (option.isWaiting) {
|
|
23
|
+
clearTimeout(option.isWaiting)
|
|
85
24
|
}
|
|
25
|
+
option.isWaiting = setTimeout(() => {
|
|
26
|
+
option.isWaiting = null
|
|
27
|
+
fileMapper(targetDirectory, true)
|
|
28
|
+
}, 30)
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
option.isRunning = true
|
|
33
|
+
console.time("File Mapper")
|
|
34
|
+
|
|
35
|
+
let oldFileMapperFiles = await glob("**/file-map.*.js", targetDirectory)
|
|
36
|
+
Promise.all(oldFileMapperFiles.map(async x => {
|
|
37
|
+
await rm(`${targetDirectory}/${x}`)
|
|
38
|
+
}))
|
|
39
|
+
|
|
40
|
+
let files: string[] = await glob("**/*.{js,css,json,ico,svg,png}", targetDirectory)
|
|
41
|
+
|
|
42
|
+
let mapper = files.map(x => {
|
|
43
|
+
let parsed = path.parse(x)
|
|
44
|
+
let parsed2 = path.parse(parsed.name)
|
|
45
|
+
let url = `/${parsed.dir}/${parsed2.name}${parsed.ext}`
|
|
46
|
+
let file = `/${x}`
|
|
47
|
+
return {
|
|
48
|
+
url,
|
|
49
|
+
file,
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.filter(isFileMapper)
|
|
53
|
+
|
|
54
|
+
if (option.isWaiting) return
|
|
55
|
+
// Write mapper to file in src/web/file-map.js
|
|
56
|
+
let fileMapJsonContent = JSON.stringify(mapper)
|
|
57
|
+
let hash = getHash(fileMapJsonContent)
|
|
58
|
+
let fileMapUrl = `/web/file-map.${hash}.js`
|
|
59
|
+
|
|
60
|
+
fileMapJsonContent = `${fileMapJsonContent.slice(0, -1)},{"url":"/web/file-map.js","file":"${fileMapUrl}"}]`
|
|
61
|
+
|
|
62
|
+
let fileMapContent = `(() => { self.sw = { links: ${fileMapJsonContent} } })()`
|
|
63
|
+
await write(`${targetDirectory}${fileMapUrl}`, fileMapContent)
|
|
64
|
+
|
|
65
|
+
let globalFiles =
|
|
66
|
+
mapper
|
|
67
|
+
.filter(x => x.url.includes(".global."))
|
|
68
|
+
.map(x => x.file)
|
|
69
|
+
.join(`","`)
|
|
70
|
+
|
|
71
|
+
let swFile = mapper.find(x => x.url === "/web/sw.js")?.file
|
|
72
|
+
|
|
73
|
+
let globals = `${globalFiles && `"`}${globalFiles}${globalFiles && `",`}`
|
|
74
|
+
// Create service worker central file
|
|
75
|
+
if (option.isWaiting) return
|
|
76
|
+
await write(
|
|
77
|
+
`${targetDirectory}/web/sw.js`,
|
|
78
|
+
`importScripts("${fileMapUrl}",${globals}"${swFile}")`)
|
|
79
|
+
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error(error)
|
|
82
|
+
} finally {
|
|
83
|
+
option.isRunning = false
|
|
84
|
+
console.timeEnd("File Mapper")
|
|
85
|
+
}
|
|
86
86
|
}
|
|
87
|
-
|
package/lib/new-app-notifier.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// https://deanhume.com/displaying-a-new-version-available-progressive-web-app/
|
|
2
|
+
const isDev = ["localhost", "127.0.0.1"].includes(document.location.hostname)
|
|
2
3
|
let refreshing = false
|
|
3
4
|
// The event listener that is fired when the service worker updates
|
|
4
5
|
// Here we reload the page
|
|
@@ -12,15 +13,13 @@ navigator.serviceWorker.addEventListener('controllerchange', function () {
|
|
|
12
13
|
/// @param fn - Callback function to be called when a new version is available
|
|
13
14
|
/// @returns void
|
|
14
15
|
/// @example
|
|
15
|
-
/// notifier((
|
|
16
|
-
///
|
|
17
|
-
/// // Show notification
|
|
18
|
-
/// }
|
|
16
|
+
/// notifier((worker) => {
|
|
17
|
+
/// // Show notification to user about new version
|
|
19
18
|
/// if (/* user confirms update */) {
|
|
20
19
|
/// worker.postMessage("skipWaiting")
|
|
21
20
|
/// }
|
|
22
21
|
/// })
|
|
23
|
-
function notifier(fn: (
|
|
22
|
+
function notifier(fn: (worker: ServiceWorker) => void) {
|
|
24
23
|
let newWorker: ServiceWorker | undefined | null
|
|
25
24
|
|
|
26
25
|
if ('serviceWorker' in navigator) {
|
|
@@ -28,7 +27,7 @@ function notifier(fn: (state: "" | "waiting", worker: ServiceWorker) => void) {
|
|
|
28
27
|
navigator.serviceWorker.register('/web/sw.js').then(reg => {
|
|
29
28
|
if ((newWorker = reg.waiting)?.state === 'installed') {
|
|
30
29
|
// @ts-ignore
|
|
31
|
-
fn(
|
|
30
|
+
fn(newWorker)
|
|
32
31
|
return
|
|
33
32
|
}
|
|
34
33
|
reg.addEventListener('updatefound', () => {
|
|
@@ -37,11 +36,19 @@ function notifier(fn: (state: "" | "waiting", worker: ServiceWorker) => void) {
|
|
|
37
36
|
|
|
38
37
|
newWorker?.addEventListener('statechange', () => {
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
// There is a new service worker available, show the notification
|
|
40
|
+
if (newWorker?.state === "installed" && navigator.serviceWorker.controller) {
|
|
41
|
+
navigator.serviceWorker.controller.postMessage("installed")
|
|
42
|
+
// If we're in dev/server auto-activate the new worker
|
|
43
|
+
if (isDev) {
|
|
44
|
+
try {
|
|
45
|
+
newWorker.postMessage({ action: 'skipWaiting' })
|
|
46
|
+
} catch (err) {
|
|
47
|
+
// Ignore if worker doesn't accept messages
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
fn(newWorker)
|
|
51
|
+
}
|
|
45
52
|
|
|
46
53
|
})
|
|
47
54
|
})
|
|
@@ -60,7 +67,7 @@ function skipWaiting(id: string) {
|
|
|
60
67
|
})
|
|
61
68
|
}
|
|
62
69
|
|
|
63
|
-
function notifyUserAboutNewVersion(
|
|
70
|
+
function notifyUserAboutNewVersion(worker: ServiceWorker) {
|
|
64
71
|
let nav = document.getElementById("sw-message")
|
|
65
72
|
nav?.insertAdjacentHTML("afterbegin", `<div class=inline><a id=skipWaiting href="#">Click here to update your app.</a></div>`)
|
|
66
73
|
// @ts-ignore
|
|
@@ -69,11 +76,4 @@ function notifyUserAboutNewVersion(state = "", worker: ServiceWorker) {
|
|
|
69
76
|
window.app = window.app || {}
|
|
70
77
|
// @ts-ignore
|
|
71
78
|
window.app.sw = worker
|
|
72
|
-
if (state === "waiting") return
|
|
73
|
-
// Publish custom event for "user-messages" to display a toast.
|
|
74
|
-
document.dispatchEvent(new CustomEvent("user-messages", {
|
|
75
|
-
detail: { html: `A new version of the app is available. <a id=skipWaiting1 href="#">Click to update the app.</a>` }
|
|
76
|
-
}))
|
|
77
|
-
// @ts-ignore
|
|
78
|
-
skipWaiting("skipWaiting1")
|
|
79
79
|
}
|
package/lib/routes.middleware.ts
CHANGED
|
@@ -3,10 +3,10 @@ import { isHtml } from "./utils.js"
|
|
|
3
3
|
|
|
4
4
|
let { links, globalDb } =
|
|
5
5
|
// @ts-ignore
|
|
6
|
-
self.
|
|
6
|
+
self.sw as { links: { file: string, url: string }[], html: Function, db: any, globalDb: any }
|
|
7
7
|
|
|
8
8
|
if (!links) {
|
|
9
|
-
console.error("Expecting links defined with `self.
|
|
9
|
+
console.error("Expecting links defined with `self.sw.links`, but found none.")
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function redirect(req: Request) {
|
|
@@ -66,12 +66,12 @@ export async function findRoute(url: URL, method: unknown) {
|
|
|
66
66
|
let validMethod: MethodTypes = isMethod(method)
|
|
67
67
|
if (validMethod) {
|
|
68
68
|
// @ts-ignore
|
|
69
|
-
if (!self.
|
|
70
|
-
console.error("Expecting routes defined with `self.
|
|
69
|
+
if (!self.sw?.routes) {
|
|
70
|
+
console.error("Expecting routes defined with `self.sw.routes`, but found none.")
|
|
71
71
|
return null
|
|
72
72
|
}
|
|
73
73
|
// @ts-ignore
|
|
74
|
-
for (const r of self.
|
|
74
|
+
for (const r of self.sw.routes) {
|
|
75
75
|
// @ts-ignore
|
|
76
76
|
if (r.file
|
|
77
77
|
&& (r.route instanceof RegExp && r.route.test(url.pathname)
|
|
@@ -262,8 +262,8 @@ async function cacheResponse(url: string, req?: Request | undefined): Promise<Re
|
|
|
262
262
|
if (!res || res.status !== 200 || res.type !== "basic") return res
|
|
263
263
|
const responseToCache = res.clone()
|
|
264
264
|
// @ts-ignore
|
|
265
|
-
let version: string = self.
|
|
266
|
-
?? (console.warn("The version number is not available, expected glboal value `self.
|
|
265
|
+
let version: string = self.sw?.version
|
|
266
|
+
?? (console.warn("The version number is not available, expected glboal value `self.sw.version`."), "")
|
|
267
267
|
const cache = await caches.open(version)
|
|
268
268
|
cache.put(url, responseToCache)
|
|
269
269
|
return res
|