underpost 2.85.1 → 2.89.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/.env.development +2 -1
- package/.env.production +2 -1
- package/.env.test +2 -1
- package/.github/workflows/release.cd.yml +3 -3
- package/.vscode/zed.keymap.json +22 -0
- package/README.md +3 -3
- package/bin/build.js +8 -10
- package/bin/deploy.js +4 -2
- package/bin/file.js +4 -0
- package/bin/vs.js +4 -4
- package/cli.md +16 -11
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +50 -50
- package/manifests/deployment/dd-test-development/proxy.yaml +4 -4
- package/package.json +2 -2
- package/src/api/file/file.service.js +29 -3
- package/src/cli/baremetal.js +4 -5
- package/src/cli/deploy.js +26 -4
- package/src/cli/index.js +8 -3
- package/src/cli/repository.js +42 -45
- package/src/cli/run.js +217 -48
- package/src/client/components/core/AgGrid.js +42 -3
- package/src/client/components/core/CommonJs.js +5 -0
- package/src/client/components/core/Css.js +95 -48
- package/src/client/components/core/CssCore.js +0 -1
- package/src/client/components/core/LoadingAnimation.js +2 -2
- package/src/client/components/core/Logger.js +2 -9
- package/src/client/components/core/Modal.js +22 -14
- package/src/client/components/core/ObjectLayerEngine.js +300 -9
- package/src/client/components/core/ObjectLayerEngineModal.js +686 -148
- package/src/client/components/core/ObjectLayerEngineViewer.js +1061 -0
- package/src/client/components/core/Pagination.js +15 -5
- package/src/client/components/core/Router.js +5 -1
- package/src/client/components/core/SocketIo.js +5 -1
- package/src/client/components/core/Translate.js +4 -0
- package/src/client/components/core/Worker.js +8 -1
- package/src/client/services/default/default.management.js +86 -16
- package/src/client/sw/default.sw.js +193 -97
- package/src/client.dev.js +1 -1
- package/src/db/mariadb/MariaDB.js +2 -2
- package/src/index.js +1 -1
- package/src/proxy.js +1 -1
- package/src/runtime/express/Express.js +4 -1
- package/src/server/auth.js +2 -1
- package/src/server/client-build.js +57 -2
- package/src/server/conf.js +132 -15
- package/src/server/object-layer.js +44 -0
- package/src/server/proxy.js +53 -26
- package/src/server/start.js +25 -3
- package/src/server/tls.js +1 -1
- package/src/ws/IoInterface.js +2 -3
- package/AUTHORS.md +0 -21
- package/src/server/network.js +0 -72
|
@@ -10,12 +10,13 @@ class AgPagination extends HTMLElement {
|
|
|
10
10
|
this._limit = parseInt(queryParams.limit, 10) || 10;
|
|
11
11
|
this._totalPages = 1;
|
|
12
12
|
this._totalItems = 0;
|
|
13
|
+
this._limitOptions = [10, 20, 50, 100]; // Default options
|
|
13
14
|
this.handlePageChange = this.handlePageChange.bind(this);
|
|
14
15
|
this.handleLimitChange = this.handleLimitChange.bind(this);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
static get observedAttributes() {
|
|
18
|
-
return ['grid-id', 'current-page', 'total-pages', 'total-items', 'limit'];
|
|
19
|
+
return ['grid-id', 'current-page', 'total-pages', 'total-items', 'limit', 'limit-options'];
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -35,6 +36,16 @@ class AgPagination extends HTMLElement {
|
|
|
35
36
|
case 'limit':
|
|
36
37
|
this._limit = parseInt(newValue, 10) || this._limit;
|
|
37
38
|
break;
|
|
39
|
+
case 'limit-options':
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(newValue);
|
|
42
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
43
|
+
this._limitOptions = parsed.map((v) => parseInt(v, 10)).filter((v) => !isNaN(v) && v > 0);
|
|
44
|
+
}
|
|
45
|
+
} catch (e) {
|
|
46
|
+
console.warn('Invalid limit-options format, using defaults');
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
38
49
|
}
|
|
39
50
|
this.update();
|
|
40
51
|
}
|
|
@@ -133,6 +144,8 @@ class AgPagination extends HTMLElement {
|
|
|
133
144
|
}
|
|
134
145
|
|
|
135
146
|
render() {
|
|
147
|
+
const limitOptionsHtml = this._limitOptions.map((value) => `<option value="${value}">${value}</option>`).join('');
|
|
148
|
+
|
|
136
149
|
this.shadowRoot.innerHTML = html`
|
|
137
150
|
<style>
|
|
138
151
|
:host {
|
|
@@ -193,10 +206,7 @@ class AgPagination extends HTMLElement {
|
|
|
193
206
|
<button id="next-page">Next</button>
|
|
194
207
|
<button id="last-page">Last</button>
|
|
195
208
|
<select id="limit-selector">
|
|
196
|
-
|
|
197
|
-
<option value="20">20</option>
|
|
198
|
-
<option value="50">50</option>
|
|
199
|
-
<option value="100">100</option>
|
|
209
|
+
${limitOptionsHtml}
|
|
200
210
|
</select>
|
|
201
211
|
`;
|
|
202
212
|
}
|
|
@@ -300,7 +300,11 @@ const setQueryParams = (newParams, options = { replace: true }) => {
|
|
|
300
300
|
|
|
301
301
|
const newPath = url.pathname + url.search + url.hash;
|
|
302
302
|
|
|
303
|
-
|
|
303
|
+
if (options.replace) {
|
|
304
|
+
history.replaceState(history.state, '', newPath);
|
|
305
|
+
} else {
|
|
306
|
+
history.pushState(history.state, '', newPath);
|
|
307
|
+
}
|
|
304
308
|
};
|
|
305
309
|
|
|
306
310
|
export {
|
|
@@ -35,8 +35,12 @@ const SocketIo = {
|
|
|
35
35
|
// forceNew: true,
|
|
36
36
|
// reconnectionAttempts: 'Infinity',
|
|
37
37
|
// timeout: 10000,
|
|
38
|
-
// withCredentials: true,
|
|
39
38
|
// autoConnect: 5000,
|
|
39
|
+
// Custom auth socket io credentials:
|
|
40
|
+
withCredentials: true,
|
|
41
|
+
extraHeaders: {
|
|
42
|
+
// "my-custom-header": "abcd"
|
|
43
|
+
},
|
|
40
44
|
transports: ['websocket', 'polling', 'flashsocket'],
|
|
41
45
|
};
|
|
42
46
|
// logger.error(`connect options:`, JSON.stringify(connectOptions, null, 4));
|
|
@@ -53,15 +53,22 @@ class PwaWorker {
|
|
|
53
53
|
*/
|
|
54
54
|
constructor() {
|
|
55
55
|
this.title = `${s('title').textContent}`;
|
|
56
|
+
if (!window.renderPayload.dev) {
|
|
57
|
+
console.log = () => null;
|
|
58
|
+
console.error = () => null;
|
|
59
|
+
console.info = () => null;
|
|
60
|
+
console.warn = () => null;
|
|
61
|
+
}
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
/**
|
|
59
65
|
* Checks if the application is running in development mode (localhost or 127.0.0.1).
|
|
66
|
+
* @method devMode
|
|
60
67
|
* @memberof PwaWorker
|
|
61
68
|
* @returns {boolean} True if in development mode.
|
|
62
69
|
*/
|
|
63
70
|
devMode() {
|
|
64
|
-
return location.origin.match('localhost') || location.origin.match('127.0.0.1');
|
|
71
|
+
return window.renderPayload.dev || location.origin.match('localhost') || location.origin.match('127.0.0.1');
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
/**
|
|
@@ -18,7 +18,7 @@ const DefaultOptions = {
|
|
|
18
18
|
serviceId: 'default-management',
|
|
19
19
|
entity: 'default',
|
|
20
20
|
columnDefs: [
|
|
21
|
-
{ field: '0', headerName: '0' },
|
|
21
|
+
{ field: '0', headerName: '0', cellClassRules: { 'row-new-highlight': (params) => true } },
|
|
22
22
|
{ field: '1', headerName: '1' },
|
|
23
23
|
{ field: '2', headerName: '2' },
|
|
24
24
|
{ field: 'createdAt', headerName: 'createdAt', cellDataType: 'date', editable: false },
|
|
@@ -29,6 +29,10 @@ const DefaultOptions = {
|
|
|
29
29
|
permissions: {
|
|
30
30
|
add: true,
|
|
31
31
|
remove: true,
|
|
32
|
+
reload: true,
|
|
33
|
+
},
|
|
34
|
+
paginationOptions: {
|
|
35
|
+
limitOptions: [10, 20, 50, 100],
|
|
32
36
|
},
|
|
33
37
|
};
|
|
34
38
|
|
|
@@ -66,25 +70,36 @@ const DefaultManagement = {
|
|
|
66
70
|
paginationComp.setAttribute('current-page', this.Tokens[id].page);
|
|
67
71
|
paginationComp.setAttribute('total-pages', this.Tokens[id].totalPages);
|
|
68
72
|
paginationComp.setAttribute('total-items', this.Tokens[id].total);
|
|
73
|
+
setTimeout(async () => {
|
|
74
|
+
if (DefaultManagement.Tokens[id].readyRowDataEvent)
|
|
75
|
+
for (const event of Object.keys(DefaultManagement.Tokens[id].readyRowDataEvent))
|
|
76
|
+
await DefaultManagement.Tokens[id].readyRowDataEvent[event](rowDataScope);
|
|
77
|
+
}, 1);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
refreshTable: async function (id) {
|
|
81
|
+
const gridApi = AgGrid.grids[this.Tokens[id].gridId];
|
|
82
|
+
if (gridApi) {
|
|
83
|
+
// Use refreshCells with change detection for optimal performance
|
|
84
|
+
// This is preferred over redrawRows() as it only updates changed cells
|
|
85
|
+
gridApi.refreshCells({
|
|
86
|
+
force: false, // Use change detection - only refresh cells whose values have changed
|
|
87
|
+
suppressFlash: false, // Show flash animation for changed cells (requires enableCellChangeFlash)
|
|
88
|
+
});
|
|
69
89
|
}
|
|
70
90
|
},
|
|
71
91
|
RenderTable: async function (options = DefaultOptions) {
|
|
72
92
|
if (!options) options = DefaultOptions;
|
|
73
|
-
const { serviceId, columnDefs, entity, defaultColKeyFocus, ServiceProvider, permissions } =
|
|
93
|
+
const { serviceId, columnDefs, entity, defaultColKeyFocus, ServiceProvider, permissions, paginationOptions } =
|
|
94
|
+
options;
|
|
74
95
|
logger.info('DefaultManagement RenderTable', options);
|
|
75
96
|
const id = options?.idModal ? options.idModal : getId(this.Tokens, `${serviceId}-`);
|
|
76
97
|
const gridId = `${serviceId}-grid-${id}`;
|
|
77
98
|
const queryParams = getQueryParams();
|
|
78
99
|
const page = parseInt(queryParams.page) || 1;
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
gridId,
|
|
83
|
-
page,
|
|
84
|
-
limit,
|
|
85
|
-
total: 0,
|
|
86
|
-
totalPages: 1,
|
|
87
|
-
};
|
|
100
|
+
const defaultLimit = paginationOptions?.limitOptions?.[0] || 10;
|
|
101
|
+
const limit = parseInt(queryParams.limit) || defaultLimit;
|
|
102
|
+
this.Tokens[id] = { ...this.Tokens[id], ...options, gridId, page, limit, total: 0, totalPages: 1 };
|
|
88
103
|
|
|
89
104
|
setQueryParams({ page, limit });
|
|
90
105
|
setTimeout(async () => {
|
|
@@ -107,7 +122,7 @@ const DefaultManagement = {
|
|
|
107
122
|
label: html`<div class="abs center">
|
|
108
123
|
<i class="fas fa-times"></i>
|
|
109
124
|
</div> `,
|
|
110
|
-
class: `in fll section-mp management-table-btn-mini management-table-btn-remove-${id}-${cellRenderId}`,
|
|
125
|
+
class: `in fll section-mp management-table-btn-mini management-table-btn-remove-${id}-${cellRenderId} ${!params.data._id ? 'hide' : ''}`,
|
|
111
126
|
})}`;
|
|
112
127
|
setTimeout(() => {
|
|
113
128
|
EventsUI.onClick(
|
|
@@ -191,9 +206,16 @@ const DefaultManagement = {
|
|
|
191
206
|
// }
|
|
192
207
|
// }
|
|
193
208
|
s(`.management-table-btn-save-${id}`).onclick = () => {
|
|
209
|
+
s(`.management-table-btn-save-${id}`).classList.add('hide');
|
|
210
|
+
// s(`.management-table-btn-stop-${id}`).classList.add('hide');
|
|
211
|
+
if (permissions.add) s(`.management-table-btn-add-${id}`).classList.remove('hide');
|
|
212
|
+
if (permissions.remove) s(`.management-table-btn-clean-${id}`).classList.remove('hide');
|
|
213
|
+
if (permissions.reload) s(`.management-table-btn-reload-${id}`).classList.remove('hide');
|
|
194
214
|
AgGrid.grids[gridId].stopEditing();
|
|
195
215
|
};
|
|
196
216
|
EventsUI.onClick(`.management-table-btn-add-${id}`, async () => {
|
|
217
|
+
if (options.customEvent && options.customEvent.add) return await options.customEvent.add();
|
|
218
|
+
|
|
197
219
|
const rowObj = {};
|
|
198
220
|
for (const def of columnDefs) {
|
|
199
221
|
rowObj[def.field] = '';
|
|
@@ -260,6 +282,11 @@ const DefaultManagement = {
|
|
|
260
282
|
// }
|
|
261
283
|
|
|
262
284
|
setTimeout(() => {
|
|
285
|
+
s(`.management-table-btn-save-${id}`).classList.remove('hide');
|
|
286
|
+
// s(`.management-table-btn-stop-${id}`).classList.remove('hide');
|
|
287
|
+
if (permissions.add) s(`.management-table-btn-add-${id}`).classList.add('hide');
|
|
288
|
+
if (permissions.remove) s(`.management-table-btn-clean-${id}`).classList.add('hide');
|
|
289
|
+
if (permissions.reload) s(`.management-table-btn-reload-${id}`).classList.add('hide');
|
|
263
290
|
AgGrid.grids[gridId].startEditingCell({
|
|
264
291
|
rowIndex: 0,
|
|
265
292
|
colKey: defaultColKeyFocus,
|
|
@@ -268,6 +295,15 @@ const DefaultManagement = {
|
|
|
268
295
|
});
|
|
269
296
|
});
|
|
270
297
|
});
|
|
298
|
+
|
|
299
|
+
EventsUI.onClick(`.management-table-btn-stop-${id}`, async () => {
|
|
300
|
+
s(`.management-table-btn-save-${id}`).classList.add('hide');
|
|
301
|
+
// s(`.management-table-btn-stop-${id}`).classList.add('hide');
|
|
302
|
+
if (permissions.add) s(`.management-table-btn-add-${id}`).classList.remove('hide');
|
|
303
|
+
if (permissions.remove) s(`.management-table-btn-clean-${id}`).classList.remove('hide');
|
|
304
|
+
if (permissions.reload) s(`.management-table-btn-reload-${id}`).classList.remove('hide');
|
|
305
|
+
AgGrid.grids[gridId].stopEditing();
|
|
306
|
+
});
|
|
271
307
|
EventsUI.onClick(`.management-table-btn-clean-${id}`, async () => {
|
|
272
308
|
const confirmResult = await Modal.RenderConfirm(
|
|
273
309
|
{
|
|
@@ -292,6 +328,26 @@ const DefaultManagement = {
|
|
|
292
328
|
DefaultManagement.loadTable(id);
|
|
293
329
|
}
|
|
294
330
|
});
|
|
331
|
+
EventsUI.onClick(`.management-table-btn-reload-${id}`, async () => {
|
|
332
|
+
try {
|
|
333
|
+
// Reload data from server
|
|
334
|
+
await DefaultManagement.loadTable(id);
|
|
335
|
+
|
|
336
|
+
// Other option: Refresh cells to update UI
|
|
337
|
+
// DefaultManagement.refreshTable(id);
|
|
338
|
+
|
|
339
|
+
NotificationManager.Push({
|
|
340
|
+
html: Translate.Render('success-reload-data') || 'Data reloaded successfully',
|
|
341
|
+
status: 'success',
|
|
342
|
+
});
|
|
343
|
+
} catch (error) {
|
|
344
|
+
NotificationManager.Push({
|
|
345
|
+
html: error.message || 'Error reloading data',
|
|
346
|
+
status: 'error',
|
|
347
|
+
});
|
|
348
|
+
} finally {
|
|
349
|
+
}
|
|
350
|
+
});
|
|
295
351
|
s(`#ag-pagination-${gridId}`).addEventListener('page-change', async (event) => {
|
|
296
352
|
const token = DefaultManagement.Tokens[id];
|
|
297
353
|
token.page = event.detail.page;
|
|
@@ -318,7 +374,7 @@ const DefaultManagement = {
|
|
|
318
374
|
}
|
|
319
375
|
};
|
|
320
376
|
}, 1);
|
|
321
|
-
return html`<div class="fl">
|
|
377
|
+
return html`<div class="fl management-table-toolbar">
|
|
322
378
|
${await BtnIcon.Render({
|
|
323
379
|
class: `in fll section-mp management-table-btn-mini management-table-btn-add-${id} ${
|
|
324
380
|
permissions.add ? '' : 'hide'
|
|
@@ -327,12 +383,15 @@ const DefaultManagement = {
|
|
|
327
383
|
type: 'button',
|
|
328
384
|
})}
|
|
329
385
|
${await BtnIcon.Render({
|
|
330
|
-
class: `in fll section-mp management-table-btn-mini management-table-btn-save-${id}
|
|
331
|
-
permissions.add ? '' : 'hide'
|
|
332
|
-
}`,
|
|
386
|
+
class: `in fll section-mp management-table-btn-mini management-table-btn-save-${id} hide`,
|
|
333
387
|
label: html`<div class="abs center btn-save-${id}-label"><i class="fas fa-save"></i></div> `,
|
|
334
388
|
type: 'button',
|
|
335
389
|
})}
|
|
390
|
+
${await BtnIcon.Render({
|
|
391
|
+
class: `in fll section-mp management-table-btn-mini management-table-btn-stop-${id} hide`,
|
|
392
|
+
label: html`<div class="abs center btn-save-${id}-label"><i class="fa-solid fa-rectangle-xmark"></i></div> `,
|
|
393
|
+
type: 'button',
|
|
394
|
+
})}
|
|
336
395
|
${await BtnIcon.Render({
|
|
337
396
|
class: `in fll section-mp management-table-btn-mini management-table-btn-clean-${id} ${
|
|
338
397
|
permissions.remove ? '' : 'hide'
|
|
@@ -340,12 +399,21 @@ const DefaultManagement = {
|
|
|
340
399
|
label: html`<div class="abs center btn-clean-${id}-label"><i class="fas fa-broom"></i></div> `,
|
|
341
400
|
type: 'button',
|
|
342
401
|
})}
|
|
402
|
+
${await BtnIcon.Render({
|
|
403
|
+
class: `in fll section-mp management-table-btn-mini management-table-btn-reload-${id} ${
|
|
404
|
+
permissions.reload ? '' : 'hide'
|
|
405
|
+
}`,
|
|
406
|
+
label: html`<div class="abs center btn-reload-${id}-label"><i class="fas fa-sync-alt"></i></div> `,
|
|
407
|
+
type: 'button',
|
|
408
|
+
})}
|
|
343
409
|
</div>
|
|
344
410
|
<div class="in section-mp">
|
|
345
411
|
${await AgGrid.Render({
|
|
346
412
|
id: gridId,
|
|
347
413
|
parentModal: options.idModal,
|
|
348
414
|
usePagination: true,
|
|
415
|
+
paginationOptions,
|
|
416
|
+
customHeightOffset: !permissions.add && !permissions.remove && !permissions.reload ? 50 : 0,
|
|
349
417
|
darkTheme,
|
|
350
418
|
gridOptions: {
|
|
351
419
|
defaultColDef: {
|
|
@@ -425,6 +493,7 @@ const DefaultManagement = {
|
|
|
425
493
|
// rowNode.setData(newRow);
|
|
426
494
|
// }, 2000);
|
|
427
495
|
}
|
|
496
|
+
s(`.management-table-btn-save-${id}`).click();
|
|
428
497
|
}
|
|
429
498
|
} else {
|
|
430
499
|
const body = event.data ? event.data : {};
|
|
@@ -441,6 +510,7 @@ const DefaultManagement = {
|
|
|
441
510
|
}
|
|
442
511
|
}
|
|
443
512
|
},
|
|
513
|
+
...(options.gridOptions ? options.gridOptions : undefined),
|
|
444
514
|
},
|
|
445
515
|
})}
|
|
446
516
|
</div>`;
|
|
@@ -1,108 +1,204 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
(async () => {
|
|
32
|
-
// Enable navigation preload if it's supported.
|
|
33
|
-
// See https://developers.google.com/web/updates/2017/02/navigation-preload
|
|
34
|
-
if ('navigationPreload' in self.registration) {
|
|
35
|
-
await self.registration.navigationPreload.enable();
|
|
36
|
-
}
|
|
37
|
-
})(),
|
|
38
|
-
);
|
|
39
|
-
// Tell the active service worker to take control of the page immediately.
|
|
40
|
-
self.clients.claim();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
self.addEventListener('fetch', (event) => {
|
|
44
|
-
// Cache-First Strategy
|
|
45
|
-
event.respondWith(
|
|
46
|
-
(async () => {
|
|
47
|
-
// First, try to use the navigation preload response if it's supported.
|
|
48
|
-
try {
|
|
49
|
-
const preloadResponse = await event.preloadResponse;
|
|
50
|
-
if (preloadResponse) return preloadResponse;
|
|
51
|
-
return await fetch(event.request);
|
|
52
|
-
} catch (error) {
|
|
53
|
-
console.error('Fetch failed; returning offline page instead.', event.request.url, error);
|
|
54
|
-
// Fallback to the offline page.
|
|
55
|
-
const path = PRE_CACHED_RESOURCES.find((path) => event.request.url.match(path.replaceAll('/index.html', '')));
|
|
1
|
+
/**
|
|
2
|
+
* This module provides a configurable Progressive Web App (PWA) service worker and caching strategies.
|
|
3
|
+
* It supports precaching assets, runtime caching with stale-while-revalidate strategy,
|
|
4
|
+
* and offline fallback handling.
|
|
5
|
+
* @module src/client/sw/default.sw.js
|
|
6
|
+
* @namespace PwaServiceWorker
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Class representing a Progressive Web App (PWA) Service Worker with caching strategies.
|
|
11
|
+
* @class
|
|
12
|
+
* @memberof PwaServiceWorker
|
|
13
|
+
*/
|
|
14
|
+
class PwaServiceWorker {
|
|
15
|
+
/**
|
|
16
|
+
* Initializes the service worker configuration by reading from self.renderPayload.
|
|
17
|
+
* If properties are not found, defaults are used.
|
|
18
|
+
* @constructor
|
|
19
|
+
* @property {Array<string>} PRE_CACHED_RESOURCES - List of resources to precache.
|
|
20
|
+
* @property {string} CACHE_NAME - Name of the cache storage.
|
|
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
|
+
}
|
|
56
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.
|
|
57
58
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const cache = await caches.open(CACHE_NAME);
|
|
61
|
-
const preCachedResponse = await cache.match(path);
|
|
62
|
-
if (!preCachedResponse) throw new Error(error.message);
|
|
63
|
-
return preCachedResponse;
|
|
59
|
+
console.log(`Precaching ${this.PRE_CACHED_RESOURCES.length} resources...`);
|
|
60
|
+
await cache.addAll(this.PRE_CACHED_RESOURCES);
|
|
64
61
|
} catch (error) {
|
|
65
|
-
console.error('Error
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
124
|
+
try {
|
|
125
|
+
const cachedResponse = await caches.match(event.request);
|
|
126
|
+
if (cachedResponse) {
|
|
127
|
+
console.log(`Cache hit for: ${event.request.url}`);
|
|
128
|
+
return cachedResponse;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 3. Try to match a precached resource path (e.g., if requesting /page, match /page/index.html)
|
|
132
|
+
const path = this.PRE_CACHED_RESOURCES.find((p) => event.request.url.match(p.replaceAll('/index.html', '')));
|
|
133
|
+
|
|
134
|
+
if (path) {
|
|
135
|
+
const cache = await caches.open(this.CACHE_NAME);
|
|
136
|
+
const preCachedResponse = await cache.match(path);
|
|
137
|
+
if (preCachedResponse) {
|
|
138
|
+
console.log(`Matched precached resource for: ${event.request.url} via path: ${path}`);
|
|
139
|
+
return preCachedResponse;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// If neither cache match nor precache path match worked, fall through to complex fallback
|
|
144
|
+
throw new Error('Cache miss and no precache match.');
|
|
145
|
+
} catch (cacheError) {
|
|
146
|
+
console.error('Error in primary cache lookup. Falling back to offline/maintenance pages.', {
|
|
147
|
+
url: event.request.url,
|
|
148
|
+
cacheError,
|
|
149
|
+
onLine: navigator.onLine,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// 4. Complex Fallback Logic (Offline or Maintenance)
|
|
153
|
+
try {
|
|
154
|
+
const cache = await caches.open(this.CACHE_NAME);
|
|
155
|
+
|
|
156
|
+
if (!navigator.onLine) {
|
|
157
|
+
// A. OFFLINE FALLBACK
|
|
85
158
|
if (event.request.method.toUpperCase() === 'GET') {
|
|
86
|
-
const
|
|
87
|
-
const preCachedResponse = await cache.match(
|
|
88
|
-
|
|
89
|
-
);
|
|
90
|
-
|
|
159
|
+
const offlinePath = `${this.PROXY_PATH === '/' ? '' : this.PROXY_PATH}/offline/index.html`;
|
|
160
|
+
const preCachedResponse = await cache.match(offlinePath);
|
|
161
|
+
|
|
162
|
+
if (!preCachedResponse) throw new Error(`Offline page not found in cache: ${offlinePath}`);
|
|
163
|
+
|
|
164
|
+
console.log('Serving offline HTML page.');
|
|
91
165
|
return preCachedResponse;
|
|
92
166
|
}
|
|
93
|
-
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
} catch (error) {
|
|
98
|
-
console.error('Error opening cache for offline page', event.request.url, error);
|
|
99
|
-
const response = new Response(JSON.stringify({ status: 'error', message: error.message }));
|
|
100
|
-
// response.status = 200;
|
|
167
|
+
|
|
168
|
+
// B. OFFLINE API FALLBACK (Non-GET requests)
|
|
169
|
+
console.log('Serving offline JSON response for non-GET request.');
|
|
170
|
+
const response = new Response(JSON.stringify({ status: 'error', message: 'offline test response' }));
|
|
101
171
|
response.headers.set('Content-Type', 'application/json');
|
|
102
172
|
return response;
|
|
103
173
|
}
|
|
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;
|
|
104
197
|
}
|
|
105
198
|
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Instantiate and run the service worker class
|
|
204
|
+
new PwaServiceWorker().run();
|
package/src/client.dev.js
CHANGED
|
@@ -42,9 +42,9 @@ class MariaDBService {
|
|
|
42
42
|
try {
|
|
43
43
|
conn = await pool.getConnection();
|
|
44
44
|
result = await conn.query(query, { supportBigNumbers: true, bigNumberStrings: true });
|
|
45
|
-
logger.info(query
|
|
45
|
+
logger.info('query');
|
|
46
|
+
console.log(result);
|
|
46
47
|
} catch (error) {
|
|
47
|
-
if (error.stack.startsWith('TypeError: Do not know how to serialize a BigInt')) return;
|
|
48
48
|
logger.error(error, error.stack);
|
|
49
49
|
} finally {
|
|
50
50
|
if (conn) conn.release(); // release to pool
|