@eclipse-docks/extension-pwa 0.7.89 → 0.7.91
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/dist/index.js +1 -1
- package/dist/{pwa-extension-DEg-tRRo.js → pwa-extension-Ceo_RWeH.js} +40 -1
- package/dist/pwa-extension-Ceo_RWeH.js.map +1 -0
- package/dist/sw-update-indicator.d.ts +3 -0
- package/dist/sw-update-indicator.d.ts.map +1 -1
- package/dist/sw.d.ts +2 -0
- package/dist/sw.d.ts.map +1 -0
- package/dist/vite-pwa-plugin.d.ts +11 -0
- package/dist/vite-pwa-plugin.d.ts.map +1 -0
- package/package.json +12 -3
- package/src/sw.ts +94 -0
- package/src/vite-pwa-plugin.ts +66 -0
- package/dist/pwa-extension-DEg-tRRo.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ extensionRegistry.registerExtension({
|
|
|
10
10
|
id: pkg.name,
|
|
11
11
|
name: t.EXT_PWA_NAME,
|
|
12
12
|
description: t.EXT_PWA_DESC,
|
|
13
|
-
loader: () => import("./pwa-extension-
|
|
13
|
+
loader: () => import("./pwa-extension-Ceo_RWeH.js"),
|
|
14
14
|
icon: "download"
|
|
15
15
|
});
|
|
16
16
|
//#endregion
|
|
@@ -8,6 +8,8 @@ var DocksSwUpdateIndicator = class DocksSwUpdateIndicator extends DocksElement {
|
|
|
8
8
|
constructor(..._args) {
|
|
9
9
|
super(..._args);
|
|
10
10
|
this.updateAvailable = false;
|
|
11
|
+
this.updateProgress = 0;
|
|
12
|
+
this.updateProgressVisible = false;
|
|
11
13
|
this.pendingReload = false;
|
|
12
14
|
this.registration = null;
|
|
13
15
|
this.periodicInterval = null;
|
|
@@ -18,15 +20,32 @@ var DocksSwUpdateIndicator = class DocksSwUpdateIndicator extends DocksElement {
|
|
|
18
20
|
if (!this.pendingReload) return;
|
|
19
21
|
window.location.reload();
|
|
20
22
|
};
|
|
23
|
+
this.onServiceWorkerMessage = (event) => {
|
|
24
|
+
const data = event.data;
|
|
25
|
+
if (!data || data.type !== "SW_UPDATE_PROGRESS") return;
|
|
26
|
+
if (data.total <= 0) return;
|
|
27
|
+
const ratio = Math.max(0, Math.min(1, data.completed / data.total));
|
|
28
|
+
this.updateProgress = ratio;
|
|
29
|
+
this.updateProgressVisible = !this.updateAvailable && !data.done && ratio < 1;
|
|
30
|
+
};
|
|
21
31
|
this.onUpdateFound = () => {
|
|
22
32
|
const registration = this.registration;
|
|
23
33
|
if (!registration) return;
|
|
24
34
|
this.syncUpdateState(registration);
|
|
25
35
|
const installing = registration.installing;
|
|
26
36
|
if (!installing) return;
|
|
37
|
+
if (navigator.serviceWorker.controller) {
|
|
38
|
+
this.updateProgress = 0;
|
|
39
|
+
this.updateProgressVisible = true;
|
|
40
|
+
}
|
|
27
41
|
const signal = this.attachAbort?.signal;
|
|
28
42
|
if (!signal) return;
|
|
29
43
|
installing.addEventListener("statechange", () => {
|
|
44
|
+
if (installing.state === "redundant") {
|
|
45
|
+
this.updateProgress = 0;
|
|
46
|
+
this.updateProgressVisible = false;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
30
49
|
if (installing.state !== "installed") return;
|
|
31
50
|
if (!navigator.serviceWorker.controller) return;
|
|
32
51
|
this.syncUpdateState(registration);
|
|
@@ -37,11 +56,13 @@ var DocksSwUpdateIndicator = class DocksSwUpdateIndicator extends DocksElement {
|
|
|
37
56
|
super.connectedCallback();
|
|
38
57
|
if (!("serviceWorker" in navigator)) return;
|
|
39
58
|
navigator.serviceWorker.addEventListener("controllerchange", this.onControllerChange);
|
|
59
|
+
navigator.serviceWorker.addEventListener("message", this.onServiceWorkerMessage);
|
|
40
60
|
this.findOrAttachRegistration();
|
|
41
61
|
}
|
|
42
62
|
disconnectedCallback() {
|
|
43
63
|
super.disconnectedCallback();
|
|
44
64
|
navigator.serviceWorker.removeEventListener("controllerchange", this.onControllerChange);
|
|
65
|
+
navigator.serviceWorker.removeEventListener("message", this.onServiceWorkerMessage);
|
|
45
66
|
this.teardownAttachment();
|
|
46
67
|
}
|
|
47
68
|
async findOrAttachRegistration() {
|
|
@@ -77,6 +98,8 @@ var DocksSwUpdateIndicator = class DocksSwUpdateIndicator extends DocksElement {
|
|
|
77
98
|
this.attachAbort = null;
|
|
78
99
|
this.registration = null;
|
|
79
100
|
this.updateAvailable = false;
|
|
101
|
+
this.updateProgress = 0;
|
|
102
|
+
this.updateProgressVisible = false;
|
|
80
103
|
}
|
|
81
104
|
/**
|
|
82
105
|
* Show only when a new worker is waiting and an older version is already controlling
|
|
@@ -86,6 +109,10 @@ var DocksSwUpdateIndicator = class DocksSwUpdateIndicator extends DocksElement {
|
|
|
86
109
|
const waiting = Boolean(registration.waiting);
|
|
87
110
|
const hasActiveController = Boolean(navigator.serviceWorker.controller);
|
|
88
111
|
this.updateAvailable = waiting && hasActiveController;
|
|
112
|
+
if (this.updateAvailable) {
|
|
113
|
+
this.updateProgress = 1;
|
|
114
|
+
this.updateProgressVisible = false;
|
|
115
|
+
}
|
|
89
116
|
}
|
|
90
117
|
attach(registration) {
|
|
91
118
|
if (this.registration === registration) return;
|
|
@@ -114,6 +141,16 @@ var DocksSwUpdateIndicator = class DocksSwUpdateIndicator extends DocksElement {
|
|
|
114
141
|
window.location.reload();
|
|
115
142
|
}
|
|
116
143
|
render() {
|
|
144
|
+
if (this.updateProgressVisible && !this.updateAvailable) return html`
|
|
145
|
+
<div
|
|
146
|
+
style="display: inline-flex; align-items: center; gap: 0.5rem; min-width: 170px;"
|
|
147
|
+
title="Downloading the latest update..."
|
|
148
|
+
aria-label="Downloading the latest update"
|
|
149
|
+
>
|
|
150
|
+
<wa-progress-bar value=${Math.round(this.updateProgress * 100)}></wa-progress-bar>
|
|
151
|
+
<span style="font-size: 0.8rem; opacity: 0.8;">${Math.round(this.updateProgress * 100)}%</span>
|
|
152
|
+
</div>
|
|
153
|
+
`;
|
|
117
154
|
if (!this.updateAvailable) return html``;
|
|
118
155
|
return html`
|
|
119
156
|
<wa-animation
|
|
@@ -141,6 +178,8 @@ var DocksSwUpdateIndicator = class DocksSwUpdateIndicator extends DocksElement {
|
|
|
141
178
|
}
|
|
142
179
|
};
|
|
143
180
|
_decorate([state()], DocksSwUpdateIndicator.prototype, "updateAvailable", void 0);
|
|
181
|
+
_decorate([state()], DocksSwUpdateIndicator.prototype, "updateProgress", void 0);
|
|
182
|
+
_decorate([state()], DocksSwUpdateIndicator.prototype, "updateProgressVisible", void 0);
|
|
144
183
|
DocksSwUpdateIndicator = _decorate([customElement("docks-sw-update-indicator")], DocksSwUpdateIndicator);
|
|
145
184
|
//#endregion
|
|
146
185
|
//#region src/pwa-install-button.ts
|
|
@@ -220,4 +259,4 @@ contributionRegistry.registerContribution(TOOLBAR_MAIN_CENTER, {
|
|
|
220
259
|
});
|
|
221
260
|
//#endregion
|
|
222
261
|
|
|
223
|
-
//# sourceMappingURL=pwa-extension-
|
|
262
|
+
//# sourceMappingURL=pwa-extension-Ceo_RWeH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pwa-extension-Ceo_RWeH.js","names":[],"sources":["../src/sw-update-indicator.ts","../src/pwa-install-button.ts","../src/pwa-extension.ts"],"sourcesContent":["import { html } from 'lit';\nimport { customElement, state } from 'lit/decorators.js';\n\nimport { DocksElement } from '@eclipse-docks/core';\n\ntype SwUpdateProgressMessage = {\n type: 'SW_UPDATE_PROGRESS';\n completed: number;\n total: number;\n done?: boolean;\n};\n\n/**\n * Shows a toolbar icon when a new service worker is waiting (vite-plugin-pwa injectManifest + SKIP_WAITING message).\n * Hidden when there is no update or when service workers are unavailable.\n *\n * Safe when the extension loads late: attaches via `getRegistration()`, reads `registration.waiting`,\n * and subscribes to `updatefound` for subsequent updates.\n */\n@customElement('docks-sw-update-indicator')\nexport class DocksSwUpdateIndicator extends DocksElement {\n @state()\n private updateAvailable = false;\n @state()\n private updateProgress = 0;\n @state()\n private updateProgressVisible = false;\n\n private pendingReload = false;\n private registration: ServiceWorkerRegistration | null = null;\n private periodicInterval: number | null = null;\n private pollInterval: number | null = null;\n private pollAttempts = 0;\n private attachAbort: AbortController | null = null;\n\n private readonly onControllerChange = (): void => {\n if (!this.pendingReload) {\n return;\n }\n window.location.reload();\n };\n\n private readonly onServiceWorkerMessage = (event: MessageEvent): void => {\n const data = event.data as SwUpdateProgressMessage | undefined;\n if (!data || data.type !== 'SW_UPDATE_PROGRESS') {\n return;\n }\n if (data.total <= 0) {\n return;\n }\n const ratio = Math.max(0, Math.min(1, data.completed / data.total));\n this.updateProgress = ratio;\n this.updateProgressVisible = !this.updateAvailable && !data.done && ratio < 1;\n };\n\n connectedCallback(): void {\n super.connectedCallback();\n if (!('serviceWorker' in navigator)) {\n return;\n }\n navigator.serviceWorker.addEventListener('controllerchange', this.onControllerChange);\n navigator.serviceWorker.addEventListener('message', this.onServiceWorkerMessage);\n void this.findOrAttachRegistration();\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n navigator.serviceWorker.removeEventListener('controllerchange', this.onControllerChange);\n navigator.serviceWorker.removeEventListener('message', this.onServiceWorkerMessage);\n this.teardownAttachment();\n }\n\n private async findOrAttachRegistration(): Promise<void> {\n const existing = await navigator.serviceWorker.getRegistration();\n if (existing) {\n this.attach(existing);\n return;\n }\n\n this.pollInterval = window.setInterval(async () => {\n this.pollAttempts += 1;\n const reg = await navigator.serviceWorker.getRegistration();\n if (reg) {\n this.clearPoll();\n this.attach(reg);\n return;\n }\n if (this.pollAttempts >= 30) {\n this.clearPoll();\n }\n }, 1000);\n }\n\n private clearPoll(): void {\n if (this.pollInterval !== null) {\n window.clearInterval(this.pollInterval);\n this.pollInterval = null;\n }\n }\n\n private teardownAttachment(): void {\n this.clearPoll();\n if (this.periodicInterval !== null) {\n window.clearInterval(this.periodicInterval);\n this.periodicInterval = null;\n }\n this.attachAbort?.abort();\n this.attachAbort = null;\n this.registration = null;\n this.updateAvailable = false;\n this.updateProgress = 0;\n this.updateProgressVisible = false;\n }\n\n /**\n * Show only when a new worker is waiting and an older version is already controlling\n * the page (real update). Avoid treating the first install as an “update”.\n */\n private syncUpdateState(registration: ServiceWorkerRegistration): void {\n const waiting = Boolean(registration.waiting);\n const hasActiveController = Boolean(navigator.serviceWorker.controller);\n this.updateAvailable = waiting && hasActiveController;\n if (this.updateAvailable) {\n this.updateProgress = 1;\n this.updateProgressVisible = false;\n }\n }\n\n private readonly onUpdateFound = (): void => {\n const registration = this.registration;\n if (!registration) {\n return;\n }\n // Fast installs (common in standalone PWA): worker may already be `waiting` when\n // this runs, with `installing` already null — still an update if a controller exists.\n this.syncUpdateState(registration);\n\n const installing = registration.installing;\n if (!installing) {\n return;\n }\n if (navigator.serviceWorker.controller) {\n this.updateProgress = 0;\n this.updateProgressVisible = true;\n }\n const signal = this.attachAbort?.signal;\n if (!signal) {\n return;\n }\n installing.addEventListener(\n 'statechange',\n () => {\n if (installing.state === 'redundant') {\n this.updateProgress = 0;\n this.updateProgressVisible = false;\n return;\n }\n if (installing.state !== 'installed') {\n return;\n }\n if (!navigator.serviceWorker.controller) {\n return;\n }\n this.syncUpdateState(registration);\n },\n { signal },\n );\n };\n\n private attach(registration: ServiceWorkerRegistration): void {\n if (this.registration === registration) {\n return;\n }\n this.attachAbort?.abort();\n this.attachAbort = new AbortController();\n const signal = this.attachAbort.signal;\n\n this.registration = registration;\n this.syncUpdateState(registration);\n\n registration.addEventListener('updatefound', this.onUpdateFound, { signal });\n\n void registration.update().catch(() => {});\n this.periodicInterval = window.setInterval(() => {\n void registration.update().catch(() => {});\n }, 60 * 60 * 1000);\n\n queueMicrotask(() => {\n if (this.registration !== registration) {\n return;\n }\n this.syncUpdateState(registration);\n });\n }\n\n private onActivateClick(): void {\n const w = this.registration?.waiting;\n if (w) {\n this.pendingReload = true;\n w.postMessage({ type: 'SKIP_WAITING' });\n return;\n }\n window.location.reload();\n }\n\n protected render() {\n if (this.updateProgressVisible && !this.updateAvailable) {\n return html`\n <div\n style=\"display: inline-flex; align-items: center; gap: 0.5rem; min-width: 170px;\"\n title=\"Downloading the latest update...\"\n aria-label=\"Downloading the latest update\"\n >\n <wa-progress-bar value=${Math.round(this.updateProgress * 100)}></wa-progress-bar>\n <span style=\"font-size: 0.8rem; opacity: 0.8;\">${Math.round(this.updateProgress * 100)}%</span>\n </div>\n `;\n }\n\n if (!this.updateAvailable) {\n return html``;\n }\n\n return html`\n <wa-animation\n style=\"display: inline-flex; align-items: center;\"\n name=\"zoomIn\"\n duration=\"1400\"\n easing=\"ease-out\"\n iterations=\"Infinity\"\n ?play=${true}\n >\n <wa-button\n appearance=\"plain\"\n title=\"A new version is available. Click to reload.\"\n aria-label=\"A new version is available. Reload to update.\"\n @click=${this.onActivateClick}\n >\n <wa-icon\n name=\"arrows-rotate\"\n label=\"\"\n style=\"color: var(--wa-color-success-fill-loud)\"\n ></wa-icon>\n </wa-button>\n </wa-animation>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'docks-sw-update-indicator': DocksSwUpdateIndicator;\n }\n}\n","import { html } from 'lit';\nimport { customElement, state } from 'lit/decorators.js';\n\nimport { DocksElement } from '@eclipse-docks/core';\n\nimport {\n clearInstallPromptCapture,\n peekInstallPrompt,\n PWA_INSTALL_PROMPT_AVAILABLE,\n type PwaInstallPromptEvent,\n} from './install-prompt-capture';\n\nfunction isRunningAsInstalledPwa(): boolean {\n return (\n window.matchMedia('(display-mode: standalone)').matches ||\n window.matchMedia('(display-mode: window-controls-overlay)').matches ||\n (window.navigator as Navigator & { standalone?: boolean }).standalone === true\n );\n}\n\n@customElement('docks-pwa-install')\nexport class DocksPwaInstall extends DocksElement {\n @state()\n private showInstall = false;\n\n private deferredPrompt: PwaInstallPromptEvent | null = null;\n\n private readonly onPromptAvailable = (): void => {\n this.applyCapturedPrompt();\n };\n\n private readonly onAppInstalled = (): void => {\n clearInstallPromptCapture();\n this.deferredPrompt = null;\n this.showInstall = false;\n };\n\n private applyCapturedPrompt(): void {\n const p = peekInstallPrompt();\n if (!p) {\n return;\n }\n this.deferredPrompt = p;\n this.showInstall = true;\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n if (isRunningAsInstalledPwa()) {\n return;\n }\n this.applyCapturedPrompt();\n window.addEventListener(PWA_INSTALL_PROMPT_AVAILABLE, this.onPromptAvailable);\n window.addEventListener('appinstalled', this.onAppInstalled);\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n window.removeEventListener(PWA_INSTALL_PROMPT_AVAILABLE, this.onPromptAvailable);\n window.removeEventListener('appinstalled', this.onAppInstalled);\n }\n\n private async onInstallClick(): Promise<void> {\n const promptEvent = this.deferredPrompt;\n if (!promptEvent) {\n return;\n }\n await promptEvent.prompt();\n await promptEvent.userChoice.catch(() => {});\n clearInstallPromptCapture();\n this.deferredPrompt = null;\n this.showInstall = false;\n }\n\n protected render() {\n if (!this.showInstall) {\n return html``;\n }\n\n return html`\n <wa-button\n appearance=\"plain\"\n title=\"Install this app on your device\"\n aria-label=\"Install app\"\n @click=${() => void this.onInstallClick()}\n >\n <wa-icon name=\"download\" label=\"\"></wa-icon>\n </wa-button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'docks-pwa-install': DocksPwaInstall;\n }\n}\n","import { contributionRegistry, type HTMLContribution, TOOLBAR_MAIN_CENTER } from '@eclipse-docks/core';\nimport './sw-update-indicator';\nimport './pwa-install-button';\n\nconst TOOLBAR_SW_UPDATE = 'toolbar.swUpdate';\nconst TOOLBAR_PWA_INSTALL = 'toolbar.pwaInstall';\n\ncontributionRegistry.registerContribution(TOOLBAR_MAIN_CENTER, {\n name: TOOLBAR_SW_UPDATE,\n label: 'App update',\n component: `<docks-sw-update-indicator></docks-sw-update-indicator>`,\n} as HTMLContribution);\n\ncontributionRegistry.registerContribution(TOOLBAR_MAIN_CENTER, {\n name: TOOLBAR_PWA_INSTALL,\n label: 'Install app',\n component: `<docks-pwa-install></docks-pwa-install>`,\n} as HTMLContribution);\n"],"mappings":"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"}
|
|
@@ -8,6 +8,8 @@ import { DocksElement } from '@eclipse-docks/core';
|
|
|
8
8
|
*/
|
|
9
9
|
export declare class DocksSwUpdateIndicator extends DocksElement {
|
|
10
10
|
private updateAvailable;
|
|
11
|
+
private updateProgress;
|
|
12
|
+
private updateProgressVisible;
|
|
11
13
|
private pendingReload;
|
|
12
14
|
private registration;
|
|
13
15
|
private periodicInterval;
|
|
@@ -15,6 +17,7 @@ export declare class DocksSwUpdateIndicator extends DocksElement {
|
|
|
15
17
|
private pollAttempts;
|
|
16
18
|
private attachAbort;
|
|
17
19
|
private readonly onControllerChange;
|
|
20
|
+
private readonly onServiceWorkerMessage;
|
|
18
21
|
connectedCallback(): void;
|
|
19
22
|
disconnectedCallback(): void;
|
|
20
23
|
private findOrAttachRegistration;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sw-update-indicator.d.ts","sourceRoot":"","sources":["../src/sw-update-indicator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"sw-update-indicator.d.ts","sourceRoot":"","sources":["../src/sw-update-indicator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AASnD;;;;;;GAMG;AACH,qBACa,sBAAuB,SAAQ,YAAY;IAEtD,OAAO,CAAC,eAAe,CAAS;IAEhC,OAAO,CAAC,cAAc,CAAK;IAE3B,OAAO,CAAC,qBAAqB,CAAS;IAEtC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,YAAY,CAA0C;IAC9D,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAgC;IAEnD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAKjC;IAEF,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAWrC;IAEF,iBAAiB,IAAI,IAAI;IAUzB,oBAAoB,IAAI,IAAI;YAOd,wBAAwB;IAqBtC,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,kBAAkB;IAc1B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAuC5B;IAEF,OAAO,CAAC,MAAM;IA0Bd,OAAO,CAAC,eAAe;IAUvB,SAAS,CAAC,MAAM;CA0CjB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,2BAA2B,EAAE,sBAAsB,CAAC;KACrD;CACF"}
|
package/dist/sw.d.ts
ADDED
package/dist/sw.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sw.d.ts","sourceRoot":"","sources":["../src/sw.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PluginOption } from 'vite';
|
|
2
|
+
type CreateDocksPwaPluginOptions = {
|
|
3
|
+
basePath: string;
|
|
4
|
+
appName: string;
|
|
5
|
+
appDescription: string;
|
|
6
|
+
maximumFileSizeToCacheInBytes: number;
|
|
7
|
+
shortName?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function createDocksPwaPlugin({ basePath, appName, appDescription, maximumFileSizeToCacheInBytes, shortName, }: CreateDocksPwaPluginOptions): PluginOption;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=vite-pwa-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-pwa-plugin.d.ts","sourceRoot":"","sources":["../src/vite-pwa-plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAGzC,KAAK,2BAA2B,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,6BAA6B,EAAE,MAAM,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAQF,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,OAAO,EACP,cAAc,EACd,6BAA6B,EAC7B,SAAmB,GACpB,EAAE,2BAA2B,GAAG,YAAY,CAwC5C"}
|
package/package.json
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eclipse-docks/extension-pwa",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.91",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"import": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./vite": {
|
|
12
|
+
"import": "./src/vite-pwa-plugin.ts"
|
|
13
|
+
},
|
|
14
|
+
"./sw": {
|
|
15
|
+
"import": "./src/sw.ts"
|
|
10
16
|
}
|
|
11
17
|
},
|
|
12
18
|
"dependencies": {
|
|
13
|
-
"@eclipse-docks/core": "*"
|
|
19
|
+
"@eclipse-docks/core": "*",
|
|
20
|
+
"vite-plugin-pwa": "^1.2.0"
|
|
14
21
|
},
|
|
15
22
|
"devDependencies": {
|
|
16
23
|
"typescript": "^6.0.0",
|
|
@@ -20,7 +27,9 @@
|
|
|
20
27
|
"module": "./dist/index.js",
|
|
21
28
|
"types": "./dist/index.d.ts",
|
|
22
29
|
"files": [
|
|
23
|
-
"dist"
|
|
30
|
+
"dist",
|
|
31
|
+
"src/sw.ts",
|
|
32
|
+
"src/vite-pwa-plugin.ts"
|
|
24
33
|
],
|
|
25
34
|
"scripts": {
|
|
26
35
|
"build": "vite build"
|
package/src/sw.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/// <reference lib="webworker" />
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
addPlugins,
|
|
5
|
+
cleanupOutdatedCaches,
|
|
6
|
+
createHandlerBoundToURL,
|
|
7
|
+
precacheAndRoute,
|
|
8
|
+
} from 'workbox-precaching';
|
|
9
|
+
import { NavigationRoute, registerRoute } from 'workbox-routing';
|
|
10
|
+
|
|
11
|
+
declare const self: ServiceWorkerGlobalScope;
|
|
12
|
+
|
|
13
|
+
type SwUpdateProgressMessage = {
|
|
14
|
+
type: 'SW_UPDATE_PROGRESS';
|
|
15
|
+
completed: number;
|
|
16
|
+
total: number;
|
|
17
|
+
done: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const PRECACHE_MANIFEST = self.__WB_MANIFEST;
|
|
21
|
+
const PRECACHE_TOTAL = PRECACHE_MANIFEST.length;
|
|
22
|
+
let updateProgressStarted = false;
|
|
23
|
+
const updateProgressSeen = new Set<string>();
|
|
24
|
+
const updateProgressFetched = new Set<string>();
|
|
25
|
+
|
|
26
|
+
async function broadcastUpdateProgress(
|
|
27
|
+
completed: number,
|
|
28
|
+
total: number,
|
|
29
|
+
done = false,
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
if (total <= 0) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
|
|
35
|
+
const message: SwUpdateProgressMessage = { type: 'SW_UPDATE_PROGRESS', completed, total, done };
|
|
36
|
+
clients.forEach((client) => client.postMessage(message));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function withCoopCoep(response: Response): Response {
|
|
40
|
+
if (response.status === 0 || response.type === 'opaque' || response.type === 'opaqueredirect') {
|
|
41
|
+
return response;
|
|
42
|
+
}
|
|
43
|
+
const headers = new Headers(response.headers);
|
|
44
|
+
headers.set('Cross-Origin-Opener-Policy', 'same-origin');
|
|
45
|
+
headers.set('Cross-Origin-Embedder-Policy', 'require-corp');
|
|
46
|
+
return new Response(response.body, {
|
|
47
|
+
status: response.status,
|
|
48
|
+
statusText: response.statusText,
|
|
49
|
+
headers,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
cleanupOutdatedCaches();
|
|
54
|
+
|
|
55
|
+
addPlugins([
|
|
56
|
+
{
|
|
57
|
+
cachedResponseWillBeUsed: async ({ cachedResponse }) =>
|
|
58
|
+
cachedResponse ? withCoopCoep(cachedResponse) : cachedResponse,
|
|
59
|
+
requestWillFetch: async ({ request }) => {
|
|
60
|
+
if (!updateProgressStarted && self.registration.active && PRECACHE_TOTAL > 0) {
|
|
61
|
+
updateProgressStarted = true;
|
|
62
|
+
void broadcastUpdateProgress(0, PRECACHE_TOTAL);
|
|
63
|
+
}
|
|
64
|
+
updateProgressSeen.add(request.url);
|
|
65
|
+
return request;
|
|
66
|
+
},
|
|
67
|
+
fetchDidSucceed: async ({ request, response }) => {
|
|
68
|
+
if (
|
|
69
|
+
updateProgressStarted &&
|
|
70
|
+
updateProgressSeen.has(request.url) &&
|
|
71
|
+
!updateProgressFetched.has(request.url)
|
|
72
|
+
) {
|
|
73
|
+
updateProgressFetched.add(request.url);
|
|
74
|
+
void broadcastUpdateProgress(updateProgressFetched.size, PRECACHE_TOTAL);
|
|
75
|
+
}
|
|
76
|
+
return withCoopCoep(response);
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
precacheAndRoute(PRECACHE_MANIFEST);
|
|
82
|
+
|
|
83
|
+
const indexUrl = new URL('index.html', self.registration.scope).href;
|
|
84
|
+
registerRoute(new NavigationRoute(createHandlerBoundToURL(indexUrl)));
|
|
85
|
+
|
|
86
|
+
self.addEventListener('message', (event) => {
|
|
87
|
+
if (event.data?.type === 'SKIP_WAITING') {
|
|
88
|
+
void self.skipWaiting();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
self.addEventListener('activate', (event) => {
|
|
93
|
+
event.waitUntil(self.clients.claim());
|
|
94
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import type { PluginOption } from 'vite';
|
|
4
|
+
import { VitePWA } from 'vite-plugin-pwa';
|
|
5
|
+
|
|
6
|
+
type CreateDocksPwaPluginOptions = {
|
|
7
|
+
basePath: string;
|
|
8
|
+
appName: string;
|
|
9
|
+
appDescription: string;
|
|
10
|
+
maximumFileSizeToCacheInBytes: number;
|
|
11
|
+
shortName?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const DEFAULT_GLOB_PATTERNS = ['**/*.{js,css,html,ico,png,svg,webp,woff2,woff,ttf,wasm}'];
|
|
15
|
+
const DEFAULT_ASSETS = ['favicon.svg', 'logo.svg', 'logo-loading.svg'];
|
|
16
|
+
const pwaSwPath = fileURLToPath(new URL('./sw.ts', import.meta.url));
|
|
17
|
+
const pwaSwDir = path.dirname(pwaSwPath);
|
|
18
|
+
const pwaSwFilename = path.basename(pwaSwPath);
|
|
19
|
+
|
|
20
|
+
export function createDocksPwaPlugin({
|
|
21
|
+
basePath,
|
|
22
|
+
appName,
|
|
23
|
+
appDescription,
|
|
24
|
+
maximumFileSizeToCacheInBytes,
|
|
25
|
+
shortName = appName,
|
|
26
|
+
}: CreateDocksPwaPluginOptions): PluginOption {
|
|
27
|
+
return VitePWA({
|
|
28
|
+
registerType: 'autoUpdate',
|
|
29
|
+
injectRegister: 'inline',
|
|
30
|
+
strategies: 'injectManifest',
|
|
31
|
+
srcDir: pwaSwDir,
|
|
32
|
+
filename: pwaSwFilename,
|
|
33
|
+
manifest: {
|
|
34
|
+
name: appName,
|
|
35
|
+
short_name: shortName,
|
|
36
|
+
description: appDescription,
|
|
37
|
+
theme_color: '#1a1a1a',
|
|
38
|
+
background_color: '#1a1a1a',
|
|
39
|
+
display: 'standalone',
|
|
40
|
+
start_url: basePath,
|
|
41
|
+
scope: basePath,
|
|
42
|
+
icons: [
|
|
43
|
+
{
|
|
44
|
+
src: 'logo.svg',
|
|
45
|
+
sizes: 'any',
|
|
46
|
+
type: 'image/svg+xml',
|
|
47
|
+
purpose: 'any',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
src: 'favicon.svg',
|
|
51
|
+
sizes: 'any',
|
|
52
|
+
type: 'image/svg+xml',
|
|
53
|
+
purpose: 'monochrome',
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
includeAssets: DEFAULT_ASSETS,
|
|
58
|
+
injectManifest: {
|
|
59
|
+
maximumFileSizeToCacheInBytes,
|
|
60
|
+
globPatterns: DEFAULT_GLOB_PATTERNS,
|
|
61
|
+
},
|
|
62
|
+
devOptions: {
|
|
63
|
+
enabled: false,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pwa-extension-DEg-tRRo.js","names":[],"sources":["../src/sw-update-indicator.ts","../src/pwa-install-button.ts","../src/pwa-extension.ts"],"sourcesContent":["import { html } from 'lit';\nimport { customElement, state } from 'lit/decorators.js';\n\nimport { DocksElement } from '@eclipse-docks/core';\n\n/**\n * Shows a toolbar icon when a new service worker is waiting (vite-plugin-pwa injectManifest + SKIP_WAITING message).\n * Hidden when there is no update or when service workers are unavailable.\n *\n * Safe when the extension loads late: attaches via `getRegistration()`, reads `registration.waiting`,\n * and subscribes to `updatefound` for subsequent updates.\n */\n@customElement('docks-sw-update-indicator')\nexport class DocksSwUpdateIndicator extends DocksElement {\n @state()\n private updateAvailable = false;\n\n private pendingReload = false;\n private registration: ServiceWorkerRegistration | null = null;\n private periodicInterval: number | null = null;\n private pollInterval: number | null = null;\n private pollAttempts = 0;\n private attachAbort: AbortController | null = null;\n\n private readonly onControllerChange = (): void => {\n if (!this.pendingReload) {\n return;\n }\n window.location.reload();\n };\n\n connectedCallback(): void {\n super.connectedCallback();\n if (!('serviceWorker' in navigator)) {\n return;\n }\n navigator.serviceWorker.addEventListener('controllerchange', this.onControllerChange);\n void this.findOrAttachRegistration();\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n navigator.serviceWorker.removeEventListener('controllerchange', this.onControllerChange);\n this.teardownAttachment();\n }\n\n private async findOrAttachRegistration(): Promise<void> {\n const existing = await navigator.serviceWorker.getRegistration();\n if (existing) {\n this.attach(existing);\n return;\n }\n\n this.pollInterval = window.setInterval(async () => {\n this.pollAttempts += 1;\n const reg = await navigator.serviceWorker.getRegistration();\n if (reg) {\n this.clearPoll();\n this.attach(reg);\n return;\n }\n if (this.pollAttempts >= 30) {\n this.clearPoll();\n }\n }, 1000);\n }\n\n private clearPoll(): void {\n if (this.pollInterval !== null) {\n window.clearInterval(this.pollInterval);\n this.pollInterval = null;\n }\n }\n\n private teardownAttachment(): void {\n this.clearPoll();\n if (this.periodicInterval !== null) {\n window.clearInterval(this.periodicInterval);\n this.periodicInterval = null;\n }\n this.attachAbort?.abort();\n this.attachAbort = null;\n this.registration = null;\n this.updateAvailable = false;\n }\n\n /**\n * Show only when a new worker is waiting and an older version is already controlling\n * the page (real update). Avoid treating the first install as an “update”.\n */\n private syncUpdateState(registration: ServiceWorkerRegistration): void {\n const waiting = Boolean(registration.waiting);\n const hasActiveController = Boolean(navigator.serviceWorker.controller);\n this.updateAvailable = waiting && hasActiveController;\n }\n\n private readonly onUpdateFound = (): void => {\n const registration = this.registration;\n if (!registration) {\n return;\n }\n // Fast installs (common in standalone PWA): worker may already be `waiting` when\n // this runs, with `installing` already null — still an update if a controller exists.\n this.syncUpdateState(registration);\n\n const installing = registration.installing;\n if (!installing) {\n return;\n }\n const signal = this.attachAbort?.signal;\n if (!signal) {\n return;\n }\n installing.addEventListener(\n 'statechange',\n () => {\n if (installing.state !== 'installed') {\n return;\n }\n if (!navigator.serviceWorker.controller) {\n return;\n }\n this.syncUpdateState(registration);\n },\n { signal },\n );\n };\n\n private attach(registration: ServiceWorkerRegistration): void {\n if (this.registration === registration) {\n return;\n }\n this.attachAbort?.abort();\n this.attachAbort = new AbortController();\n const signal = this.attachAbort.signal;\n\n this.registration = registration;\n this.syncUpdateState(registration);\n\n registration.addEventListener('updatefound', this.onUpdateFound, { signal });\n\n void registration.update().catch(() => {});\n this.periodicInterval = window.setInterval(() => {\n void registration.update().catch(() => {});\n }, 60 * 60 * 1000);\n\n queueMicrotask(() => {\n if (this.registration !== registration) {\n return;\n }\n this.syncUpdateState(registration);\n });\n }\n\n private onActivateClick(): void {\n const w = this.registration?.waiting;\n if (w) {\n this.pendingReload = true;\n w.postMessage({ type: 'SKIP_WAITING' });\n return;\n }\n window.location.reload();\n }\n\n protected render() {\n if (!this.updateAvailable) {\n return html``;\n }\n\n return html`\n <wa-animation\n style=\"display: inline-flex; align-items: center;\"\n name=\"zoomIn\"\n duration=\"1400\"\n easing=\"ease-out\"\n iterations=\"Infinity\"\n ?play=${true}\n >\n <wa-button\n appearance=\"plain\"\n title=\"A new version is available. Click to reload.\"\n aria-label=\"A new version is available. Reload to update.\"\n @click=${this.onActivateClick}\n >\n <wa-icon\n name=\"arrows-rotate\"\n label=\"\"\n style=\"color: var(--wa-color-success-fill-loud)\"\n ></wa-icon>\n </wa-button>\n </wa-animation>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'docks-sw-update-indicator': DocksSwUpdateIndicator;\n }\n}\n","import { html } from 'lit';\nimport { customElement, state } from 'lit/decorators.js';\n\nimport { DocksElement } from '@eclipse-docks/core';\n\nimport {\n clearInstallPromptCapture,\n peekInstallPrompt,\n PWA_INSTALL_PROMPT_AVAILABLE,\n type PwaInstallPromptEvent,\n} from './install-prompt-capture';\n\nfunction isRunningAsInstalledPwa(): boolean {\n return (\n window.matchMedia('(display-mode: standalone)').matches ||\n window.matchMedia('(display-mode: window-controls-overlay)').matches ||\n (window.navigator as Navigator & { standalone?: boolean }).standalone === true\n );\n}\n\n@customElement('docks-pwa-install')\nexport class DocksPwaInstall extends DocksElement {\n @state()\n private showInstall = false;\n\n private deferredPrompt: PwaInstallPromptEvent | null = null;\n\n private readonly onPromptAvailable = (): void => {\n this.applyCapturedPrompt();\n };\n\n private readonly onAppInstalled = (): void => {\n clearInstallPromptCapture();\n this.deferredPrompt = null;\n this.showInstall = false;\n };\n\n private applyCapturedPrompt(): void {\n const p = peekInstallPrompt();\n if (!p) {\n return;\n }\n this.deferredPrompt = p;\n this.showInstall = true;\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n if (isRunningAsInstalledPwa()) {\n return;\n }\n this.applyCapturedPrompt();\n window.addEventListener(PWA_INSTALL_PROMPT_AVAILABLE, this.onPromptAvailable);\n window.addEventListener('appinstalled', this.onAppInstalled);\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n window.removeEventListener(PWA_INSTALL_PROMPT_AVAILABLE, this.onPromptAvailable);\n window.removeEventListener('appinstalled', this.onAppInstalled);\n }\n\n private async onInstallClick(): Promise<void> {\n const promptEvent = this.deferredPrompt;\n if (!promptEvent) {\n return;\n }\n await promptEvent.prompt();\n await promptEvent.userChoice.catch(() => {});\n clearInstallPromptCapture();\n this.deferredPrompt = null;\n this.showInstall = false;\n }\n\n protected render() {\n if (!this.showInstall) {\n return html``;\n }\n\n return html`\n <wa-button\n appearance=\"plain\"\n title=\"Install this app on your device\"\n aria-label=\"Install app\"\n @click=${() => void this.onInstallClick()}\n >\n <wa-icon name=\"download\" label=\"\"></wa-icon>\n </wa-button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'docks-pwa-install': DocksPwaInstall;\n }\n}\n","import { contributionRegistry, type HTMLContribution, TOOLBAR_MAIN_CENTER } from '@eclipse-docks/core';\nimport './sw-update-indicator';\nimport './pwa-install-button';\n\nconst TOOLBAR_SW_UPDATE = 'toolbar.swUpdate';\nconst TOOLBAR_PWA_INSTALL = 'toolbar.pwaInstall';\n\ncontributionRegistry.registerContribution(TOOLBAR_MAIN_CENTER, {\n name: TOOLBAR_SW_UPDATE,\n label: 'App update',\n component: `<docks-sw-update-indicator></docks-sw-update-indicator>`,\n} as HTMLContribution);\n\ncontributionRegistry.registerContribution(TOOLBAR_MAIN_CENTER, {\n name: TOOLBAR_PWA_INSTALL,\n label: 'Install app',\n component: `<docks-pwa-install></docks-pwa-install>`,\n} as HTMLContribution);\n"],"mappings":"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"}
|