@geogirafe/lib-geoportal 1.1.0-dev.2507911619 → 1.1.0-dev.2527788074

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/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "GeoGirafe PSC",
6
6
  "url": "https://doc.geomapfish.dev"
7
7
  },
8
- "version": "1.1.0-dev.2507911619",
8
+ "version": "1.1.0-dev.2527788074",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
package/service-worker.js CHANGED
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  /**
4
- * To allow offline mode, the first 100 queries made by the application will be cached.
4
+ * To allow offline mode, the first 300 queries made by the application will be cached.
5
5
  * We could have implemented a more specific cache with file extensions for example
6
6
  * But then we should define exactly what should be cached.
7
7
  * This can be quite complex, because we cannot just cache all images,
8
- * as some of them are WMTS or WMTS results and others ar icons.
8
+ * as some of them are WMTS or WMS results and others are icons.
9
9
  * So a simple solution here is to cache all the first queries that are done
10
10
  * by the application when it starts. With this we should have all the necessary
11
11
  * cache to be able to start the application in offline mode
@@ -15,23 +15,37 @@
15
15
  */
16
16
  const sw = globalThis;
17
17
  const currentOrigin = self.location.origin;
18
- let storeVersion; // Version of the store. Value is defined by OfflineManager.
19
- let dbCacheName; // Name of the cache for downloaded data. Value is defined by OfflineManager.
20
- let tilesStoreName; // Name of the store used to downloaded tiles. Value is defined by OfflineManager.
21
- let logLevel; // Current log level. Value is defined by OfflineManager.
22
- let audience; // List of domains for which the Authorization Token and cookies should be sent
23
- let audienceExcludedPaths; // List of regex paths at audience where Authorization Token and cookies should not be sent
24
- let accessToken; // The current access token
25
- let loginState; // The current login status
26
- let authMode; // How the authentication should be sent to the audience
27
- let refererPolicy; // RefererPolicy used for the queries. Default strict-origin-when-cross-origin
18
+ /**
19
+ * The service worker state needs to be persisted
20
+ * because when not using the app, the service worker can be paused by the browser
21
+ * and in this case it will be reinitialized with the next query
22
+ * with a base default empty state.
23
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope
24
+ */
25
+ const stateDbName = 'geogirafe-sw-state';
26
+ const stateStoreName = 'state';
27
+ const stateKey = 'config';
28
+ let initialized = false;
29
+ let state = {
30
+ storeVersion: 1,
31
+ dbCacheName: 'geogirafe-cache',
32
+ tilesStoreName: 'tiles',
33
+ logLevel: 'warning',
34
+ audience: [],
35
+ audienceExcludedPaths: [],
36
+ accessToken: undefined,
37
+ loginState: undefined,
38
+ authMode: undefined,
39
+ refererPolicy: undefined
40
+ };
28
41
  function isLoggedIn() {
29
- return loginState === 'loggedIn' || loginState === 'issuer.loggedIn';
42
+ return state.loginState === 'loggedIn' || state.loginState === 'issuer.loggedIn';
30
43
  }
31
44
  const offlineTimeout = 3000; // Timeout in case of not reachable IndexedDB. (3 sec.)
32
45
  const appCacheName = 'pages'; // Name of the cache for application pages
33
46
  const maxCacheCount = 300; // Number of queries that should be cached by the service-worker for offline usage.
34
47
  let cacheCount = 0; // Counter related to the max value above
48
+ let audienceExcludedPathPatterns = []; // list of regex pattern built from state.audienceExcludedPaths
35
49
  sw.addEventListener('message', handleMessage);
36
50
  sw.addEventListener('install', handleInstall);
37
51
  sw.addEventListener('activate', handleActivate);
@@ -43,79 +57,86 @@ sw.addEventListener('fetch', (event) => {
43
57
  });
