@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 +1 -1
- package/service-worker.js +124 -59
- package/templates/public/about.json +1 -1
package/package.json
CHANGED
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
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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
|
|
73
|
-
|
|
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
|
-
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
source
|
|
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
|
|
122
|
+
log('Service worker installed');
|
|
106
123
|
}
|
|
107
124
|
function handleActivate(event) {
|
|
108
125
|
event.waitUntil((async () => {
|
|
109
|
-
if ('clients' in
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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.
|
|
1
|
+
{"version":"1.1.0-dev.2527788074", "build":"2527788074", "date":"15/05/2026"}
|