underpost 2.85.1 → 2.85.7
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/deploy.js +4 -2
- package/bin/file.js +4 -0
- package/bin/vs.js +4 -4
- package/cli.md +15 -11
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +2 -2
- package/src/cli/baremetal.js +3 -3
- package/src/cli/deploy.js +26 -4
- package/src/cli/index.js +7 -3
- package/src/cli/repository.js +34 -44
- package/src/cli/run.js +121 -13
- package/src/client/components/core/CommonJs.js +1 -0
- package/src/client/components/core/SocketIo.js +5 -1
- package/src/client/sw/default.sw.js +193 -97
- package/src/client.dev.js +1 -1
- 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/conf.js +132 -15
- package/src/server/proxy.js +53 -26
- package/src/server/start.js +13 -3
- package/src/server/tls.js +1 -1
package/src/cli/run.js
CHANGED
|
@@ -5,7 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { daemonProcess, getTerminalPid, openTerminal, pbcopy, shellCd, shellExec } from '../server/process.js';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
awaitDeployMonitor,
|
|
10
|
+
Config,
|
|
11
|
+
getNpmRootPath,
|
|
12
|
+
getUnderpostRootPath,
|
|
13
|
+
isDeployRunnerContext,
|
|
14
|
+
writeEnv,
|
|
15
|
+
} from '../server/conf.js';
|
|
9
16
|
import { actionInitLog, loggerFactory } from '../server/logger.js';
|
|
10
17
|
import UnderpostTest from './test.js';
|
|
11
18
|
import fs from 'fs-extra';
|
|
@@ -15,6 +22,7 @@ import UnderpostRootEnv from './env.js';
|
|
|
15
22
|
import UnderpostRepository from './repository.js';
|
|
16
23
|
import os from 'os';
|
|
17
24
|
import Underpost from '../index.js';
|
|
25
|
+
import dotenv from 'dotenv';
|
|
18
26
|
|
|
19
27
|
const logger = loggerFactory(import.meta);
|
|
20
28
|
|
|
@@ -44,6 +52,12 @@ class UnderpostRun {
|
|
|
44
52
|
* @property {boolean} k3s - Whether to run in k3s mode.
|
|
45
53
|
* @property {boolean} kubeadm - Whether to run in kubeadm mode.
|
|
46
54
|
* @property {boolean} force - Whether to force the operation.
|
|
55
|
+
* @property {boolean} reset - Whether to reset the operation.
|
|
56
|
+
* @property {boolean} tls - Whether to use TLS.
|
|
57
|
+
* @property {string} tty - The TTY option for the container.
|
|
58
|
+
* @property {string} stdin - The stdin option for the container.
|
|
59
|
+
* @property {string} restartPolicy - The restart policy for the container.
|
|
60
|
+
* @property {boolean} terminal - Whether to open a terminal.
|
|
47
61
|
* @memberof UnderpostRun
|
|
48
62
|
*/
|
|
49
63
|
static DEFAULT_OPTION = {
|
|
@@ -59,6 +73,13 @@ class UnderpostRun {
|
|
|
59
73
|
k3s: false,
|
|
60
74
|
kubeadm: false,
|
|
61
75
|
force: false,
|
|
76
|
+
reset: false,
|
|
77
|
+
tls: false,
|
|
78
|
+
tty: '',
|
|
79
|
+
stdin: '',
|
|
80
|
+
restartPolicy: '',
|
|
81
|
+
terminal: false,
|
|
82
|
+
devProxyPortOffset: 0,
|
|
62
83
|
};
|
|
63
84
|
/**
|
|
64
85
|
* @static
|
|
@@ -106,13 +127,21 @@ class UnderpostRun {
|
|
|
106
127
|
},
|
|
107
128
|
/**
|
|
108
129
|
* @method kill
|
|
109
|
-
* @description Kills
|
|
130
|
+
* @description Kills processes listening on the specified port(s). If the `path` contains a `+`, it treats it as a range of ports to kill.
|
|
110
131
|
* @param {string} path - The input value, identifier, or path for the operation (used as the port number).
|
|
111
132
|
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
112
133
|
* @memberof UnderpostRun
|
|
113
134
|
*/
|
|
114
|
-
kill: (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
115
|
-
|
|
135
|
+
kill: (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
136
|
+
for (const _path of path.split(',')) {
|
|
137
|
+
if (_path.split('+')[1]) {
|
|
138
|
+
let [port, sumPortOffSet] = _path.split('+');
|
|
139
|
+
port = parseInt(port);
|
|
140
|
+
sumPortOffSet = parseInt(sumPortOffSet);
|
|
141
|
+
for (const sumPort of range(0, sumPortOffSet))
|
|
142
|
+
shellExec(`sudo kill -9 $(lsof -t -i:${parseInt(port) + parseInt(sumPort)})`);
|
|
143
|
+
} else shellExec(`sudo kill -9 $(lsof -t -i:${_path})`);
|
|
144
|
+
}
|
|
116
145
|
},
|
|
117
146
|
/**
|
|
118
147
|
* @method secret
|
|
@@ -330,7 +359,9 @@ class UnderpostRun {
|
|
|
330
359
|
if (!fs.existsSync(`/home/dd/engine/engine-private`))
|
|
331
360
|
shellExec(`cd /home/dd/engine && underpost clone ${process.env.GITHUB_USERNAME}/engine-private`);
|
|
332
361
|
else
|
|
333
|
-
shellExec(
|
|
362
|
+
shellExec(
|
|
363
|
+
`cd /home/dd/engine/engine-private && underpost pull . ${process.env.GITHUB_USERNAME}/engine-private`,
|
|
364
|
+
);
|
|
334
365
|
},
|
|
335
366
|
/**
|
|
336
367
|
* @method release-deploy
|
|
@@ -440,6 +471,16 @@ class UnderpostRun {
|
|
|
440
471
|
'ls-deployments': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
441
472
|
console.table(await UnderpostDeploy.API.get(path, 'deployments'));
|
|
442
473
|
},
|
|
474
|
+
/**
|
|
475
|
+
* @method ls-images
|
|
476
|
+
* @description Retrieves and logs a table of currently loaded Docker images in the 'kind-worker' node using `UnderpostDeploy.API.getCurrentLoadedImages`.
|
|
477
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
478
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
479
|
+
* @memberof UnderpostRun
|
|
480
|
+
*/
|
|
481
|
+
'ls-images': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
482
|
+
console.table(UnderpostDeploy.API.getCurrentLoadedImages('kind-worker', false));
|
|
483
|
+
},
|
|
443
484
|
|
|
444
485
|
/**
|
|
445
486
|
* @method host-update
|
|
@@ -608,10 +649,16 @@ class UnderpostRun {
|
|
|
608
649
|
* @memberof UnderpostRun
|
|
609
650
|
*/
|
|
610
651
|
'git-conf': (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
611
|
-
const defaultUsername = UnderpostRootEnv.API.get('GITHUB_USERNAME'
|
|
612
|
-
const defaultEmail = UnderpostRootEnv.API.get('GITHUB_EMAIL'
|
|
613
|
-
const
|
|
614
|
-
|
|
652
|
+
const defaultUsername = UnderpostRootEnv.API.get('GITHUB_USERNAME');
|
|
653
|
+
const defaultEmail = UnderpostRootEnv.API.get('GITHUB_EMAIL');
|
|
654
|
+
const validPath = path && path.split(',').length;
|
|
655
|
+
const [username, email] = validPath ? path.split(',') : [defaultUsername, defaultEmail];
|
|
656
|
+
if (validPath) {
|
|
657
|
+
UnderpostRootEnv.API.set('GITHUB_USERNAME', username);
|
|
658
|
+
UnderpostRootEnv.API.set('GITHUB_EMAIL', email);
|
|
659
|
+
UnderpostRootEnv.API.get('GITHUB_USERNAME');
|
|
660
|
+
UnderpostRootEnv.API.get('GITHUB_EMAIL');
|
|
661
|
+
}
|
|
615
662
|
shellExec(
|
|
616
663
|
`git config --global credential.helper "" && ` +
|
|
617
664
|
`git config credential.helper "" && ` +
|
|
@@ -750,6 +797,47 @@ class UnderpostRun {
|
|
|
750
797
|
UnderpostDeploy.API.switchTraffic(deployId, env, targetTraffic);
|
|
751
798
|
},
|
|
752
799
|
|
|
800
|
+
/**
|
|
801
|
+
* @method dev
|
|
802
|
+
* @description Starts development servers for client, API, and proxy based on provided parameters (deployId, host, path, clientHostPort).
|
|
803
|
+
* @param {string} path - The input value, identifier, or path for the operation (formatted as `deployId,subConf,host,path,clientHostPort`).
|
|
804
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
805
|
+
* @memberof UnderpostRun
|
|
806
|
+
*/
|
|
807
|
+
dev: async (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
808
|
+
let [deployId, subConf, host, _path, clientHostPort] = path.split(',');
|
|
809
|
+
if (!deployId) deployId = 'dd-default';
|
|
810
|
+
if (!host) host = 'default.net';
|
|
811
|
+
if (!_path) _path = '/';
|
|
812
|
+
if (!clientHostPort) clientHostPort = 'localhost:4004';
|
|
813
|
+
if (!subConf) subConf = 'local';
|
|
814
|
+
if (options.reset && fs.existsSync(`./engine-private/conf/${deployId}`))
|
|
815
|
+
fs.removeSync(`./engine-private/conf/${deployId}`);
|
|
816
|
+
if (!fs.existsSync(`./engine-private/conf/${deployId}`)) Config.deployIdFactory(deployId, { subConf });
|
|
817
|
+
if (options.devProxyPortOffset) {
|
|
818
|
+
const envPath = `./engine-private/conf/${deployId}/.env.development`;
|
|
819
|
+
const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
820
|
+
envObj.DEV_PROXY_PORT_OFFSET = options.devProxyPortOffset;
|
|
821
|
+
writeEnv(envPath, envObj);
|
|
822
|
+
}
|
|
823
|
+
shellExec(`node bin run dev-cluster expose`);
|
|
824
|
+
{
|
|
825
|
+
const cmd = `npm run dev-api ${deployId} ${subConf} ${host} ${_path} ${clientHostPort}${options.tls ? ' tls' : ''}`;
|
|
826
|
+
options.terminal ? openTerminal(cmd) : shellExec(cmd, { async: true });
|
|
827
|
+
}
|
|
828
|
+
await awaitDeployMonitor(true);
|
|
829
|
+
{
|
|
830
|
+
const cmd = `npm run dev-client ${deployId} ${subConf} ${host} ${_path} proxy${options.tls ? ' tls' : ''}`;
|
|
831
|
+
options.terminal
|
|
832
|
+
? openTerminal(cmd)
|
|
833
|
+
: shellExec(cmd, {
|
|
834
|
+
async: true,
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
await awaitDeployMonitor(true);
|
|
838
|
+
shellExec(`npm run dev-proxy ${deployId} ${subConf} ${host} ${_path}${options.tls ? ' tls' : ''}`);
|
|
839
|
+
},
|
|
840
|
+
|
|
753
841
|
/**
|
|
754
842
|
* @method service
|
|
755
843
|
* @description Deploys and exposes specific services (like `mongo-express-service`) on the cluster, updating deployment configurations and monitoring status.
|
|
@@ -808,6 +896,21 @@ class UnderpostRun {
|
|
|
808
896
|
}
|
|
809
897
|
},
|
|
810
898
|
|
|
899
|
+
/**
|
|
900
|
+
* @method release-cmt
|
|
901
|
+
* @description Commits and pushes a new release for the `engine` repository with a message indicating the new version.
|
|
902
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
903
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
904
|
+
* @memberof UnderpostRun
|
|
905
|
+
*/
|
|
906
|
+
'release-cmt': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
907
|
+
shellExec(`underpost run pull`);
|
|
908
|
+
shellExec(`underpost run secret`);
|
|
909
|
+
shellCd(`/home/dd/engine`);
|
|
910
|
+
shellExec(`underpost cmt --empty . ci engine ' New engine release $(underpost --version)'`);
|
|
911
|
+
shellExec(`underpost push . ${process.env.GITHUB_USERNAME}/engine`);
|
|
912
|
+
},
|
|
913
|
+
|
|
811
914
|
/**
|
|
812
915
|
* @method sync-replica
|
|
813
916
|
* @description Syncs a replica for the dd.router
|
|
@@ -896,6 +999,9 @@ class UnderpostRun {
|
|
|
896
999
|
const volumeMountPath = options.volumeMountPath || path;
|
|
897
1000
|
const volumeHostPath = options.volumeHostPath || path;
|
|
898
1001
|
const enableVolumeMount = volumeHostPath && volumeMountPath;
|
|
1002
|
+
const tty = options.tty ? 'true' : 'false';
|
|
1003
|
+
const stdin = options.stdin ? 'true' : 'false';
|
|
1004
|
+
const restartPolicy = options.restartPolicy || 'Never';
|
|
899
1005
|
|
|
900
1006
|
if (options.volumeType === 'dev') options.volumeType = 'FileOrCreate';
|
|
901
1007
|
const volumeType =
|
|
@@ -909,15 +1015,17 @@ kind: Pod
|
|
|
909
1015
|
metadata:
|
|
910
1016
|
name: ${podName}
|
|
911
1017
|
namespace: ${namespace}
|
|
1018
|
+
labels:
|
|
1019
|
+
app: ${podName}
|
|
912
1020
|
spec:
|
|
913
|
-
restartPolicy:
|
|
1021
|
+
restartPolicy: ${restartPolicy}
|
|
914
1022
|
${runtimeClassName ? ` runtimeClassName: ${runtimeClassName}` : ''}
|
|
915
1023
|
containers:
|
|
916
1024
|
- name: ${containerName}
|
|
917
1025
|
image: ${imageName}
|
|
918
1026
|
imagePullPolicy: IfNotPresent
|
|
919
|
-
tty:
|
|
920
|
-
stdin:
|
|
1027
|
+
tty: ${tty}
|
|
1028
|
+
stdin: ${stdin}
|
|
921
1029
|
command: ${JSON.stringify(options.command ? options.command : ['/bin/bash', '-c'])}
|
|
922
1030
|
${
|
|
923
1031
|
args.length > 0
|
|
@@ -953,7 +1061,7 @@ ${
|
|
|
953
1061
|
: ''
|
|
954
1062
|
}
|
|
955
1063
|
EOF`;
|
|
956
|
-
shellExec(`kubectl delete pod ${podName}`);
|
|
1064
|
+
shellExec(`kubectl delete pod ${podName} --ignore-not-found`);
|
|
957
1065
|
console.log(cmd);
|
|
958
1066
|
shellExec(cmd, { disableLog: true });
|
|
959
1067
|
const successInstance = await UnderpostTest.API.statusMonitor(podName);
|
|
@@ -101,6 +101,7 @@ const cap = (str) =>
|
|
|
101
101
|
|
|
102
102
|
const capFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
103
103
|
|
|
104
|
+
// Other option: Array.from(new Set(arr))
|
|
104
105
|
const uniqueArray = (arr) => arr.filter((item, pos) => arr.indexOf(item) == pos);
|
|
105
106
|
|
|
106
107
|
const orderArrayFromAttrInt = (arr, attr, type) =>
|
|
@@ -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));
|
|
@@ -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
package/src/index.js
CHANGED
package/src/proxy.js
CHANGED
|
@@ -23,6 +23,7 @@ import { applySecurity, authMiddlewareFactory } from '../../server/auth.js';
|
|
|
23
23
|
import { ssrMiddlewareFactory } from '../../server/ssr.js';
|
|
24
24
|
import { TLS } from '../../server/tls.js';
|
|
25
25
|
import { shellExec } from '../../server/process.js';
|
|
26
|
+
import { devProxyHostFactory, isDevProxyContext, isTlsDevProxy } from '../../server/conf.js';
|
|
26
27
|
|
|
27
28
|
const logger = loggerFactory(import.meta);
|
|
28
29
|
|
|
@@ -94,7 +95,9 @@ class ExpressService {
|
|
|
94
95
|
|
|
95
96
|
const app = express();
|
|
96
97
|
|
|
97
|
-
if (
|
|
98
|
+
if (origins && isDevProxyContext())
|
|
99
|
+
origins.push(devProxyHostFactory({ host, includeHttp: true, tls: isTlsDevProxy() }));
|
|
100
|
+
app.set('trust proxy', true);
|
|
98
101
|
|
|
99
102
|
app.use((req, res, next) => {
|
|
100
103
|
res.on('finish', () => {
|
package/src/server/auth.js
CHANGED
|
@@ -17,6 +17,7 @@ import slowDown from 'express-slow-down';
|
|
|
17
17
|
import cors from 'cors';
|
|
18
18
|
import cookieParser from 'cookie-parser';
|
|
19
19
|
import { DataBaseProvider } from '../db/DataBaseProvider.js';
|
|
20
|
+
import { isDevProxyContext } from './conf.js';
|
|
20
21
|
|
|
21
22
|
dotenv.config();
|
|
22
23
|
const logger = loggerFactory(import.meta);
|
|
@@ -348,7 +349,7 @@ const cookieOptionsFactory = (req, host) => {
|
|
|
348
349
|
secure,
|
|
349
350
|
sameSite,
|
|
350
351
|
path: '/',
|
|
351
|
-
domain: process.env.NODE_ENV === 'production' ? host : 'localhost',
|
|
352
|
+
domain: process.env.NODE_ENV === 'production' || isDevProxyContext() ? host : 'localhost',
|
|
352
353
|
maxAge,
|
|
353
354
|
};
|
|
354
355
|
|