44
58
  function handleMessage(event) {
45
59
  if (event.origin !== currentOrigin) {
46
- console.warn(`Message ignored: origin not allwed (${event.origin}`);
60
+ console.warn(`Message ignored: origin not allowed (${event.origin})`);
47
61
  return;
48
62
  }
49
63
  const data = event.data;
50
64
  if (data.logLevel) {
51
- logLevel = data.logLevel;
52
- log(`LogLevel changed: ${logLevel}`);
65
+ state.logLevel = data.logLevel;
66
+ log(`LogLevel changed: ${state.logLevel}`);
53
67
  }
54
68
  if (data.tilesStoreName) {
55
- tilesStoreName = data.tilesStoreName;
56
- log(`tilesStoreName changed: ${tilesStoreName}`);
69
+ state.tilesStoreName = data.tilesStoreName;
70
+ log(`tilesStoreName changed: ${state.tilesStoreName}`);
57
71
  }
58
72
  if (data.storeVersion) {
59
- storeVersion = data.storeVersion;
60
- log(`storeVersion changed: ${storeVersion}`);
73
+ state.storeVersion = data.storeVersion;
74
+ log(`storeVersion changed: ${state.storeVersion}`);
61
75
  }
62
76
  if (data.dbCacheName) {
63
- dbCacheName = data.dbCacheName;
64
- log(`dbCacheName changed: ${dbCacheName}`);
77
+ state.dbCacheName = data.dbCacheName;
78
+ log(`dbCacheName changed: ${state.dbCacheName}`);
65
79
  }
66
80
  if (data.audience) {
67
- audience = data.audience ?? [];
68
- log(`audience changed: ${audience}`);
81
+ state.audience = data.audience ?? [];
82
+ log(`audience changed: ${state.audience}`);
69
83
  }
70
- audienceExcludedPaths = [];
71
84
  if (data.audienceExcludedPaths) {
72
- audienceExcludedPaths = data.audienceExcludedPaths.map((str) => new RegExp(str));
73
- log(`audienceExcludedPaths changed: ${audienceExcludedPaths}`);
85
+ state.audienceExcludedPaths = data.audienceExcludedPaths;
86
+ audienceExcludedPathPatterns = state.audienceExcludedPaths.map((str) => new RegExp(str));
87
+ log(`audienceExcludedPaths changed: ${state.audienceExcludedPaths}`);
74
88
  }
75
89
  if (data.access_token) {
76
- accessToken = data.access_token;
77
- log(`access_token changed: ${accessToken}`);
90
+ state.accessToken = data.access_token;
91
+ log(`access_token changed: ${state.accessToken}`);
78
92
  }
79
93
  if (data.clear_access_token) {
80
- accessToken = undefined;
94
+ state.accessToken = undefined;
81
95
  log('access_token cleared');
82
96
  }
83
97
  if (data.authMode) {
84
- authMode = data.authMode;
85
- log(`authMode changed: ${authMode}`);
98
+ state.authMode = data.authMode;
99
+ log(`authMode changed: ${state.authMode}`);
86
100
  }
87
101
  if (data.refererPolicy) {
88
- refererPolicy = data.refererPolicy;
89
- log(`refererPolicy changed: ${refererPolicy}`);
102
+ state.refererPolicy = data.refererPolicy;
103
+ log(`refererPolicy changed: ${state.refererPolicy}`);
90
104
  }
91
105
  if (data.loginState) {
92
- loginState = data.loginState;
93
- log(`loginState changed: ${loginState}`);
106
+ state.loginState = data.loginState;
107
+ log(`loginState changed: ${state.loginState}`);
94
108
  }
95
- if (data.messageId) {
96
- // Reply to the message with the same messageId
97
- const source = event.source;
98
- if (source) {
99
- source.postMessage({ messageId: data.messageId, status: 'ServiceWorker updated' });
109
+ event.waitUntil((async () => {
110
+ // Persist the service worker state before acknowledging the message.
111
+ await saveState();
112
+ if (data.messageId) {
113
+ const source = event.source;
114
+ if (source) {
115
+ source.postMessage({ messageId: data.messageId, status: 'ServiceWorker updated' });
116
+ }
100
117
  }
101
- }
118
+ })());
102
119
  }
103
120
  function handleInstall() {
104
121
  sw.skipWaiting();
105
- log('Service Worker installed');
122
+ log('Service worker installed');
106
123
  }
107
124
  function handleActivate(event) {
108
125
  event.waitUntil((async () => {
109
- if ('clients' in self) {
126
+ if ('clients' in sw) {
127
+ await sw.clients.claim();
110
128
  const clientsList = await sw.clients.matchAll({ type: 'window' });
111
129
  for (const client of clientsList) {
112
130
  client.navigate(client.url);
113
- log('Page reloaded by the Service Worker.');
131
+ log('Page reloaded by the service worker.');
114
132
  }
115
133
  }
116
134
  })());
117
135
  }
118
136
  async function handleFetchEvent(event) {
137
+ if (!initialized) {
138
+ await loadState();
139
+ }
119
140
  const newRequest = getRequest(event.request);
120
141
  let response = await fetchAndCache(newRequest);
121
142
  // Use cache only for GET queries
@@ -125,7 +146,7 @@ async function handleFetchEvent(event) {
125
146
  response = await loadFromCache(event.request);
126
147
  }
127
148
  if (!response) {
128
- // Not found in cache. We try to load from indexedDB
149
+ // Not found in cache. We try to load from IndexedDB.
129
150
  response = await loadFromIndexedDB(event.request);
130
151
  }
131
152
  }
@@ -148,7 +169,7 @@ function isCacheAllowed(request) {
148
169
  async function fetchAndCache(request) {
149
170
  try {
150
171
  const response = await fetch(request);
151
- if (cacheCount < maxCacheCount && isCacheAllowed(request) && response.type !== 'opaque') {
172
+ if (cacheCount < maxCacheCount && isCacheAllowed(request) && response.ok && response.type !== 'opaque') {
152
173
  // Fetch was successful. We cache the result if necessary and return the response.
153
174
  cacheCount++;
154
175
  log(`SW ${cacheCount}/${maxCacheCount} caching ${request.url} for offline use.`);
@@ -169,19 +190,19 @@ function getRequest(request) {
169
190
  try {
170
191
  const requestedUrl = new URL(request.url);
171
192
  const hostname = requestedUrl.hostname;
172
- const shouldExclude = audienceExcludedPaths.some((pattern) => pattern.test(requestedUrl.pathname));
173
- if (audience?.includes(hostname) && !shouldExclude) {
193
+ const shouldExclude = audienceExcludedPathPatterns.some((pattern) => pattern.test(requestedUrl.pathname));
194
+ if (state.audience?.includes(hostname) && !shouldExclude) {
174
195
  // Prepare headers
175
196
  const headers = new Headers(request.headers);
176
- if (authMode == 'token' && isLoggedIn() && accessToken) {
177
- headers.set('Authorization', `Bearer ${accessToken}`);
197
+ if (state.authMode == 'token' && isLoggedIn() && state.accessToken) {
198
+ headers.set('Authorization', `Bearer ${state.accessToken}`);
178
199
  }
179
200
  // Prepare options
180
201
  const fetchOptions = {
181
202
  headers: headers,
182
- credentials: isLoggedIn() && authMode === 'cookie' ? 'include' : 'omit',
203
+ credentials: isLoggedIn() && state.authMode === 'cookie' ? 'include' : 'omit',
183
204
  referrer: request.referrer,
184
- referrerPolicy: refererPolicy
205
+ referrerPolicy: state.refererPolicy
185
206
  };
186
207
  return new Request(request, fetchOptions);
187
208
  }
@@ -209,15 +230,15 @@ async function loadFromCache(request) {
209
230
  async function loadFromIndexedDB(request) {
210
231
  try {
211
232
  const database = await openIndexedDB();
212
- const transaction = database.transaction([tilesStoreName], 'readonly');
213
- const store = transaction.objectStore(tilesStoreName);
233
+ const transaction = database.transaction([state.tilesStoreName], 'readonly');
234
+ const store = transaction.objectStore(state.tilesStoreName);
214
235
  const index = store.index('url');
215
236
  return new Promise((resolve, reject) => {
216
237
  const dbRequest = index.get(request.url);
217
238
  dbRequest.onsuccess = () => {
218
239
  if (dbRequest.result) {
219
240
  const blob = dbRequest.result.data;
220
- log('Tile found in Cache.');
241
+ log('Tile found in cache.');
221
242
  resolve(new Response(blob));
222
243
  }
223
244
  else {
@@ -235,11 +256,11 @@ async function loadFromIndexedDB(request) {
235
256
  }
236
257
  }
237
258
  /**
238
- * Open indexedDB
259
+ * Open IndexedDB
239
260
  */
240
261
  async function openIndexedDB() {
241
262
  return new Promise((resolve, reject) => {
242
- const request = indexedDB.open(dbCacheName, storeVersion);
263
+ const request = indexedDB.open(state.dbCacheName, state.storeVersion);
243
264
  let timedOut = false;
244
265
  const timeoutId = setTimeout(() => {
245
266
  timedOut = true;
@@ -259,8 +280,52 @@ async function openIndexedDB() {
259
280
  };
260
281
  });
261
282
  }
283
+ /**
284
+ * Open IndexedDB for the service worker state
285
+ */
286
+ async function openStateDB() {
287
+ return new Promise((resolve, reject) => {
288
+ const request = indexedDB.open(stateDbName, 1);
289
+ request.onupgradeneeded = () => {
290
+ request.result.createObjectStore(stateStoreName);
291
+ };
292
+ request.onsuccess = () => resolve(request.result);
293
+ request.onerror = () => reject(request.error);
294
+ });
295
+ }
296
+ /**
297
+ * Saves the service worker state
298
+ */
299
+ async function saveState() {
300
+ log('Save state');
301
+ const db = await openStateDB();
302
+ return new Promise((resolve, reject) => {
303
+ const tx = db.transaction(stateStoreName, 'readwrite');
304
+ tx.objectStore(stateStoreName).put(state, stateKey);
305
+ tx.oncomplete = () => resolve();
306
+ tx.onerror = () => reject(tx.error);
307
+ });
308
+ }
309
+ /**
310
+ * Reloads the service worker state from indexDB
311
+ */
312
+ async function loadState() {
313
+ log('Reload state');
314
+ const db = await openStateDB();
315
+ const loadedState = await new Promise((resolve, reject) => {
316
+ const tx = db.transaction(stateStoreName, 'readonly');
317
+ const req = tx.objectStore(stateStoreName).get(stateKey);
318
+ req.onsuccess = () => resolve(req.result);
319
+ req.onerror = () => reject(req.error);
320
+ });
321
+ if (loadedState) {
322
+ state = loadedState;
323
+ audienceExcludedPathPatterns = state.audienceExcludedPaths.map((str) => new RegExp(str));
324
+ }
325
+ initialized = true;
326
+ }
262
327
  function log(str, error) {
263
- if (logLevel === 'debug') {
328
+ if (state.logLevel === 'debug') {
264
329
  console.debug(`SW: ${str}`, error ?? '');
265
330
  }
266
331
  }
@@ -1 +1 @@
1
- {"version":"1.1.0-dev.2507911619", "build":"2507911619", "date":"07/05/2026"}
1
+ {"version":"1.1.0-dev.2527788074", "build":"2527788074", "date":"15/05/2026"}