@underpostnet/underpost 2.97.5 → 2.98.0
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/README.md +2 -2
- package/cli.md +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -1
- package/scripts/rocky-pwa.sh +200 -0
- package/src/api/document/document.model.js +1 -1
- package/src/api/document/document.service.js +89 -99
- package/src/client/components/core/Auth.js +2 -0
- package/src/client/components/core/Content.js +2 -2
- package/src/client/components/core/Css.js +30 -0
- package/src/client/components/core/FileExplorer.js +699 -42
- package/src/client/components/core/Input.js +3 -1
- package/src/client/components/core/Panel.js +2 -1
- package/src/client/components/core/Responsive.js +15 -7
- package/src/client/components/core/SearchBox.js +0 -110
- package/src/client/components/core/Translate.js +50 -0
- package/src/client/services/default/default.management.js +3 -12
- package/src/client/sw/default.sw.js +107 -184
- package/src/index.js +1 -1
|
@@ -43,7 +43,9 @@ const fileFormDataFactory = (e, extensions) => {
|
|
|
43
43
|
logger.error('Invalid file extension', e.target.files[keyFile]);
|
|
44
44
|
continue;
|
|
45
45
|
}
|
|
46
|
-
form.append(e.target.files[keyFile].name, e.target.files[keyFile]);
|
|
46
|
+
// form.append(e.target.files[keyFile].name, e.target.files[keyFile]);
|
|
47
|
+
// Use standard 'file' field name for all files - server expects this format
|
|
48
|
+
form.append('file', e.target.files[keyFile]);
|
|
47
49
|
}
|
|
48
50
|
return form;
|
|
49
51
|
};
|
|
@@ -348,7 +348,7 @@ const Panel = {
|
|
|
348
348
|
? 'rgba(255,255,255,0.9)'
|
|
349
349
|
: 'rgba(0,0,0,0.85)'}; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
|
|
350
350
|
>
|
|
351
|
-
${obj.userInfo.username ||
|
|
351
|
+
${obj.userInfo.username || 'Unknown'}
|
|
352
352
|
</a>
|
|
353
353
|
<span
|
|
354
354
|
style="font-size: 11px; color: ${darkTheme
|
|
@@ -644,6 +644,7 @@ const Panel = {
|
|
|
644
644
|
break;
|
|
645
645
|
case 'file':
|
|
646
646
|
setTimeout(() => {
|
|
647
|
+
if (!s(`.${modelData.id}`)) return;
|
|
647
648
|
s(`.${modelData.id}`).fileNameInputExtDefaultContent = fileNameInputExtDefaultContent;
|
|
648
649
|
s(`.${modelData.id}`).onchange = async (e) => {
|
|
649
650
|
if (!Object.keys(e.target.files).length) return;
|
|
@@ -46,13 +46,21 @@ const Responsive = {
|
|
|
46
46
|
// alternative option
|
|
47
47
|
// this.Observer = new ResizeObserver(this.resizeCallback);
|
|
48
48
|
// this.Observer.observe(document.documentElement);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
|
|
50
|
+
// Check if screen.orientation is available before adding event listener
|
|
51
|
+
if (
|
|
52
|
+
typeof screen !== 'undefined' &&
|
|
53
|
+
screen.orientation &&
|
|
54
|
+
typeof screen.orientation.addEventListener === 'function'
|
|
55
|
+
) {
|
|
56
|
+
screen.orientation.addEventListener('change', (event) => {
|
|
57
|
+
const type = event.target.type; // landscape-primary | portrait-primary
|
|
58
|
+
const angle = event.target.angle; // 90 degrees.
|
|
59
|
+
logger.info(`ScreenOrientation change: ${type}, ${angle} degrees.`);
|
|
60
|
+
setTimeout(() => window.onresize({}, true));
|
|
61
|
+
Responsive.triggerEventsOrientation();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
56
64
|
Responsive.matchMediaOrientationInstance = matchMedia('screen and (orientation:portrait)');
|
|
57
65
|
|
|
58
66
|
Responsive.matchMediaOrientationInstance.onchange = (e) => {
|
|
@@ -746,116 +746,6 @@ const SearchBox = {
|
|
|
746
746
|
}, 0);
|
|
747
747
|
},
|
|
748
748
|
|
|
749
|
-
/**
|
|
750
|
-
* Debounce helper for search-while-typing
|
|
751
|
-
*/
|
|
752
|
-
debounce: function (func, wait) {
|
|
753
|
-
let timeout;
|
|
754
|
-
return function executedFunction(...args) {
|
|
755
|
-
const later = () => {
|
|
756
|
-
clearTimeout(timeout);
|
|
757
|
-
func(...args);
|
|
758
|
-
};
|
|
759
|
-
clearTimeout(timeout);
|
|
760
|
-
timeout = setTimeout(later, wait);
|
|
761
|
-
};
|
|
762
|
-
},
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
* Sets up a search input element with automatic search on typing.
|
|
766
|
-
* Attaches debounced input event handler and manages search lifecycle.
|
|
767
|
-
* @memberof SearchBoxClient.SearchBox
|
|
768
|
-
* @param {string} inputId - Input element ID or class name.
|
|
769
|
-
* @param {string} resultsContainerId - Results container element ID or class name.
|
|
770
|
-
* @param {object} [context={}] - Configuration context object.
|
|
771
|
-
* @param {number} [context.debounceTime=300] - Debounce delay in milliseconds.
|
|
772
|
-
* @param {number} [context.minQueryLength=1] - Minimum query length to trigger search.
|
|
773
|
-
* @returns {Function} Cleanup function to remove event listeners.
|
|
774
|
-
*/
|
|
775
|
-
setupSearchInput: function (inputId, resultsContainerId, context = {}) {
|
|
776
|
-
const input = s(`#${inputId}`) || s(`.${inputId}`);
|
|
777
|
-
if (!input) {
|
|
778
|
-
logger.warn(`Input ${inputId} not found`);
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
const debounceTime = context.debounceTime || 300;
|
|
783
|
-
|
|
784
|
-
const performSearch = this.debounce(async (query) => {
|
|
785
|
-
const trimmedQuery = query ? query.trim() : '';
|
|
786
|
-
const minLength = context.minQueryLength !== undefined ? context.minQueryLength : 1;
|
|
787
|
-
|
|
788
|
-
// Show recent results when query is empty
|
|
789
|
-
if (trimmedQuery.length === 0) {
|
|
790
|
-
const recentResults = this.RecentResults.getAll();
|
|
791
|
-
this.renderResults(recentResults, resultsContainerId, context);
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// Support single character searches by default (minQueryLength: 1)
|
|
796
|
-
// Can be configured via context.minQueryLength for different use cases
|
|
797
|
-
if (trimmedQuery.length < minLength) {
|
|
798
|
-
this.renderResults([], resultsContainerId, context);
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
const results = await this.search(trimmedQuery, context);
|
|
803
|
-
this.renderResults(results, resultsContainerId, context);
|
|
804
|
-
}, debounceTime);
|
|
805
|
-
|
|
806
|
-
// Store the handler reference
|
|
807
|
-
const handlerId = `search-handler-${inputId}`;
|
|
808
|
-
if (this.Data[handlerId]) {
|
|
809
|
-
input.removeEventListener('input', this.Data[handlerId]);
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
this.Data[handlerId] = (e) => {
|
|
813
|
-
performSearch(e.target.value);
|
|
814
|
-
};
|
|
815
|
-
|
|
816
|
-
input.addEventListener('input', this.Data[handlerId]);
|
|
817
|
-
|
|
818
|
-
logger.info(`Setup search input: ${inputId}`);
|
|
819
|
-
|
|
820
|
-
return () => {
|
|
821
|
-
input.removeEventListener('input', this.Data[handlerId]);
|
|
822
|
-
delete this.Data[handlerId];
|
|
823
|
-
};
|
|
824
|
-
},
|
|
825
|
-
|
|
826
|
-
/**
|
|
827
|
-
* Debounces a function call to reduce excessive invocations.
|
|
828
|
-
* Used for search input to prevent searching on every keystroke.
|
|
829
|
-
* @memberof SearchBoxClient.SearchBox
|
|
830
|
-
* @param {Function} func - The function to debounce.
|
|
831
|
-
* @param {number} wait - Delay in milliseconds before invoking the function.
|
|
832
|
-
* @returns {Function} Debounced function that delays invocation.
|
|
833
|
-
*/
|
|
834
|
-
debounce: function (func, wait) {
|
|
835
|
-
let timeout;
|
|
836
|
-
|
|
837
|
-
const later = function (...args) {
|
|
838
|
-
timeout = null;
|
|
839
|
-
func(...args);
|
|
840
|
-
};
|
|
841
|
-
|
|
842
|
-
return function (...args) {
|
|
843
|
-
if (timeout) clearTimeout(timeout);
|
|
844
|
-
timeout = setTimeout(() => later(...args), wait);
|
|
845
|
-
};
|
|
846
|
-
},
|
|
847
|
-
|
|
848
|
-
/**
|
|
849
|
-
* Clears all registered search providers.
|
|
850
|
-
* Useful for cleanup or resetting search functionality.
|
|
851
|
-
* @memberof SearchBoxClient.SearchBox
|
|
852
|
-
* @returns {void}
|
|
853
|
-
*/
|
|
854
|
-
clearProviders: function () {
|
|
855
|
-
this.providers = [];
|
|
856
|
-
logger.info('Cleared all search providers');
|
|
857
|
-
},
|
|
858
|
-
|
|
859
749
|
/**
|
|
860
750
|
* Gets base CSS styles for SearchBox with theme-aware styling.
|
|
861
751
|
* Uses subThemeManager colors for consistent theming across light and dark modes.
|
|
@@ -180,6 +180,7 @@ const TranslateCore = {
|
|
|
180
180
|
Translate.Data['load'] = { en: 'load', es: 'Cargar' };
|
|
181
181
|
Translate.Data['settings'] = { en: 'settings', es: 'configuraciones' };
|
|
182
182
|
Translate.Data['search'] = { en: 'Search', es: 'Buscar' };
|
|
183
|
+
Translate.Data['filter-by-file-name'] = { en: 'Filter by file name', es: 'Filtrar por nombre de archivo' };
|
|
183
184
|
Translate.Data['view'] = { en: 'view', es: 'ver' };
|
|
184
185
|
Translate.Data['user'] = { en: 'User', es: 'Usuario' };
|
|
185
186
|
Translate.Data['pass'] = { en: 'Password', es: 'Contraseña' };
|
|
@@ -397,6 +398,55 @@ const TranslateCore = {
|
|
|
397
398
|
};
|
|
398
399
|
Translate.Data['resend'] = { en: 'Resend', es: 'Reenviar' };
|
|
399
400
|
Translate.Data['delete-account'] = { en: 'Delete Account', es: 'Borrar cuenta' };
|
|
401
|
+
|
|
402
|
+
Translate.Data['doc-title'] = {
|
|
403
|
+
en: 'Doc Title',
|
|
404
|
+
es: 'Título del documento',
|
|
405
|
+
};
|
|
406
|
+
Translate.Data['md-file-name'] = {
|
|
407
|
+
en: 'MD File Name',
|
|
408
|
+
es: 'Nombre de archivo MD',
|
|
409
|
+
};
|
|
410
|
+
Translate.Data['generic-file-name'] = {
|
|
411
|
+
en: 'Generic File Name',
|
|
412
|
+
es: 'Nombre de archivo genérico',
|
|
413
|
+
};
|
|
414
|
+
Translate.Data['edit-document'] = {
|
|
415
|
+
en: 'Edit Document',
|
|
416
|
+
es: 'Editar documento',
|
|
417
|
+
};
|
|
418
|
+
Translate.Data['location'] = {
|
|
419
|
+
en: 'Location',
|
|
420
|
+
es: 'Ubicación',
|
|
421
|
+
};
|
|
422
|
+
Translate.Data['save'] = {
|
|
423
|
+
en: 'Save',
|
|
424
|
+
es: 'Guardar',
|
|
425
|
+
};
|
|
426
|
+
Translate.Data['success-update-document'] = {
|
|
427
|
+
en: 'Document updated successfully.',
|
|
428
|
+
es: 'Documento actualizado con éxito.',
|
|
429
|
+
};
|
|
430
|
+
Translate.Data['error-update-document'] = {
|
|
431
|
+
en: 'Error updating document.',
|
|
432
|
+
es: 'Error al actualizar el documento.',
|
|
433
|
+
};
|
|
434
|
+
Translate.Data['error-title-required'] = {
|
|
435
|
+
en: 'Title is required.',
|
|
436
|
+
es: 'El título es obligatorio.',
|
|
437
|
+
};
|
|
438
|
+
Translate.Data['editing'] = {
|
|
439
|
+
en: 'Editing',
|
|
440
|
+
es: 'Editando',
|
|
441
|
+
};
|
|
442
|
+
Translate.Data['no-md-file-attached'] = {
|
|
443
|
+
en: 'No markdown file attached to this document',
|
|
444
|
+
es: 'No hay archivo markdown adjunto a este documento',
|
|
445
|
+
};
|
|
446
|
+
Translate.Data['no-generic-file-attached'] = {
|
|
447
|
+
en: 'No generic file attached to this document',
|
|
448
|
+
es: 'No hay archivo genérico adjunto a este documento',
|
|
449
|
+
};
|
|
400
450
|
Translate.Data['success-delete-account'] = {
|
|
401
451
|
en: 'Account deleted successfully.',
|
|
402
452
|
es: 'Cuenta borrada con éxito.',
|
|
@@ -910,18 +910,9 @@ const DefaultManagement = {
|
|
|
910
910
|
s(`.management-table-btn-save-${id}`).click();
|
|
911
911
|
}
|
|
912
912
|
} else {
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
html: result.status === 'error' ? result.message : `${Translate.Render('success-update-item')}`,
|
|
917
|
-
status: result.status,
|
|
918
|
-
});
|
|
919
|
-
if (result.status === 'success') {
|
|
920
|
-
AgGrid.grids[gridId].applyTransaction({
|
|
921
|
-
update: [event.data],
|
|
922
|
-
});
|
|
923
|
-
DefaultManagement.loadTable(id, { reload: false });
|
|
924
|
-
}
|
|
913
|
+
// Skip update here - onCellValueChanged already handles auto-save for existing rows
|
|
914
|
+
// This prevents duplicate notifications and API calls
|
|
915
|
+
return;
|
|
925
916
|
}
|
|
926
917
|
},
|
|
927
918
|
...(options.gridOptions ? options.gridOptions : undefined),
|
|
@@ -6,199 +6,122 @@
|
|
|
6
6
|
* @namespace PwaServiceWorker
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* @property {string} PROXY_PATH - Base path for proxying requests.
|
|
22
|
-
*/
|
|
23
|
-
constructor() {
|
|
24
|
-
// Configuration properties equivalent to the original global constants
|
|
25
|
-
this.PRE_CACHED_RESOURCES = self.renderPayload?.PRE_CACHED_RESOURCES ?? [];
|
|
26
|
-
this.CACHE_NAME = self.renderPayload?.CACHE_NAME ?? 'app-cache';
|
|
27
|
-
this.PROXY_PATH = self.renderPayload?.PROXY_PATH ?? '/';
|
|
28
|
-
|
|
29
|
-
console.log(`Service Worker Initialized. Cache: ${this.CACHE_NAME}, Proxy: ${this.PROXY_PATH}`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Registers event listeners for the service worker lifecycle and requests.
|
|
34
|
-
* @method
|
|
35
|
-
* @memberof PwaServiceWorker
|
|
36
|
-
*/
|
|
37
|
-
run() {
|
|
38
|
-
// Bind methods to 'this' (the instance) before attaching to self
|
|
39
|
-
self.addEventListener('install', this._onInstall.bind(this));
|
|
40
|
-
self.addEventListener('activate', this._onActivate.bind(this));
|
|
41
|
-
self.addEventListener('fetch', this._onFetch.bind(this));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Handles the 'install' event. Skips waiting and precaches static assets.
|
|
46
|
-
* @param {ExtendableEvent} event
|
|
47
|
-
* @memberof PwaServiceWorker
|
|
48
|
-
*/
|
|
49
|
-
_onInstall(event) {
|
|
50
|
-
// Activate right away
|
|
51
|
-
self.skipWaiting();
|
|
52
|
-
|
|
53
|
-
event.waitUntil(
|
|
54
|
-
(async () => {
|
|
55
|
-
// Open the app's cache using the configured name.
|
|
56
|
-
const cache = await caches.open(this.CACHE_NAME);
|
|
57
|
-
// Cache all static resources.
|
|
58
|
-
try {
|
|
59
|
-
console.log(`Precaching ${this.PRE_CACHED_RESOURCES.length} resources...`);
|
|
60
|
-
await cache.addAll(this.PRE_CACHED_RESOURCES);
|
|
61
|
-
} catch (error) {
|
|
62
|
-
console.error('Error during precaching resources:', error);
|
|
63
|
-
}
|
|
64
|
-
})(),
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Handles the 'activate' event. Enables navigation preload and takes control
|
|
70
|
-
* of uncontrolled clients immediately.
|
|
71
|
-
* @param {ExtendableEvent} event
|
|
72
|
-
* @memberof PwaServiceWorker
|
|
73
|
-
*/
|
|
74
|
-
_onActivate(event) {
|
|
75
|
-
event.waitUntil(
|
|
76
|
-
(async () => {
|
|
77
|
-
// Enable navigation preload if it's supported.
|
|
78
|
-
if ('navigationPreload' in self.registration) {
|
|
79
|
-
await self.registration.navigationPreload.enable();
|
|
80
|
-
console.log('Navigation Preload enabled.');
|
|
81
|
-
}
|
|
82
|
-
})(),
|
|
83
|
-
);
|
|
84
|
-
// Tell the active service worker to take control of the page immediately.
|
|
85
|
-
self.clients.claim();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Handles the 'fetch' event, implementing the Cache-First strategy with
|
|
90
|
-
* complex offline and maintenance fallbacks.
|
|
91
|
-
* @param {FetchEvent} event
|
|
92
|
-
* @memberof PwaServiceWorker
|
|
93
|
-
*/
|
|
94
|
-
_onFetch(event) {
|
|
95
|
-
// Only handle HTTP/HTTPS requests that are not cross-origin (optional, but robust)
|
|
96
|
-
if (event.request.url.startsWith('http')) {
|
|
97
|
-
event.respondWith(this._handleFetchRequest(event));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Core logic to handle fetching, caching, and fallbacks.
|
|
103
|
-
* @param {FetchEvent} event
|
|
104
|
-
* @returns {Promise<Response>}
|
|
105
|
-
* @memberof PwaServiceWorker
|
|
106
|
-
*/
|
|
107
|
-
async _handleFetchRequest(event) {
|
|
108
|
-
// 1. Try Navigation Preload (if available) or network first
|
|
109
|
-
try {
|
|
110
|
-
const preloadResponse = await event.preloadResponse;
|
|
111
|
-
if (preloadResponse) return preloadResponse;
|
|
112
|
-
|
|
113
|
-
// Fall through to network request if no preload response
|
|
114
|
-
const networkResponse = await fetch(event.request);
|
|
115
|
-
|
|
116
|
-
// OPTIONAL: If the network request is successful, cache it for future use (stale-while-revalidate logic)
|
|
117
|
-
// Omitted for strict equivalence, as original only had complex fallback, not runtime caching.
|
|
118
|
-
|
|
119
|
-
return networkResponse;
|
|
120
|
-
} catch (error) {
|
|
121
|
-
console.error('Network request failed. Attempting cache/fallback logic.', event.request.url, error);
|
|
122
|
-
|
|
123
|
-
// 2. Try to match the request in the cache
|
|
9
|
+
const PRE_CACHED_RESOURCES = self.renderPayload?.PRE_CACHED_RESOURCES ? self.renderPayload.PRE_CACHED_RESOURCES : [];
|
|
10
|
+
const CACHE_NAME = self.renderPayload?.CACHE_NAME ? self.renderPayload.CACHE_NAME : 'app-cache';
|
|
11
|
+
const PROXY_PATH = self.renderPayload?.PROXY_PATH ? self.renderPayload.PROXY_PATH : '/';
|
|
12
|
+
self.addEventListener('install', (event) => {
|
|
13
|
+
// Activate right away
|
|
14
|
+
self.skipWaiting();
|
|
15
|
+
|
|
16
|
+
event.waitUntil(
|
|
17
|
+
(async () => {
|
|
18
|
+
// Open the app's cache.
|
|
19
|
+
const cache = await caches.open(CACHE_NAME);
|
|
20
|
+
// Cache all static resources.
|
|
124
21
|
try {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
22
|
+
await cache.addAll(PRE_CACHED_RESOURCES);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error(error);
|
|
25
|
+
}
|
|
26
|
+
// for (const cacheKey of PRE_CACHED_RESOURCES) {
|
|
27
|
+
// try {
|
|
28
|
+
// await cache.add(cacheKey);
|
|
29
|
+
// } catch (error) {
|
|
30
|
+
// console.error(error, cacheKey);
|
|
31
|
+
// }
|
|
32
|
+
// }
|
|
33
|
+
})(),
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
self.addEventListener('activate', (event) => {
|
|
38
|
+
event.waitUntil(
|
|
39
|
+
(async () => {
|
|
40
|
+
// Enable navigation preload if it's supported.
|
|
41
|
+
// See https://developers.google.com/web/updates/2017/02/navigation-preload
|
|
42
|
+
if ('navigationPreload' in self.registration) {
|
|
43
|
+
await self.registration.navigationPreload.enable();
|
|
44
|
+
}
|
|
45
|
+
})(),
|
|
46
|
+
);
|
|
47
|
+
// Tell the active service worker to take control of the page immediately.
|
|
48
|
+
self.clients.claim();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
self.addEventListener('fetch', (event) => {
|
|
52
|
+
// Cache-First Strategy
|
|
53
|
+
event.respondWith(
|
|
54
|
+
(async () => {
|
|
55
|
+
// First, try to use the navigation preload response if it's supported.
|
|
56
|
+
try {
|
|
57
|
+
(async () => {
|
|
58
|
+
// Get the client.
|
|
59
|
+
if (event.request.url.match(location.origin)) {
|
|
60
|
+
const client = await clients.get(event.clientId);
|
|
61
|
+
if (client)
|
|
62
|
+
client.postMessage({
|
|
63
|
+
status: 'loader',
|
|
64
|
+
path: event.request.url.slice(location.origin.length),
|
|
65
|
+
});
|
|
140
66
|
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
} catch (
|
|
146
|
-
console.error('
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
onLine: navigator.onLine,
|
|
150
|
-
});
|
|
67
|
+
})();
|
|
68
|
+
const preloadResponse = await event.preloadResponse;
|
|
69
|
+
if (preloadResponse) return preloadResponse;
|
|
70
|
+
return await fetch(event.request);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Fetch failed; returning offline page instead.', event.request.url, error);
|
|
73
|
+
// Fallback to the offline page.
|
|
74
|
+
const path = PRE_CACHED_RESOURCES.find((path) => event.request.url.match(path.replaceAll('/index.html', '')));
|
|
151
75
|
|
|
152
|
-
// 4. Complex Fallback Logic (Offline or Maintenance)
|
|
153
76
|
try {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
77
|
+
const cachedResponse = await caches.match(event.request);
|
|
78
|
+
if (cachedResponse) return cachedResponse;
|
|
79
|
+
const cache = await caches.open(CACHE_NAME);
|
|
80
|
+
const preCachedResponse = await cache.match(path);
|
|
81
|
+
if (!preCachedResponse) throw new Error(error.message);
|
|
82
|
+
return preCachedResponse;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('Error opening cache for pre cached page', {
|
|
85
|
+
url: event.request.url,
|
|
86
|
+
error,
|
|
87
|
+
onLine: navigator.onLine,
|
|
88
|
+
});
|
|
89
|
+
try {
|
|
90
|
+
if (!navigator.onLine) {
|
|
91
|
+
if (event.request.method.toUpperCase() === 'GET') {
|
|
92
|
+
const cache = await caches.open(CACHE_NAME);
|
|
93
|
+
const preCachedResponse = await cache.match(
|
|
94
|
+
`${PROXY_PATH === '/' ? '' : PROXY_PATH}/offline/index.html`,
|
|
95
|
+
);
|
|
96
|
+
if (!preCachedResponse) throw new Error(error.message);
|
|
97
|
+
return preCachedResponse;
|
|
98
|
+
}
|
|
99
|
+
const response = new Response(JSON.stringify({ status: 'error', message: 'offline test response' }));
|
|
100
|
+
// response.status = 200;
|
|
101
|
+
response.headers.set('Content-Type', 'application/json');
|
|
102
|
+
return response;
|
|
103
|
+
}
|
|
158
104
|
if (event.request.method.toUpperCase() === 'GET') {
|
|
159
|
-
const
|
|
160
|
-
const preCachedResponse = await cache.match(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
console.log('Serving offline HTML page.');
|
|
105
|
+
const cache = await caches.open(CACHE_NAME);
|
|
106
|
+
const preCachedResponse = await cache.match(
|
|
107
|
+
`${PROXY_PATH === '/' ? '' : PROXY_PATH}/maintenance/index.html`,
|
|
108
|
+
);
|
|
109
|
+
if (!preCachedResponse) throw new Error(error.message);
|
|
165
110
|
return preCachedResponse;
|
|
166
111
|
}
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
112
|
+
const response = new Response(JSON.stringify({ status: 'error', message: 'server in maintenance' }));
|
|
113
|
+
// response.status = 200;
|
|
114
|
+
response.headers.set('Content-Type', 'application/json');
|
|
115
|
+
return response;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('Error opening cache for offline page', event.request.url, error);
|
|
118
|
+
const response = new Response(JSON.stringify({ status: 'error', message: error.message }));
|
|
119
|
+
// response.status = 200;
|
|
171
120
|
response.headers.set('Content-Type', 'application/json');
|
|
172
121
|
return response;
|
|
173
122
|
}
|
|
174
|
-
|
|
175
|
-
// C. MAINTENANCE FALLBACK (Online, but network failed - interpreted as maintenance)
|
|
176
|
-
if (event.request.method.toUpperCase() === 'GET') {
|
|
177
|
-
const maintenancePath = `${this.PROXY_PATH === '/' ? '' : this.PROXY_PATH}/maintenance/index.html`;
|
|
178
|
-
const preCachedResponse = await cache.match(maintenancePath);
|
|
179
|
-
|
|
180
|
-
if (!preCachedResponse) throw new Error(`Maintenance page not found in cache: ${maintenancePath}`);
|
|
181
|
-
|
|
182
|
-
console.log('Serving maintenance HTML page.');
|
|
183
|
-
return preCachedResponse;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// D. MAINTENANCE API FALLBACK (Non-GET requests)
|
|
187
|
-
console.log('Serving maintenance JSON response for non-GET request.');
|
|
188
|
-
const response = new Response(JSON.stringify({ status: 'error', message: 'server in maintenance' }));
|
|
189
|
-
response.headers.set('Content-Type', 'application/json');
|
|
190
|
-
return response;
|
|
191
|
-
} catch (finalError) {
|
|
192
|
-
// 5. Final fail-safe response
|
|
193
|
-
console.error('Final fail-safe execution failed.', event.request.url, finalError);
|
|
194
|
-
const response = new Response(JSON.stringify({ status: 'error', message: finalError.message }));
|
|
195
|
-
response.headers.set('Content-Type', 'application/json');
|
|
196
|
-
return response;
|
|
197
123
|
}
|
|
198
124
|
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Instantiate and run the service worker class
|
|
204
|
-
new PwaServiceWorker().run();
|
|
125
|
+
})(),
|
|
126
|
+
);
|
|
127
|
+
});
|