@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.
@@ -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
- url: string
8
- file: string
7
+ url: string
8
+ file: string
9
9
  }
10
10
 
11
11
  function isFileMapper(x: any | undefined): x is FileMapper {
12
- return x?.url && x?.file
12
+ return x?.url && x?.file
13
13
  }
14
14
 
15
15
  let option: { isRunning: boolean, isWaiting: NodeJS.Timeout | null } = {
16
- isRunning: false,
17
- isWaiting: null
16
+ isRunning: false,
17
+ isWaiting: null
18
18
  }
19
19
 
20
20
  export async function fileMapper(targetDirectory: string, force = false) {
21
- if (option.isRunning || option.isWaiting || !force) {
22
- if (option.isWaiting) {
23
- clearTimeout(option.isWaiting)
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
-
@@ -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((state, worker) => {
16
- /// if (state !== "waiting") {
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: (state: "" | "waiting", worker: ServiceWorker) => void) {
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("waiting", newWorker)
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
- // There is a new service worker available, show the notification
41
- if (newWorker?.state === "installed" && navigator.serviceWorker.controller) {
42
- navigator.serviceWorker.controller.postMessage("installed")
43
- fn("", newWorker)
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(state = "", worker: ServiceWorker) {
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
  }
@@ -3,10 +3,10 @@ import { isHtml } from "./utils.js"
3
3
 
4
4
  let { links, globalDb } =
5
5
  // @ts-ignore
6
- self.app as { links: { file: string, url: string }[], html: Function, db: any, globalDb: any }
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.app.links`, but found none.")
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.app?.routes) {
70
- console.error("Expecting routes defined with `self.app.routes`, but found none.")
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.app.routes) {
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.app?.version
266
- ?? (console.warn("The version number is not available, expected glboal value `self.app.version`."), "")
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jon49/sw",
3
- "version": "0.14.7",
3
+ "version": "0.15.0",
4
4
  "description": "Packages for MVC service workers.",
5
5
  "type": "module",
6
6
  "files": [