@equinor/fusion-framework-vite-plugin-spa 1.1.1 → 1.1.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.
@@ -15,5 +15,5 @@
15
15
  * @constant
16
16
  * @type {string}
17
17
  */
18
- export declare const html = "\n <!DOCTYPE html>\n <html>\n <head>\n <title>%FUSION_SPA_TITLE%</title>\n <meta name=\"mode\" content=\"%MODE%\">\n <meta name=\"fusion-spa-plugin-version\" content=\"1.1.1\">\n <link rel=\"stylesheet\" href=\"https://cdn.eds.equinor.com/font/equinor-font.css\" />\n <script type=\"module\" src=\"%FUSION_SPA_BOOTSTRAP%\"></script>\n <script>\n // suppress console error for custom elements already defined. \n // WebComponents should be added by the portal, but not removed from application\n const _customElementsDefine = window.customElements.define;\n window.customElements.define = (name, cl, conf) => {\n if (!customElements.get(name)) {\n _customElementsDefine.call(window.customElements, name, cl, conf);\n }\n };\n </script>\n <style>\n html, body {\n margin: 0;\n padding: 0;\n height: 100%;\n font-family: 'EquinorFont', sans-serif;\n }\n </style>\n </head>\n <body></body>\n </html>\n";
18
+ export declare const html = "\n <!DOCTYPE html>\n <html>\n <head>\n <title>%FUSION_SPA_TITLE%</title>\n <meta name=\"mode\" content=\"%MODE%\">\n <meta name=\"fusion-spa-plugin-version\" content=\"1.1.3\">\n <link rel=\"stylesheet\" href=\"https://cdn.eds.equinor.com/font/equinor-font.css\" />\n <script type=\"module\" src=\"%FUSION_SPA_BOOTSTRAP%\"></script>\n <script>\n // suppress console error for custom elements already defined. \n // WebComponents should be added by the portal, but not removed from application\n const _customElementsDefine = window.customElements.define;\n window.customElements.define = (name, cl, conf) => {\n if (!customElements.get(name)) {\n _customElementsDefine.call(window.customElements, name, cl, conf);\n }\n };\n </script>\n <style>\n html, body {\n margin: 0;\n padding: 0;\n height: 100%;\n font-family: 'EquinorFont', sans-serif;\n }\n </style>\n </head>\n <body></body>\n </html>\n";
19
19
  export default html;
@@ -1 +1 @@
1
- export declare const version = "1.1.1";
1
+ export declare const version = "1.1.3";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/fusion-framework-vite-plugin-spa",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Vite plugin for SPA development",
5
5
  "type": "module",
6
6
  "types": "dist/types/index.d.ts",
@@ -47,10 +47,10 @@
47
47
  "rollup": "^4.39.0",
48
48
  "typescript": "^5.8.2",
49
49
  "vite": "^7.1.5",
