underpost 2.8.884 → 2.8.885
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 +4 -120
- package/bin/deploy.js +9 -10
- package/bin/file.js +4 -6
- 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 +1 -1
- package/src/cli/cluster.js +21 -0
- package/src/cli/cron.js +8 -0
- package/src/cli/db.js +63 -1
- package/src/cli/deploy.js +156 -3
- package/src/cli/env.js +43 -0
- package/src/cli/fs.js +94 -0
- package/src/cli/image.js +8 -0
- package/src/cli/index.js +17 -4
- package/src/cli/monitor.js +0 -1
- package/src/cli/repository.js +95 -2
- package/src/client/components/core/Css.js +16 -0
- package/src/client/components/core/Docs.js +5 -13
- package/src/client/components/core/Modal.js +48 -29
- package/src/client/components/core/Router.js +6 -3
- package/src/client/components/core/Worker.js +205 -118
- package/src/client/components/default/MenuDefault.js +1 -0
- package/src/client.dev.js +6 -3
- package/src/db/DataBaseProvider.js +65 -12
- package/src/db/mariadb/MariaDB.js +39 -6
- package/src/db/mongo/MongooseDB.js +51 -133
- package/src/index.js +1 -1
- package/src/mailer/EmailRender.js +58 -9
- package/src/mailer/MailerProvider.js +98 -25
- package/src/runtime/express/Express.js +20 -34
- package/src/server/auth.js +9 -28
- package/src/server/client-build-live.js +14 -5
- package/src/server/client-dev-server.js +21 -8
- package/src/server/conf.js +78 -25
- package/src/server/peer.js +2 -2
- package/src/server/runtime.js +0 -5
- package/src/server/start.js +39 -0
- package/src/ws/IoInterface.js +132 -39
- package/src/ws/IoServer.js +79 -31
- package/src/ws/core/core.ws.connection.js +50 -16
- package/src/ws/core/core.ws.emit.js +47 -8
- package/src/ws/core/core.ws.server.js +62 -10
|
@@ -8,18 +8,20 @@ const logger = loggerFactory(import.meta);
|
|
|
8
8
|
const clientLiveBuild = async () => {
|
|
9
9
|
if (fs.existsSync(`./tmp/client.build.json`)) {
|
|
10
10
|
const deployId = process.argv[2];
|
|
11
|
-
|
|
11
|
+
const subConf = process.argv[3];
|
|
12
12
|
let clientId = 'default';
|
|
13
13
|
let host = 'default.net';
|
|
14
14
|
let path = '/';
|
|
15
15
|
let baseHost = `${host}${path === '/' ? '' : path}`;
|
|
16
16
|
let views = Config.default.client[clientId].views;
|
|
17
|
+
let apiBaseHost;
|
|
18
|
+
let apiBaseProxyPath;
|
|
17
19
|
|
|
18
20
|
if (
|
|
19
21
|
deployId &&
|
|
20
22
|
(fs.existsSync(`./engine-private/conf/${deployId}`) || fs.existsSync(`./engine-private/replica/${deployId}`))
|
|
21
23
|
) {
|
|
22
|
-
loadConf(deployId);
|
|
24
|
+
loadConf(deployId, subConf);
|
|
23
25
|
const confClient = JSON.parse(
|
|
24
26
|
fs.readFileSync(
|
|
25
27
|
fs.existsSync(`./engine-private/replica/${deployId}`)
|
|
@@ -32,7 +34,9 @@ const clientLiveBuild = async () => {
|
|
|
32
34
|
);
|
|
33
35
|
const confServer = JSON.parse(
|
|
34
36
|
fs.readFileSync(
|
|
35
|
-
fs.existsSync(`./engine-private/
|
|
37
|
+
fs.existsSync(`./engine-private/conf/${deployId}/conf.server.dev.${subConf}.json`)
|
|
38
|
+
? `./engine-private/conf/${deployId}/conf.server.dev.${subConf}.json`
|
|
39
|
+
: fs.existsSync(`./engine-private/replica/${deployId}`)
|
|
36
40
|
? `./engine-private/replica/${deployId}/conf.server.json`
|
|
37
41
|
: fs.existsSync(`./engine-private/conf/${deployId}/conf.server.json`)
|
|
38
42
|
? `./engine-private/conf/${deployId}/conf.server.json`
|
|
@@ -40,20 +44,25 @@ const clientLiveBuild = async () => {
|
|
|
40
44
|
'utf8',
|
|
41
45
|
),
|
|
42
46
|
);
|
|
43
|
-
host = process.argv[
|
|
44
|
-
path = process.argv[
|
|
47
|
+
host = process.argv[4];
|
|
48
|
+
path = process.argv[5];
|
|
45
49
|
clientId = confServer[host][path].client;
|
|
46
50
|
views = confClient[clientId].views;
|
|
47
51
|
baseHost = `${host}${path === '/' ? '' : path}`;
|
|
52
|
+
apiBaseHost = confServer[host][path].apiBaseHost;
|
|
53
|
+
apiBaseProxyPath = confServer[host][path].apiBaseProxyPath;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
logger.info('Live build config', {
|
|
51
57
|
deployId,
|
|
58
|
+
subConf,
|
|
52
59
|
host,
|
|
53
60
|
path,
|
|
54
61
|
clientId,
|
|
55
62
|
baseHost,
|
|
56
63
|
views: views.length,
|
|
64
|
+
apiBaseHost,
|
|
65
|
+
apiBaseProxyPath,
|
|
57
66
|
});
|
|
58
67
|
|
|
59
68
|
const updates = JSON.parse(fs.readFileSync(`./tmp/client.build.json`, 'utf8'));
|
|
@@ -2,17 +2,26 @@ import fs from 'fs-extra';
|
|
|
2
2
|
import nodemon from 'nodemon';
|
|
3
3
|
import { shellExec } from './process.js';
|
|
4
4
|
import { loggerFactory } from './logger.js';
|
|
5
|
+
import { writeEnv } from './conf.js';
|
|
6
|
+
import dotenv from 'dotenv';
|
|
5
7
|
|
|
6
8
|
const logger = loggerFactory(import.meta);
|
|
7
9
|
|
|
8
|
-
const createClientDevServer = (
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const createClientDevServer = (
|
|
11
|
+
deployId = process.argv[2] || 'dd-default',
|
|
12
|
+
subConf = process.argv[3] || '',
|
|
13
|
+
host = process.argv[4] || 'default.net',
|
|
14
|
+
path = process.argv[5] || '/',
|
|
15
|
+
) => {
|
|
11
16
|
shellExec(
|
|
12
|
-
`env-cmd -f .env.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
`env-cmd -f ./engine-private/conf/${deployId}/.env.${process.env.NODE_ENV}.${subConf}-dev-client node bin/deploy build-full-client ${deployId} ${subConf}-dev-client ${host} ${path}`.trim(),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
shellExec(
|
|
21
|
+
`env-cmd -f ./engine-private/conf/${deployId}/.env.${process.env.NODE_ENV}.${subConf}-dev-client node src/server ${deployId} ${subConf}-dev-client`.trim(),
|
|
22
|
+
{
|
|
23
|
+
async: true,
|
|
24
|
+
},
|
|
16
25
|
);
|
|
17
26
|
|
|
18
27
|
// https://github.com/remy/nodemon/blob/main/doc/events.md
|
|
@@ -28,7 +37,11 @@ const createClientDevServer = () => {
|
|
|
28
37
|
|
|
29
38
|
let buildPathScope = [];
|
|
30
39
|
|
|
31
|
-
const nodemonOptions = {
|
|
40
|
+
const nodemonOptions = {
|
|
41
|
+
script: './src/client.build',
|
|
42
|
+
args: [`${deployId}`, `${subConf}-dev-client`, `${host}`, `${path}`],
|
|
43
|
+
watch: 'src/client',
|
|
44
|
+
};
|
|
32
45
|
logger.info('nodemon option', { nodemonOptions });
|
|
33
46
|
nodemon(nodemonOptions)
|
|
34
47
|
.on('start', function (...args) {
|
package/src/server/conf.js
CHANGED
|
@@ -25,13 +25,14 @@ const logger = loggerFactory(import.meta);
|
|
|
25
25
|
const Config = {
|
|
26
26
|
default: DefaultConf,
|
|
27
27
|
build: async function (deployContext = 'dd-default', deployList, subConf) {
|
|
28
|
-
if (typeof process.argv[2] === 'string' && process.argv[2].startsWith('dd-'))
|
|
28
|
+
if (process.argv[2] && typeof process.argv[2] === 'string' && process.argv[2].startsWith('dd-'))
|
|
29
|
+
deployContext = process.argv[2];
|
|
30
|
+
if (!subConf && process.argv[3] && typeof process.argv[3] === 'string') subConf = process.argv[3];
|
|
29
31
|
if (!fs.existsSync(`./tmp`)) fs.mkdirSync(`./tmp`, { recursive: true });
|
|
30
32
|
UnderpostRootEnv.API.set('await-deploy', new Date().toISOString());
|
|
31
|
-
if (fs.existsSync(`./engine-private/replica/${deployContext}`))
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (deployContext === 'proxy') Config.buildProxy(deployContext, deployList, subConf);
|
|
33
|
+
if (fs.existsSync(`./engine-private/replica/${deployContext}`)) return loadConf(deployContext, subConf);
|
|
34
|
+
else if (deployContext.startsWith('dd-')) return loadConf(deployContext, subConf);
|
|
35
|
+
if (deployContext === 'proxy') Config.buildProxy(deployList, subConf);
|
|
35
36
|
},
|
|
36
37
|
deployIdFactory: function (deployId = 'dd-default', options = { cluster: false }) {
|
|
37
38
|
if (!deployId.startsWith('dd-')) deployId = `dd-${deployId}`;
|
|
@@ -105,7 +106,7 @@ const Config = {
|
|
|
105
106
|
for (const confType of Object.keys(this.default))
|
|
106
107
|
fs.writeFileSync(`${folder}/conf.${confType}.json`, JSON.stringify(this.default[confType], null, 4), 'utf8');
|
|
107
108
|
},
|
|
108
|
-
buildProxy: function (
|
|
109
|
+
buildProxy: function (deployList = 'dd-default', subConf = '') {
|
|
109
110
|
if (!deployList) deployList = process.argv[3];
|
|
110
111
|
if (!subConf) subConf = process.argv[4];
|
|
111
112
|
this.default.server = {};
|
|
@@ -121,7 +122,7 @@ const Config = {
|
|
|
121
122
|
if (process.env.NODE_ENV === 'development' && fs.existsSync(confDevPath)) confPath = confDevPath;
|
|
122
123
|
const serverConf = JSON.parse(fs.readFileSync(confPath, 'utf8'));
|
|
123
124
|
|
|
124
|
-
for (const host of Object.keys(loadReplicas(serverConf
|
|
125
|
+
for (const host of Object.keys(loadReplicas(serverConf))) {
|
|
125
126
|
if (serverConf[host]['/'])
|
|
126
127
|
this.default.server[host] = {
|
|
127
128
|
...this.default.server[host],
|
|
@@ -138,13 +139,13 @@ const Config = {
|
|
|
138
139
|
},
|
|
139
140
|
};
|
|
140
141
|
|
|
141
|
-
const loadConf = (deployId = 'dd-default',
|
|
142
|
+
const loadConf = (deployId = 'dd-default', subConf) => {
|
|
142
143
|
if (deployId === 'current') {
|
|
143
144
|
console.log(process.env.DEPLOY_ID);
|
|
144
145
|
return;
|
|
145
146
|
}
|
|
146
147
|
if (deployId === 'clean') {
|
|
147
|
-
const path =
|
|
148
|
+
const path = '.';
|
|
148
149
|
fs.removeSync(`${path}/.env`);
|
|
149
150
|
shellExec(`git checkout ${path}/.env.production`);
|
|
150
151
|
shellExec(`git checkout ${path}/.env.development`);
|
|
@@ -164,7 +165,6 @@ const loadConf = (deployId = 'dd-default', envInput, subConf) => {
|
|
|
164
165
|
for (const typeConf of Object.keys(Config.default)) {
|
|
165
166
|
let srcConf = fs.readFileSync(`${folder}/conf.${typeConf}.json`, 'utf8');
|
|
166
167
|
if (process.env.NODE_ENV === 'development' && typeConf === 'server') {
|
|
167
|
-
if (!subConf) subConf = process.argv[3];
|
|
168
168
|
const devConfPath = `${folder}/conf.${typeConf}.dev${subConf ? `.${subConf}` : ''}.json`;
|
|
169
169
|
if (fs.existsSync(devConfPath)) srcConf = fs.readFileSync(devConfPath, 'utf8');
|
|
170
170
|
}
|
|
@@ -174,10 +174,13 @@ const loadConf = (deployId = 'dd-default', envInput, subConf) => {
|
|
|
174
174
|
fs.writeFileSync(`./.env.production`, fs.readFileSync(`${folder}/.env.production`, 'utf8'), 'utf8');
|
|
175
175
|
fs.writeFileSync(`./.env.development`, fs.readFileSync(`${folder}/.env.development`, 'utf8'), 'utf8');
|
|
176
176
|
fs.writeFileSync(`./.env.test`, fs.readFileSync(`${folder}/.env.test`, 'utf8'), 'utf8');
|
|
177
|
-
const NODE_ENV =
|
|
177
|
+
const NODE_ENV = process.env.NODE_ENV;
|
|
178
178
|
if (NODE_ENV) {
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
const subPathEnv = fs.existsSync(`${folder}/.env.${NODE_ENV}.${subConf}`)
|
|
180
|
+
? `${folder}/.env.${NODE_ENV}.${subConf}`
|
|
181
|
+
: `${folder}/.env.${NODE_ENV}`;
|
|
182
|
+
fs.writeFileSync(`./.env`, fs.readFileSync(subPathEnv, 'utf8'), 'utf8');
|
|
183
|
+
const env = dotenv.parse(fs.readFileSync(subPathEnv, 'utf8'));
|
|
181
184
|
process.env = {
|
|
182
185
|
...process.env,
|
|
183
186
|
...env,
|
|
@@ -191,22 +194,16 @@ const loadConf = (deployId = 'dd-default', envInput, subConf) => {
|
|
|
191
194
|
return { folder, deployId };
|
|
192
195
|
};
|
|
193
196
|
|
|
194
|
-
const loadReplicas = (confServer
|
|
195
|
-
if (!deployContext) deployContext = process.argv[2];
|
|
196
|
-
if (!subConf) subConf = process.argv[3];
|
|
197
|
+
const loadReplicas = (confServer) => {
|
|
197
198
|
for (const host of Object.keys(confServer)) {
|
|
198
199
|
for (const path of Object.keys(confServer[host])) {
|
|
199
200
|
const { replicas, singleReplica } = confServer[host][path];
|
|
200
|
-
if (
|
|
201
|
-
replicas &&
|
|
202
|
-
(deployContext === 'proxy' ||
|
|
203
|
-
!singleReplica ||
|
|
204
|
-
(singleReplica && process.env.NODE_ENV === 'development' && !subConf))
|
|
205
|
-
)
|
|
201
|
+
if (replicas && !singleReplica)
|
|
206
202
|
for (const replicaPath of replicas) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
203
|
+
{
|
|
204
|
+
confServer[host][replicaPath] = newInstance(confServer[host][path]);
|
|
205
|
+
delete confServer[host][replicaPath].replicas;
|
|
206
|
+
}
|
|
210
207
|
}
|
|
211
208
|
}
|
|
212
209
|
}
|
|
@@ -982,6 +979,60 @@ const getInstanceContext = async (options = { singleReplica, replicas, redirect:
|
|
|
982
979
|
return { redirectTarget };
|
|
983
980
|
};
|
|
984
981
|
|
|
982
|
+
const buildApiConf = async (options = { deployId: '', subConf: '', host: '', path: '', origin: '' }) => {
|
|
983
|
+
let { deployId, subConf, host, path, origin } = options;
|
|
984
|
+
if (!deployId) deployId = process.argv[2].trim();
|
|
985
|
+
if (!subConf) subConf = process.argv[3].trim();
|
|
986
|
+
if (process.argv[4]) host = process.argv[4].trim();
|
|
987
|
+
if (process.argv[5]) path = process.argv[5].trim();
|
|
988
|
+
if (process.argv[6])
|
|
989
|
+
origin = `${process.env.NODE_ENV === 'production' ? 'https' : 'http'}://${process.argv[6].trim()}`;
|
|
990
|
+
|
|
991
|
+
if (!origin) return;
|
|
992
|
+
const confServer = JSON.parse(
|
|
993
|
+
fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.dev.${subConf}.json`, 'utf8'),
|
|
994
|
+
);
|
|
995
|
+
const envObj = dotenv.parse(
|
|
996
|
+
fs.readFileSync(`./engine-private/conf/${deployId}/.env.${process.env.NODE_ENV}`, 'utf8'),
|
|
997
|
+
);
|
|
998
|
+
if (host && path) {
|
|
999
|
+
confServer[host][path].origins = [origin];
|
|
1000
|
+
logger.info('Build api conf', { host, path, origin });
|
|
1001
|
+
} else return;
|
|
1002
|
+
writeEnv(`./engine-private/conf/${deployId}/.env.${process.env.NODE_ENV}.${subConf}-dev-api`, envObj);
|
|
1003
|
+
fs.writeFileSync(
|
|
1004
|
+
`./engine-private/conf/${deployId}/conf.server.dev.${subConf}-dev-api.json`,
|
|
1005
|
+
JSON.stringify(confServer, null, 4),
|
|
1006
|
+
'utf8',
|
|
1007
|
+
);
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
const buildClientStaticConf = async (options = { deployId: '', subConf: '', apiBaseHost: '', host: '', path: '' }) => {
|
|
1011
|
+
let { deployId, subConf, host, path } = options;
|
|
1012
|
+
if (!deployId) deployId = process.argv[2].trim();
|
|
1013
|
+
if (!subConf) subConf = process.argv[3].trim();
|
|
1014
|
+
if (!host) host = process.argv[4].trim();
|
|
1015
|
+
if (!path) path = process.argv[5].trim();
|
|
1016
|
+
const confServer = JSON.parse(
|
|
1017
|
+
fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.dev.${subConf}-dev-api.json`, 'utf8'),
|
|
1018
|
+
);
|
|
1019
|
+
const envObj = dotenv.parse(
|
|
1020
|
+
fs.readFileSync(`./engine-private/conf/${deployId}/.env.${process.env.NODE_ENV}.${subConf}-dev-api`, 'utf8'),
|
|
1021
|
+
);
|
|
1022
|
+
envObj.PORT = parseInt(envObj.PORT);
|
|
1023
|
+
const apiBaseHost = options?.apiBaseHost ? options.apiBaseHost : `localhost:${envObj.PORT + 1}`;
|
|
1024
|
+
confServer[host][path].apiBaseHost = apiBaseHost;
|
|
1025
|
+
confServer[host][path].apiBaseProxyPath = path;
|
|
1026
|
+
logger.info('Build client static conf', { host, path, apiBaseHost });
|
|
1027
|
+
envObj.PORT = parseInt(confServer[host][path].origins[0].split(':')[2]) - 1;
|
|
1028
|
+
writeEnv(`./engine-private/conf/${deployId}/.env.${process.env.NODE_ENV}.${subConf}-dev-client`, envObj);
|
|
1029
|
+
fs.writeFileSync(
|
|
1030
|
+
`./engine-private/conf/${deployId}/conf.server.dev.${subConf}-dev-client.json`,
|
|
1031
|
+
JSON.stringify(confServer, null, 4),
|
|
1032
|
+
'utf8',
|
|
1033
|
+
);
|
|
1034
|
+
};
|
|
1035
|
+
|
|
985
1036
|
export {
|
|
986
1037
|
Cmd,
|
|
987
1038
|
Config,
|
|
@@ -1015,4 +1066,6 @@ export {
|
|
|
1015
1066
|
rebuildConfFactory,
|
|
1016
1067
|
buildCliDoc,
|
|
1017
1068
|
getInstanceContext,
|
|
1069
|
+
buildApiConf,
|
|
1070
|
+
buildClientStaticConf,
|
|
1018
1071
|
};
|
package/src/server/peer.js
CHANGED
|
@@ -48,7 +48,7 @@ const logger = loggerFactory(import.meta);
|
|
|
48
48
|
*/
|
|
49
49
|
const createPeerServer = async ({ port, devPort, origins, host, path }) => {
|
|
50
50
|
if (process.env.NODE_ENV === 'development' && devPort) {
|
|
51
|
-
logger.warn(`Adding development origin: http://localhost:${devPort}`);
|
|
51
|
+
// logger.warn(`Adding development origin: http://localhost:${devPort}`);
|
|
52
52
|
origins.push(`http://localhost:${devPort}`);
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -67,7 +67,7 @@ const createPeerServer = async ({ port, devPort, origins, host, path }) => {
|
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
// Use the framework's factory to listen on the server, ensuring graceful startup/shutdown
|
|
70
|
-
const peerServer = UnderpostStartUp.API.listenServerFactory(() => PeerServer(options));
|
|
70
|
+
const peerServer = UnderpostStartUp.API.listenServerFactory(async () => PeerServer(options));
|
|
71
71
|
|
|
72
72
|
return { options, peerServer, meta: import.meta };
|
|
73
73
|
};
|
package/src/server/runtime.js
CHANGED
|
@@ -103,10 +103,6 @@ const buildRuntime = async () => {
|
|
|
103
103
|
|
|
104
104
|
switch (runtime) {
|
|
105
105
|
case 'nodejs':
|
|
106
|
-
// The devApiPort is used for development CORS origin calculation
|
|
107
|
-
// It needs to account for the current port and potential peer server increment
|
|
108
|
-
const devApiPort = currentPort + (peer ? 2 : 1);
|
|
109
|
-
|
|
110
106
|
logger.info('Build nodejs server runtime', `${host}${path}:${port}`);
|
|
111
107
|
|
|
112
108
|
const { portsUsed } = await ExpressService.createApp({
|
|
@@ -124,7 +120,6 @@ const buildRuntime = async () => {
|
|
|
124
120
|
peer,
|
|
125
121
|
valkey,
|
|
126
122
|
apiBaseHost,
|
|
127
|
-
devApiPort, // Pass the dynamically calculated dev API port
|
|
128
123
|
redirectTarget,
|
|
129
124
|
rootHostPath,
|
|
130
125
|
confSSR,
|
package/src/server/start.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages the startup and runtime configuration of Underpost applications.
|
|
3
|
+
* @module src/server/start.js
|
|
4
|
+
* @namespace UnderpostStartUp
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import UnderpostDeploy from '../cli/deploy.js';
|
|
2
8
|
import fs from 'fs-extra';
|
|
3
9
|
import { awaitDeployMonitor } from './conf.js';
|
|
@@ -7,8 +13,17 @@ import UnderpostRootEnv from '../cli/env.js';
|
|
|
7
13
|
|
|
8
14
|
const logger = loggerFactory(import.meta);
|
|
9
15
|
|
|
16
|
+
/**
|
|
17
|
+
* @class UnderpostStartUp
|
|
18
|
+
* @description Manages the startup and runtime configuration of Underpost applications.
|
|
19
|
+
* @memberof UnderpostStartUp
|
|
20
|
+
*/
|
|
10
21
|
class UnderpostStartUp {
|
|
11
22
|
static API = {
|
|
23
|
+
/**
|
|
24
|
+
* Logs the runtime network configuration.
|
|
25
|
+
* @memberof UnderpostStartUp
|
|
26
|
+
*/
|
|
12
27
|
logRuntimeRouter: () => {
|
|
13
28
|
const displayLog = {};
|
|
14
29
|
|
|
@@ -18,6 +33,12 @@ class UnderpostStartUp {
|
|
|
18
33
|
|
|
19
34
|
logger.info('Runtime network', displayLog);
|
|
20
35
|
},
|
|
36
|
+
/**
|
|
37
|
+
* Creates a server factory.
|
|
38
|
+
* @memberof UnderpostStartUp
|
|
39
|
+
* @param {Function} logic - The logic to execute when the server is listening.
|
|
40
|
+
* @returns {Object} An object with a listen method.
|
|
41
|
+
*/
|
|
21
42
|
listenServerFactory: (logic = async () => {}) => {
|
|
22
43
|
return {
|
|
23
44
|
listen: async (...args) => {
|
|
@@ -36,6 +57,15 @@ class UnderpostStartUp {
|
|
|
36
57
|
},
|
|
37
58
|
};
|
|
38
59
|
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Controls the listening port for a server.
|
|
63
|
+
* @memberof UnderpostStartUp
|
|
64
|
+
* @param {Object} server - The server to listen on.
|
|
65
|
+
* @param {number|string} port - The port number or colon for all ports.
|
|
66
|
+
* @param {Object} metadata - Metadata for the server.
|
|
67
|
+
* @returns {Promise<boolean>} A promise that resolves to true if the server is listening, false otherwise.
|
|
68
|
+
*/
|
|
39
69
|
listenPortController: async (server, port, metadata) =>
|
|
40
70
|
new Promise((resolve) => {
|
|
41
71
|
try {
|
|
@@ -79,6 +109,15 @@ class UnderpostStartUp {
|
|
|
79
109
|
}
|
|
80
110
|
}),
|
|
81
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Starts a deployment.
|
|
114
|
+
* @memberof UnderpostStartUp
|
|
115
|
+
* @param {string} deployId - The ID of the deployment.
|
|
116
|
+
* @param {string} env - The environment of the deployment.
|
|
117
|
+
* @param {Object} options - Options for the deployment.
|
|
118
|
+
* @param {boolean} options.build - Whether to build the deployment.
|
|
119
|
+
* @param {boolean} options.run - Whether to run the deployment.
|
|
120
|
+
*/
|
|
82
121
|
async callback(deployId = 'dd-default', env = 'development', options = { build: false, run: false }) {
|
|
83
122
|
if (options.build === true) await UnderpostStartUp.API.build(deployId, env);
|
|
84
123
|
if (options.run === true) await UnderpostStartUp.API.run(deployId, env);
|
package/src/ws/IoInterface.js
CHANGED
|
@@ -1,45 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module for creating and managing WebSocket channels.
|
|
3
|
+
* @module src/ws/IoInterface.js
|
|
4
|
+
* @namespace SocketIoInterface
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import { loggerFactory } from '../server/logger.js';
|
|
8
|
+
import { Socket } from 'socket.io';
|
|
2
9
|
|
|
3
10
|
const logger = loggerFactory(import.meta);
|
|
4
11
|
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Defines the structure for a WebSocket channel's behavior functions.
|
|
14
|
+
* @typedef {Object} ChannelInterface
|
|
15
|
+
* @property {string} channel - The name of the channel.
|
|
16
|
+
* @property {function(Socket, Object.<string, Socket>, string): Promise<void>} [connection] - Handler on client connection.
|
|
17
|
+
* @property {function(Socket, Object.<string, Socket>, any, string, any[]): Promise<void>} [controller] - Handler for incoming channel messages.
|
|
18
|
+
* @property {function(Socket, Object.<string, Socket>, string, string): Promise<void>} [disconnect] - Handler on client disconnection.
|
|
19
|
+
* @property {boolean} [stream=false] - Whether the channel should treat the message as a raw stream (no JSON parsing).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @class
|
|
24
|
+
* @alias IoChannel
|
|
25
|
+
* @memberof SocketIoInterface
|
|
26
|
+
* @classdesc Manages the logic, client map, and event listeners for a specific WebSocket channel,
|
|
27
|
+
* ensuring robust message handling and lifecycle management.
|
|
28
|
+
*/
|
|
29
|
+
class IoChannel {
|
|
30
|
+
/**
|
|
31
|
+
* @private
|
|
32
|
+
* @type {ChannelInterface}
|
|
33
|
+
*/
|
|
34
|
+
#IoInterface;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Map of connected sockets for this channel, keyed by socket ID.
|
|
38
|
+
* @type {Object.<string, Socket>}
|
|
39
|
+
*/
|
|
40
|
+
client = {};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creates an instance of IoChannel.
|
|
44
|
+
* @param {ChannelInterface} IoInterface - The interface object defining the channel's behavior.
|
|
45
|
+
*/
|
|
46
|
+
constructor(IoInterface) {
|
|
47
|
+
this.#IoInterface = {
|
|
48
|
+
channel: '',
|
|
49
|
+
connection: async (socket = {}, client = {}, wsManagementId = '') => {},
|
|
50
|
+
controller: async (socket = {}, client = {}, payload = {}, wsManagementId = '', args = []) => {},
|
|
51
|
+
disconnect: async (socket = {}, client = {}, reason = '', wsManagementId = '') => {},
|
|
52
|
+
stream: false,
|
|
53
|
+
...IoInterface,
|
|
54
|
+
};
|
|
55
|
+
logger.debug(`Channel instance created for: ${this.channel}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Gets the name of the channel.
|
|
60
|
+
* @returns {string} The channel name.
|
|
61
|
+
*/
|
|
62
|
+
get channel() {
|
|
63
|
+
return this.#IoInterface.channel;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Handles a new socket connection for this channel.
|
|
68
|
+
* Sets up the listener for the channel message.
|
|
69
|
+
*
|
|
70
|
+
* @param {Socket} socket - The Socket.IO socket object.
|
|
71
|
+
* @param {string} wsManagementId - Unique identifier for the WebSocket management context.
|
|
72
|
+
* @returns {Promise<void>}
|
|
73
|
+
*/
|
|
74
|
+
async connection(socket, wsManagementId) {
|
|
75
|
+
try {
|
|
76
|
+
this.client[socket.id] = socket;
|
|
77
|
+
// Use bind/arrow function to maintain 'this' context for the controller
|
|
78
|
+
socket.on(this.channel, (...args) => this.controller(socket, args, wsManagementId));
|
|
79
|
+
await this.#IoInterface.connection(socket, this.client, wsManagementId);
|
|
80
|
+
logger.debug(`Socket ${socket.id} connected to channel ${this.channel}`);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logger.error(error, { channel: this.channel, wsManagementId, stack: error.stack });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Handles incoming messages on the channel.
|
|
88
|
+
*
|
|
89
|
+
* @private
|
|
90
|
+
* @param {Socket} socket - The Socket.IO socket object.
|
|
91
|
+
* @param {any[]} args - The raw arguments received from the socket event.
|
|
92
|
+
* @param {string} wsManagementId - Unique identifier for the WebSocket management context.
|
|
93
|
+
* @returns {Promise<void>}
|
|
94
|
+
*/
|
|
95
|
+
async controller(socket, args, wsManagementId) {
|
|
96
|
+
try {
|
|
97
|
+
if (!args || args.length === 0) {
|
|
98
|
+
logger.warn(`No arguments received for channel: ${this.channel}`, { socketId: socket.id });
|
|
99
|
+
return;
|
|
40
100
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
101
|
+
// Determine if JSON parsing is needed based on the stream flag
|
|
102
|
+
const payload = this.#IoInterface.stream ? args[0] : JSON.parse(args[0]);
|
|
103
|
+
|
|
104
|
+
await this.#IoInterface.controller(socket, this.client, payload, wsManagementId, args);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
logger.error(error, { channel: this.channel, wsManagementId, socketId: socket.id, args, stack: error.stack });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Handles a socket disconnection for this channel.
|
|
112
|
+
*
|
|
113
|
+
* @param {Socket} socket - The Socket.IO socket object.
|
|
114
|
+
* @param {string} reason - The reason for disconnection (e.g., 'client namespace disconnect').
|
|
115
|
+
* @param {string} wsManagementId - Unique identifier for the WebSocket management context.
|
|
116
|
+
* @returns {Promise<void>}
|
|
117
|
+
*/
|
|
118
|
+
async disconnect(socket, reason, wsManagementId) {
|
|
119
|
+
try {
|
|
120
|
+
await this.#IoInterface.disconnect(socket, this.client, reason, wsManagementId);
|
|
121
|
+
delete this.client[socket.id];
|
|
122
|
+
logger.debug(`Socket ${socket.id} disconnected from channel ${this.channel}. Reason: ${reason}`);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
logger.error(error, { channel: this.channel, wsManagementId, reason, socketId: socket.id, stack: error.stack });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Backward compatibility function to create a new channel instance.
|
|
131
|
+
* @memberof SocketIoInterface
|
|
132
|
+
* @function IoCreateChannel
|
|
133
|
+
* @param {ChannelInterface} IoInterface - The interface object defining the channel's behavior.
|
|
134
|
+
* @returns {IoChannel} An instance of the IoChannel class.
|
|
135
|
+
*/
|
|
136
|
+
const IoCreateChannel = (IoInterface) => new IoChannel(IoInterface);
|
|
44
137
|
|
|
45
|
-
export { IoCreateChannel };
|
|
138
|
+
export { IoChannel, IoCreateChannel };
|