@jon49/sw 0.14.6 → 0.14.8

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.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")
85
+ }
86
86
  }
87
-
@@ -1,66 +1,79 @@
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
5
6
  navigator.serviceWorker.addEventListener('controllerchange', function () {
6
- if (refreshing) return
7
- refreshing = true
8
- window.location.reload()
7
+ if (refreshing) return
8
+ refreshing = true
9
+ window.location.reload()
9
10
  })
10
11
 
11
12
  /// Checks if there is a new version of the app available
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
- /// }
19
- /// if (/* user confirms update */) {
20
- /// worker.postMessage("skipWaiting")
21
- /// }
16
+ /// notifier((worker) => {
17
+ /// // Show notification to user about new version
18
+ /// if (/* user confirms update */) {
19
+ /// worker.postMessage("skipWaiting")
20
+ /// }
22
21
  /// })
23
- function notifier(fn: (state: "" | "waiting", worker: ServiceWorker) => void) {
24
- let newWorker: ServiceWorker | undefined | null
22
+ function notifier(fn: (worker: ServiceWorker) => void) {
23
+ let newWorker: ServiceWorker | undefined | null
25
24
 
26
- if ('serviceWorker' in navigator) {
27
- // Register the service worker
28
- navigator.serviceWorker.register('/web/sw.js').then(reg => {
29
- if ((newWorker = reg.waiting)?.state === 'installed') {
30
- // @ts-ignore
31
- fn("waiting", newWorker)
32
- return
33
- }
34
- reg.addEventListener('updatefound', () => {
35
- // An updated service worker has appeared in reg.installing!
36
- newWorker = reg.installing
25
+ if ('serviceWorker' in navigator) {
26
+ // Register the service worker
27
+ navigator.serviceWorker.register('/web/sw.js').then(reg => {
28
+ if ((newWorker = reg.waiting)?.state === 'installed') {
29
+ // @ts-ignore
30
+ fn(newWorker)
31
+ return
32
+ }
33
+ reg.addEventListener('updatefound', () => {
34
+ // An updated service worker has appeared in reg.installing!
35
+ newWorker = reg.installing
37
36
 
38
- newWorker?.addEventListener('statechange', () => {
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
- })
47
- })
48
53
  })
49
- }
54
+ })
55
+ })
56
+ }
50
57
  }
51
58
 
52
59
  notifier(notifyUserAboutNewVersion)
53
60
 
54
- function notifyUserAboutNewVersion(state = "", worker: ServiceWorker) {
55
- let nav = document.getElementById("sw-message")
56
- nav?.insertAdjacentHTML("afterbegin", `<div class=inline><a traits=skip-waiting href="#">Click here to update your app.</a></div>`)
57
- // @ts-ignore
58
- window.app = window.app || {}
61
+ function skipWaiting(id: string) {
62
+ let el = document.getElementById(id)
63
+ el?.addEventListener("click", (e: MouseEvent) => {
64
+ e.preventDefault()
59
65
  // @ts-ignore
60
- window.app.sw = worker
61
- if (state === "waiting") return
62
- // Publish custom event for "user-messages" to display a toast.
63
- document.dispatchEvent(new CustomEvent("user-messages", {
64
- detail: { html: `A new version of the app is available. <a traits=skip-waiting href="#">Click to update the app.</a>` }
65
- }))
66
+ window.app.sw.postMessage({ action: 'skipWaiting' })
67
+ })
68
+ }
69
+
70
+ function notifyUserAboutNewVersion(worker: ServiceWorker) {
71
+ let nav = document.getElementById("sw-message")
72
+ nav?.insertAdjacentHTML("afterbegin", `<div class=inline><a id=skipWaiting href="#">Click here to update your app.</a></div>`)
73
+ // @ts-ignore
74
+ skipWaiting("skipWaiting")
75
+ // @ts-ignore
76
+ window.app = window.app || {}
77
+ // @ts-ignore
78
+ window.app.sw = worker
66
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jon49/sw",
3
- "version": "0.14.6",
3
+ "version": "0.14.8",
4
4
  "description": "Packages for MVC service workers.",
5
5
  "type": "module",
6
6
  "files": [