50
- "@equinor/fusion-framework-module": "^5.0.2",
51
- "@equinor/fusion-framework-module-http": "^7.0.1",
52
- "@equinor/fusion-framework-module-service-discovery": "^9.0.1",
53
- "@equinor/fusion-framework-module-msal": "^5.0.1"
50
+ "@equinor/fusion-framework-module-http": "7.0.1",
51
+ "@equinor/fusion-framework-module": "5.0.2",
52
+ "@equinor/fusion-framework-module-msal": "5.0.1",
53
+ "@equinor/fusion-framework-module-service-discovery": "9.0.1"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsc -b",
@@ -13,26 +13,21 @@ export async function registerServiceWorker(framework: ModulesInstance<[MsalModu
13
13
  return;
14
14
  }
15
15
 
16
- try {
17
- // register the service worker
18
- const registration = await navigator.serviceWorker.register('/@fusion-spa-sw.js', {
19
- type: 'module',
20
- scope: '/',
21
- });
22
-
23
- // wait for the service worker to be ready
24
- await navigator.serviceWorker.ready;
25
-
26
- // allow the service worker to start receiving messages
27
- navigator.serviceWorker.startMessages();
28
-
29
- // send the config to the service worker
30
- registration.active?.postMessage({
16
+ /**
17
+ * Helper function to send configuration to the service worker
18
+ */
19
+ const sendConfigToServiceWorker = (worker: ServiceWorker) => {
20
+ worker.postMessage({
31
21
  type: 'INIT_CONFIG',
32
22
  config: resourceConfigs,
33
23
  });
24
+ };
25
+
26
+ try {
27
+ // allow the service worker to start receiving messages early
28
+ navigator.serviceWorker.startMessages();
34
29
 
35
- // listen for messages from the service worker
30
+ // listen for messages from the service worker (set up before registration)
36
31
  navigator.serviceWorker.addEventListener('message', async (event) => {
37
32
  if (event.data.type === 'GET_TOKEN') {
38
33
  try {
@@ -61,6 +56,80 @@ export async function registerServiceWorker(framework: ModulesInstance<[MsalModu
61
56
  }
62
57
  }
63
58
  });
59
+
60
+ // register the service worker
61
+ // updateViaCache: 'none' ensures the service worker script is always fetched fresh
62
+ // This is important during development to pick up code changes
63
+ const registration = await navigator.serviceWorker.register('/@fusion-spa-sw.js', {
64
+ type: 'module',
65
+ scope: '/',
66
+ updateViaCache: 'none',
67
+ });
68
+
69
+ // Handle service worker updates/installations
70
+ // If there's a service worker waiting or installing, send config when it activates
71
+ if (registration.waiting) {
72
+ sendConfigToServiceWorker(registration.waiting);
73
+ }
74
+ if (registration.installing) {
75
+ registration.installing.addEventListener('statechange', (event) => {
76
+ const worker = event.target as ServiceWorker;
77
+ if (worker.state === 'activated') {
78
+ sendConfigToServiceWorker(worker);
79
+ }
80
+ });
81
+ }
82
+
83
+ // Listen for controller changes (happens during hard refresh or updates)
84
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
85
+ if (navigator.serviceWorker.controller) {
86
+ sendConfigToServiceWorker(navigator.serviceWorker.controller);
87
+ }
88
+ });
89
+
90
+ // wait for the service worker to be ready
91
+ const readyRegistration = await navigator.serviceWorker.ready;
92
+
93
+ // ensure we have an active service worker before sending config
94
+ const activeWorker = readyRegistration.active;
95
+ if (!activeWorker) {
96
+ console.error('[Service Worker Registration] Service worker is not active after ready state');
97
+ return;
98
+ }
99
+
100
+ // CRITICAL: Wait for the service worker to become the controller
101
+ // This ensures the service worker can intercept fetch requests
102
+ if (!navigator.serviceWorker.controller) {
103
+ await new Promise<void>((resolve) => {
104
+ let checkInterval: NodeJS.Timeout;
105
+
106
+ const finish = () => {
107
+ clearInterval(checkInterval);
108
+ navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange);
109
+ resolve();
110
+ };
111
+
112
+ const onControllerChange = () => finish();
113
+
114
+ // If controllerchange fires, the service worker has taken control
115
+ navigator.serviceWorker.addEventListener('controllerchange', onControllerChange);
116
+
117
+ // Polling fallback and timeout to prevent infinite waiting
118
+ checkInterval = setInterval(() => {
119
+ if (navigator.serviceWorker.controller) finish();
120
+ }, 200);
121
+
122
+ setTimeout(finish, 5000);
123
+ });
124
+ }
125
+
126
+ // send the config to the active service worker
127
+ sendConfigToServiceWorker(activeWorker);
128
+
129
+ // Also send to the controller if it exists and is different from active
130
+ if (navigator.serviceWorker.controller && navigator.serviceWorker.controller !== activeWorker) {
131
+ sendConfigToServiceWorker(navigator.serviceWorker.controller);
132
+ }
64
133
  } catch (error) {
65
134
  console.error('Service Worker registration failed:', error);
66
135
  }
package/src/html/sw.ts CHANGED
@@ -176,10 +176,17 @@ self.addEventListener('activate', (event: ExtendableEvent) => {
176
176
  });
177
177
 
178
178
  // Handle configuration from main thread
179
- self.addEventListener('message', (event: ExtendableMessageEvent) => {
179
+ self.addEventListener('message', async (event: ExtendableMessageEvent) => {
180
180
  const { type, config } = event.data;
181
181
  if (type === 'INIT_CONFIG') {
182
182
  resourceConfigurations = config as ResourceConfiguration[];
183
+
184
+ // CRITICAL: Force skipWaiting() and claim clients to ensure this service worker takes control
185
+ // This handles both waiting and already-active service workers during hard refresh
186
+ // - skipWaiting() forces activation if the service worker is in waiting state
187
+ // - clients.claim() takes control of all clients immediately
188
+ await self.skipWaiting();
189
+ await self.clients.claim();
183
190
  }
184
191
  });
185
192
 
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '1.1.1';
2
+ export const version = '1.1.3';