verteilen-core 1.0.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -0
  3. package/bun.lock +730 -0
  4. package/index.ts +1 -0
  5. package/jest.config.js +8 -0
  6. package/package.json +42 -0
  7. package/src/client/analysis.ts +377 -0
  8. package/src/client/client.ts +230 -0
  9. package/src/client/cluster.ts +125 -0
  10. package/src/client/execute.ts +210 -0
  11. package/src/client/http.ts +45 -0
  12. package/src/client/javascript.ts +535 -0
  13. package/src/client/job_execute.ts +216 -0
  14. package/src/client/job_parameter.ts +41 -0
  15. package/src/client/os.ts +210 -0
  16. package/src/client/parameter.ts +58 -0
  17. package/src/client/resource.ts +121 -0
  18. package/src/client/shell.ts +147 -0
  19. package/src/interface/base.ts +82 -0
  20. package/src/interface/bus.ts +144 -0
  21. package/src/interface/enum.ts +181 -0
  22. package/src/interface/execute.ts +47 -0
  23. package/src/interface/record.ts +131 -0
  24. package/src/interface/server.ts +91 -0
  25. package/src/interface/struct.ts +292 -0
  26. package/src/interface/table.ts +34 -0
  27. package/src/interface/ui.ts +35 -0
  28. package/src/interface.ts +50 -0
  29. package/src/lan/en.json +395 -0
  30. package/src/lan/zh_TW.json +395 -0
  31. package/src/plugins/i18n.ts +20 -0
  32. package/src/script/console_manager.ts +135 -0
  33. package/src/script/console_server_manager.ts +46 -0
  34. package/src/script/execute/base.ts +309 -0
  35. package/src/script/execute/feedback.ts +212 -0
  36. package/src/script/execute/region_job.ts +14 -0
  37. package/src/script/execute/region_project.ts +23 -0
  38. package/src/script/execute/region_subtask.ts +14 -0
  39. package/src/script/execute/region_task.ts +23 -0
  40. package/src/script/execute/runner.ts +339 -0
  41. package/src/script/execute/util_parser.ts +175 -0
  42. package/src/script/execute_manager.ts +348 -0
  43. package/src/script/socket_manager.ts +329 -0
  44. package/src/script/webhook_manager.ts +6 -0
  45. package/src/script/webhook_server_manager.ts +102 -0
  46. package/src/util/server/console_handle.ts +248 -0
  47. package/src/util/server/log_handle.ts +194 -0
  48. package/test/TEST.ts +63 -0
  49. package/test/client/execute.test.ts +54 -0
  50. package/test/client/javascript.test.ts +78 -0
  51. package/test/client/server.test.ts +26 -0
  52. package/test/client/task.test.ts +136 -0
  53. package/test/script/parser.test.ts +110 -0
  54. package/test/script/socket.test.ts +27 -0
  55. package/tsconfig.json +15 -0
package/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './src/interface'
package/jest.config.js ADDED
@@ -0,0 +1,8 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} **/
2
+ module.exports = {
3
+ verbose: true,
4
+ testEnvironment: "node",
5
+ transform: {
6
+ "^.+\.tsx?$": ["ts-jest",{}],
7
+ }
8
+ };
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "verteilen-core",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "homepage": "https://verteilen.github.io/wiki/",
6
+ "author": "Elly",
7
+ "repository": "https://github.com/Verteilen/Verteilen-Core",
8
+ "description": "The core library help verteilen software to work",
9
+ "main": "main/main.js",
10
+ "scripts": {
11
+ "test": "jest --coverage"
12
+ },
13
+ "devDependencies": {
14
+ "@types/bcrypt": "^5.0.2",
15
+ "@types/jest": "^30.0.0",
16
+ "@types/pem": "^1.14.4",
17
+ "@types/tcp-port-used": "^1.0.4",
18
+ "@types/ws": "^8.5.14",
19
+ "ts-jest": "^29.3.1",
20
+ "ts-node": "^10.9.2",
21
+ "typescript": "^5.2.2"
22
+ },
23
+ "dependencies": {
24
+ "@intlify/devtools-types": "^11.1.12",
25
+ "bcrypt": "^5.1.1",
26
+ "browser-or-node": "^3.0.0",
27
+ "byte-size": "^9.0.1",
28
+ "expressionparser": "^1.1.5",
29
+ "jest": "^29.7.0",
30
+ "pem": "^1.14.8",
31
+ "systeminformation": "^5.25.11",
32
+ "tcp-port-used": "^1.0.2",
33
+ "tree-kill": "^1.2.2",
34
+ "tsc": "^2.0.4",
35
+ "uuid": "^11.0.5",
36
+ "vue-i18n": "^11.1.12",
37
+ "ws": "^8.18.0"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/Verteilen/Verteilen-Core/issues"
41
+ }
42
+ }
@@ -0,0 +1,377 @@
1
+ // ========================
2
+ //
3
+ // Share Codebase
4
+ //
5
+ // ========================
6
+ import { ChildProcess, exec, spawn } from 'child_process';
7
+ import { WebSocket } from 'ws';
8
+ import { DATA_FOLDER, Header, Job, Libraries, Messager, Messager_log, Parameter, Plugin, PluginList, PluginToken, PluginWithToken } from "../interface";
9
+ import { Client } from './client';
10
+ import { ClientExecute } from "./execute";
11
+ import { ClientShell } from './shell';
12
+ import { createWriteStream, existsSync, mkdir, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
13
+ import * as path from 'path';
14
+ import * as os from 'os';
15
+
16
+ /**
17
+ * The analysis worker. decode the message received from cluster server
18
+ */
19
+ export class ClientAnalysis {
20
+ private messager: Messager
21
+ private messager_log: Messager_log
22
+ private client:Client
23
+ private exec:Array<ClientExecute>
24
+ private shell:ClientShell
25
+ private resource_wanter:Array<WebSocket> = []
26
+ private resource_thread:ChildProcess | undefined = undefined
27
+
28
+ private resource_cache:Header | undefined = undefined
29
+
30
+ constructor(_messager:Messager, _messager_log:Messager_log, _client:Client){
31
+ this.client = _client
32
+ this.messager = _messager
33
+ this.messager_log = _messager_log
34
+ this.shell = new ClientShell(_messager, _messager_log, this.client)
35
+ this.exec = []
36
+ }
37
+
38
+ /**
39
+ * Analysis the package
40
+ * @param h Package
41
+ * @param source Websocket instance
42
+ * @return
43
+ * * 0: Successfully execute command
44
+ * * 1: The header is undefined, cannot process
45
+ * * 2: Cannot find the header name match with function typeMap
46
+ */
47
+ analysis = (h:Header | undefined, source:WebSocket) => {
48
+ const typeMap = {
49
+ 'execute_job': this.execute_job,
50
+ 'release': this.release,
51
+ 'stop_job': this.stop_all,
52
+ 'set_parameter': this.set_parameter,
53
+ 'set_libs': this.set_libs,
54
+ 'shell_folder': this.shell.shell_folder,
55
+ 'open_shell': this.shell.open_shell,
56
+ 'close_shell': this.shell.close_shell,
57
+ 'enter_shell': this.shell.enter_shell,
58
+ 'resource_start': this.resource_start,
59
+ 'resource_end': this.resource_end,
60
+ 'ping': this.pong,
61
+ 'plugin_info': this.plugin_info,
62
+ 'plugin_download': this.plugin_download,
63
+ 'plugin_remove': this.plugin_remove,
64
+ }
65
+
66
+ if (h == undefined){
67
+ this.messager_log('[Client Analysis] Analysis Failed, Value is undefined')
68
+ return 1
69
+ }
70
+ if (h.message != undefined && h.message.length > 0){
71
+ this.messager_log(`[Client Analysis] ${h.message}`)
72
+ }
73
+ if (h.data == undefined) {
74
+ this.messager_log('[Client Analysis] Analysis Warn, Data is undefined')
75
+ h.data = 0
76
+ }
77
+ if(typeMap.hasOwnProperty(h.name)){
78
+ const castingFunc = typeMap[h.name]
79
+ castingFunc(h.data, source, h.channel)
80
+ return 0
81
+ }else{
82
+ this.messager_log(`[Client Analysis] Analysis Failed, Unknowed header, name: ${h.name}, meta: ${h.meta}`)
83
+ return 2
84
+ }
85
+ }
86
+
87
+ private execute_job = (job: Job, source: WebSocket, channel:string | undefined) => {
88
+ if(channel == undefined) return
89
+ const target = this.exec_checker(channel)
90
+ target.execute_job(job, source)
91
+ }
92
+
93
+ private release = (dummy:number, source: WebSocket, channel:string | undefined) => {
94
+ if(channel == undefined) return
95
+ const index = this.exec.findIndex(x => x.uuid == channel)
96
+ if(index == -1) return
97
+ this.exec.splice(index, 1)
98
+ }
99
+
100
+ private set_parameter = (data:Parameter, source: WebSocket, channel:string | undefined) => {
101
+ if(channel == undefined) return
102
+ const target = this.exec_checker(channel)
103
+ target.set_parameter(data)
104
+ }
105
+
106
+ private set_libs = (data:Libraries, source: WebSocket, channel:string | undefined) => {
107
+ if(channel == undefined) return
108
+ const target = this.exec_checker(channel)
109
+ target.set_libs(data)
110
+ }
111
+
112
+ private exec_checker = (uuid:string): ClientExecute => {
113
+ let r:ClientExecute | undefined = undefined
114
+ const index = this.exec.findIndex(x => x.uuid == uuid)
115
+ if(index == -1) {
116
+ r = new ClientExecute(uuid, this.messager, this.messager_log, this.client)
117
+ this.exec.push(r)
118
+ }else{
119
+ r = this.exec[index]
120
+ }
121
+ return r
122
+ }
123
+
124
+ /**
125
+ * Network delay request
126
+ * @param data Dummy value, should always be 0
127
+ * @param source The cluster server websocket instance
128
+ */
129
+ private pong = (data:number, source: WebSocket) => {
130
+ const h:Header = { name: 'pong', data: data }
131
+ source.send(JSON.stringify(h))
132
+ }
133
+
134
+ private plugin_info = (data:number, source: WebSocket) => {
135
+ const pat = path.join(os.homedir(), DATA_FOLDER, "plugin.json")
136
+ if(existsSync(pat)){
137
+ const p:PluginList = JSON.parse(readFileSync(pat).toString())
138
+ const h:Header = { name: 'plugin_info_reply', data: p.plugins }
139
+ source.send(JSON.stringify(h))
140
+ }else{
141
+ const p:PluginList = { plugins: [] }
142
+ const h:Header = { name: 'plugin_info_reply', data: p.plugins }
143
+ writeFileSync(pat, JSON.stringify(p))
144
+ source.send(JSON.stringify(h))
145
+ }
146
+ }
147
+
148
+ private get_releases = async (repo:string, token:string | undefined) => {
149
+ const qu = await fetch(`https://api.github.com/repos/${repo}/releases`, {
150
+ headers: {
151
+ Authorization: token ? `token ${token}`: '',
152
+ Accept: "application/vnd.github.v3.raw",
153
+ }
154
+ })
155
+ return qu.text()
156
+ }
157
+
158
+ private filterout = async (repo:string, token:string | undefined, version:string, filename:string) => {
159
+ const text = await this.get_releases(repo, token)
160
+ const json:Array<any> = JSON.parse(text)
161
+ const v = json.find(x => x.tag_name == version)
162
+ if(!v) return
163
+ const f = v.assets.find(x => x.name == filename)
164
+ if(!f) return
165
+ return f.id
166
+ }
167
+
168
+ private write_plugin = (t: string | undefined, plugin:PluginWithToken, source: WebSocket) => {
169
+ const list = this.client.plugins.plugins
170
+ const index = list.findIndex(x => x.name == plugin.name)
171
+ plugin.token = t ? [t] : []
172
+ plugin.progress = 0
173
+ if(index == -1){
174
+ list.push(plugin)
175
+ }else{
176
+ list[index] = plugin
177
+ }
178
+ this.client.savePlugin()
179
+ this.plugin_info(0, source)
180
+ }
181
+
182
+ private finish_plugin = (plugin:PluginWithToken, source: WebSocket) => {
183
+ const list = this.client.plugins.plugins
184
+ const index = list.findIndex(x => x.name == plugin.name)
185
+ plugin.progress = 1
186
+ if(index == -1){
187
+ list.push(plugin)
188
+ }else{
189
+ list[index] = plugin
190
+ }
191
+ this.client.savePlugin()
192
+ this.plugin_info(0, source)
193
+ }
194
+
195
+ private plugin_download = async (plugin:PluginWithToken, source: WebSocket) => {
196
+ const target = plugin.contents.find(x => x.arch == process.arch && x.platform == process.platform)
197
+ if(target == undefined){
198
+ this.messager_log(`[Plugin] Cannot find target plugin for ${plugin.name} on ${process.platform} ${process.arch}`)
199
+ return
200
+ }
201
+ const links = target.url.split('/')
202
+ const filename = links[links.length - 1]
203
+ const version = links[links.length - 2]
204
+ const REPO = `${links[3]}/${links[4]}`
205
+ const dir = path.join(os.homedir(), DATA_FOLDER, "exe")
206
+ if(!existsSync(dir)) mkdirSync(dir, { recursive: true })
207
+ let req:RequestInit = {}
208
+ const tokens = [undefined, ...plugin.token]
209
+ const fileStream = createWriteStream(path.join(dir, target.filename), { flags: 'a' });
210
+ let pass = false
211
+ for(let t of tokens){
212
+ if(pass) break
213
+ try{
214
+ const id = await this.filterout(REPO, t, version, filename)
215
+ req = {
216
+ method: 'GET',
217
+ credentials: 'include',
218
+ headers: {
219
+ Authorization: t ? `token ${t}` : '',
220
+ Accept: "application/octet-stream"
221
+ }
222
+ }
223
+ const url = `https://api.github.com/repos/${REPO}/releases/assets/${id}`
224
+ fetch(url, req).then(async res => {
225
+ if(!res.ok){
226
+ throw new Error(`Failed to download file: ${res.status} ${res.statusText}`);
227
+ }
228
+ this.write_plugin(t, plugin, source)
229
+ return res.blob()
230
+ }).then(blob => {
231
+ return blob.stream().getReader().read()
232
+ })
233
+ .then(reader => {
234
+ if(!reader.done){
235
+ fileStream.write(Buffer.from(reader.value))
236
+ }
237
+ }).finally(() => {
238
+ this.messager_log(`[Plugin] Downloaded ${plugin.name} successfully`)
239
+ fileStream.end();
240
+ if(process.platform == 'linux'){
241
+ exec(`chmod +x ${path.join(dir, target.filename)}`, (err) => {
242
+ this.messager_log(`[Plugin] Permission failed ${err?.message}`)
243
+ })
244
+ }
245
+ this.finish_plugin(plugin, source)
246
+ pass = true
247
+ })
248
+ }
249
+ catch(err:any){
250
+ this.messager_log(`[Plugin] Download failed for ${plugin.name}: ${err.message}`)
251
+ }
252
+ }
253
+ }
254
+
255
+ private plugin_remove = (plugin:Plugin, source: WebSocket) => {
256
+ this.client.plugins.plugins = this.client.plugins.plugins.filter(x => x.name != plugin.name)
257
+ this.client.savePlugin()
258
+ const dir = path.join(os.homedir(), DATA_FOLDER, "exe")
259
+ if(!existsSync(dir)) mkdirSync(dir, { recursive: true })
260
+ plugin.contents.forEach(x => {
261
+ if(existsSync(path.join(dir, x.filename))){
262
+ rmSync(path.join(dir, x.filename))
263
+ }
264
+ })
265
+ this.plugin_info(0, source)
266
+ }
267
+
268
+ private resource_start = (data:number, source: WebSocket) => {
269
+ this.resource_wanter.push(source)
270
+ this.messager_log(`Register resource_wanter!, count: ${this.resource_wanter.length}`)
271
+ if(this.resource_cache != undefined) source.send(JSON.stringify(this.resource_cache))
272
+ }
273
+
274
+ private resource_end = (data:number, source: WebSocket) => {
275
+ const index = this.resource_wanter.findIndex(x => x ==source)
276
+ if(index != -1) {
277
+ this.resource_wanter.splice(index, 1)
278
+ this.messager_log(`UnRegister resource_wanter!, count: ${this.resource_wanter.length}`)
279
+ }
280
+ }
281
+
282
+ update = (client:Client) => {
283
+ this.resource_require()
284
+ if(this.resource_cache != undefined){
285
+ this.resource_wanter.forEach(x => x.send(JSON.stringify(this.resource_cache)))
286
+ }
287
+ }
288
+
289
+ disconnect = (source: WebSocket) => {
290
+ this.shell.disconnect(source)
291
+ this.exec.forEach(x => x.stop_job())
292
+ }
293
+
294
+ stop_all = () => {
295
+ this.exec.forEach(x => x.stop_job())
296
+ }
297
+
298
+ destroy = () => {
299
+ if(this.resource_thread != undefined) this.resource_thread.kill()
300
+ }
301
+
302
+ private resource_require = () => {
303
+ if(this.resource_thread != undefined) return
304
+ const shouldRun = this.resource_thread == undefined && (this.resource_cache == undefined || this.resource_wanter.length > 0)
305
+ if(!shouldRun) return
306
+ this.resource_thread = spawn(Client.workerPath(), [],
307
+ {
308
+ stdio: ['inherit', 'pipe', 'pipe'],
309
+ shell: true,
310
+ windowsHide: true,
311
+ env: {
312
+ ...process.env,
313
+ type: "RESOURCE",
314
+ cache: this.resource_cache == undefined ? undefined : JSON.stringify(this.resource_cache.data)
315
+ }
316
+ }
317
+ )
318
+ let k = ""
319
+
320
+ const workerFeedbackExec = (str:string) => {
321
+ try{
322
+ const msg:Header = JSON.parse(str)
323
+ if(msg.name == 'messager'){
324
+ this.messager(msg.data, "RESOURCE")
325
+ }
326
+ else if(msg.name == 'messager_log'){
327
+ this.messager_log(msg.data, "RESOURCE")
328
+ }
329
+ else if(msg.name == 'resource'){
330
+ const h:Header = {
331
+ name: 'system_info',
332
+ data: msg.data
333
+ }
334
+ this.resource_cache = h
335
+ this.resource_wanter.forEach(x => x.send(JSON.stringify(h)))
336
+ }
337
+ else if(msg.name == 'error'){
338
+ if(msg.data instanceof String) this.messager_log(msg.data.toString(), "RESOURCE")
339
+ else this.messager_log(JSON.stringify(msg.data), "RESOURCE")
340
+ }
341
+ }catch(err:any){
342
+ console.log("str: " + str)
343
+ console.log(err.name + "\n" + err.message)
344
+ }
345
+ }
346
+ const workerFeedback = (str:string) => {
347
+ for(let i = 0; i < str.length; i++){
348
+ if(str[i] != '\n') k += str[i]
349
+ else {
350
+ workerFeedbackExec(k)
351
+ k = ''
352
+ }
353
+ }
354
+ }
355
+
356
+ this.resource_thread.on('error', (err) => {
357
+ this.messager_log(`[Worker Error] ${err}`)
358
+ })
359
+
360
+ this.resource_thread.on('exit', (code, signal) => {
361
+ this.resource_thread = undefined
362
+ })
363
+ this.resource_thread.on('message', (message, sendHandle) => {
364
+ workerFeedback(message.toString())
365
+ })
366
+ this.resource_thread.stdout?.setEncoding('utf8');
367
+ this.resource_thread.stdout?.on('data', (chunk) => {
368
+ workerFeedback(chunk.toString())
369
+ })
370
+ this.resource_thread.stderr?.setEncoding('utf8');
371
+ this.resource_thread.stderr?.on('data', (chunk) => {
372
+ workerFeedback(chunk.toString())
373
+ })
374
+ }
375
+
376
+
377
+ }
@@ -0,0 +1,230 @@
1
+ // ========================
2
+ //
3
+ // Share Codebase
4
+ //
5
+ // ========================
6
+ import * as path from 'path';
7
+ import { check } from 'tcp-port-used';
8
+ import { WebSocket } from 'ws';
9
+ import * as ws from 'ws';
10
+ import { CLIENT_UPDATETICK, DATA_FOLDER, Header, Messager, Messager_log, Plugin, PluginList, PORT } from '../interface';
11
+ import { ClientAnalysis } from './analysis';
12
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
13
+ import * as os from 'os'
14
+ import * as pem from 'pem'
15
+ import * as https from 'https'
16
+
17
+ /**
18
+ * The calculation node worker
19
+ */
20
+ export class Client {
21
+ plugins: PluginList = { plugins: [] }
22
+
23
+ private httpss:https.Server<any> | undefined = undefined
24
+ private client:ws.Server | undefined = undefined
25
+ private sources:Array<WebSocket> = []
26
+ private messager:Messager
27
+ private messager_log:Messager_log
28
+ private analysis:Array<ClientAnalysis>
29
+ private updatehandle
30
+
31
+ /**
32
+ * Get connected client count
33
+ */
34
+ public get count() : number {
35
+ return this.sources.length
36
+ }
37
+ /**
38
+ * Get connected client list instance
39
+ */
40
+ public get clients() : Array<WebSocket> {
41
+ return this.sources
42
+ }
43
+
44
+ constructor(_messager:Messager, _messager_log:Messager_log){
45
+ this.messager = _messager
46
+ this.messager_log = _messager_log
47
+ this.analysis = []
48
+ this.updatehandle = setInterval(this.update, CLIENT_UPDATETICK);
49
+ this.loadPlugins()
50
+ }
51
+
52
+ Dispose (){
53
+ clearInterval(this.updatehandle)
54
+ }
55
+
56
+ /**
57
+ * Start a websocket server, and waiting for cluster server to connect
58
+ */
59
+ Init = async () => {
60
+ let port_result = PORT
61
+ let canbeuse = false
62
+
63
+ while(!canbeuse){
64
+ await check(port_result).then(x => {
65
+ canbeuse = !x
66
+ }).catch(err => {
67
+ canbeuse = true
68
+ })
69
+ if(!canbeuse) port_result += 1
70
+ }
71
+ const pems = await this.get_pem()
72
+ this.httpss = https.createServer({ key: pems[0], cert: pems[1], minVersion: 'TLSv1.2', maxVersion: 'TLSv1.3' }, (req, res) => {
73
+ res.writeHead(200)
74
+ res.end('HTTPS server is running');
75
+ })
76
+ this.httpss.addListener('upgrade', (req, res, head) => console.log('UPGRADE:', req.url))
77
+ this.client = new ws.Server({server: this.httpss})
78
+ this.client.on('listening', () => {
79
+ this.messager_log('[Server] Listen PORT: ' + port_result.toString())
80
+ })
81
+ this.client.on('error', (err) => {
82
+ this.messager_log(`[Server] Error ${err.name}\n\t${err.message}\n\t${err.stack}`)
83
+ })
84
+ this.client.on('close', () => {
85
+ this.messager_log('[Server] Close !')
86
+ this.Release()
87
+ })
88
+ this.client.on('connection', (ws, request) => {
89
+ const a = new ClientAnalysis(this.messager, this.messager_log, this)
90
+ this.analysis.push(a)
91
+ this.sources.push(ws)
92
+ this.messager_log(`[Server] New Connection detected, ${ws.url}`)
93
+ ws.on('close', (code, reason) => {
94
+ const index = this.sources.findIndex(x => x == ws)
95
+ if(index != -1) this.sources.splice(index, 1)
96
+ this.messager_log(`[Source] Close ${code} ${reason}`)
97
+ a.disconnect(ws)
98
+ })
99
+ ws.on('error', (err) => {
100
+ this.messager_log(`[Source] Error ${err.name}\n\t${err.message}\n\t${err.stack}`)
101
+ })
102
+ ws.on('open', () => {
103
+ this.messager_log(`[Source] New source is connected, URL: ${ws?.url}`)
104
+ })
105
+ ws.on('message', (data, isBinery) => {
106
+ const h:Header | undefined = JSON.parse(data.toString());
107
+ a.analysis(h, ws);
108
+ })
109
+ })
110
+ this.httpss.listen(port_result, () => {
111
+ this.messager_log('[Server] Select Port: ' + port_result.toString())
112
+ })
113
+ }
114
+
115
+ Destroy = () => {
116
+ if(this.client == undefined) return
117
+ this.client.close((err) => {
118
+ this.messager_log(`[Client] Close error ${err}`)
119
+ })
120
+ this.Release()
121
+ }
122
+
123
+ Release = () => {
124
+ this.analysis.forEach(x => x.stop_all())
125
+ this.analysis.forEach(x => x.destroy())
126
+ this.analysis = []
127
+ }
128
+
129
+ savePlugin = () => {
130
+ const f = path.join(os.homedir(), DATA_FOLDER)
131
+ const pluginPath = path.join(f, 'plugin.json')
132
+ if(!existsSync(f)) mkdirSync(f, { recursive: true })
133
+ writeFileSync(pluginPath, JSON.stringify(this.plugins, null, 4))
134
+ }
135
+
136
+ /**
137
+ * The node update function, It will do things below
138
+ * * Send system info to cluster server
139
+ */
140
+ private update = () => {
141
+ this.analysis.forEach(x => x.update(this))
142
+ }
143
+
144
+ private loadPlugins = () => {
145
+ const f = path.join(os.homedir(), DATA_FOLDER)
146
+ const pluginPath = path.join(f, 'plugin.json')
147
+ if(!existsSync(f)) mkdirSync(f, { recursive: true })
148
+ if(!existsSync(pluginPath)){
149
+ writeFileSync(pluginPath, JSON.stringify(this.plugins, null, 4))
150
+ }else{
151
+ this.plugins = JSON.parse(readFileSync(pluginPath).toString())
152
+ }
153
+ }
154
+
155
+ private get_pem = ():Promise<[string, string]> => {
156
+ return new Promise<[string, string]>((resolve) => {
157
+ const pemFolder = path.join(os.homedir(), DATA_FOLDER, 'pem')
158
+ if(!existsSync(pemFolder)) mkdirSync(pemFolder)
159
+ const clientKey = path.join(pemFolder, "client_clientkey.pem")
160
+ const certificate = path.join(pemFolder, "client_certificate.pem")
161
+ if(!existsSync(clientKey) || !existsSync(certificate)){
162
+ pem.createCertificate({selfSigned: true}, (err, keys) => {
163
+ writeFileSync(clientKey, keys.clientKey, { encoding: 'utf8' })
164
+ writeFileSync(certificate, keys.certificate, { encoding: 'utf8' })
165
+ resolve([keys.clientKey, keys.certificate])
166
+ })
167
+ }else{
168
+ resolve([readFileSync(clientKey, 'utf8').toString(), readFileSync(certificate, 'utf8').toString()])
169
+ }
170
+ })
171
+ }
172
+
173
+ public static workerPath = (filename:string = "worker", extension:string = ".exe") => {
174
+ // @ts-ignore
175
+ const isExe = process.pkg?.entrypoint != undefined
176
+ const exe = process.platform == 'win32' ? filename + extension : filename
177
+ let workerExe = ""
178
+ let p = 0
179
+ if(isExe && path.basename(process.execPath) == (process.platform ? "app.exe" : 'app')) { // Node build
180
+ workerExe = path.join(process.execPath, "..", "bin", exe)
181
+ p = 1
182
+ }
183
+ else if(
184
+ (process.mainModule && process.mainModule.filename.indexOf('app.asar') !== -1) ||
185
+ process.argv.filter(a => a.indexOf('app.asar') !== -1).length > 0
186
+ ) { // Electron package
187
+ workerExe = path.join("bin", exe)
188
+ p = 2
189
+ }
190
+ else if (process.env.NODE_ENV === 'development'){
191
+ workerExe = path.join(process.cwd(), "bin", exe)
192
+ p = 3
193
+ }
194
+ else{ // Node un-build
195
+ workerExe = Client.isTypescript() ? path.join(__dirname, "bin", exe) : path.join(__dirname, "..", "bin", exe)
196
+ p = 4
197
+ }
198
+ return workerExe
199
+ }
200
+
201
+ static isTypescript = ():boolean => {
202
+ // if this file is typescript, we are running typescript :D
203
+ // this is the best check, but fails when actionhero is compiled to js though...
204
+ const extension = path.extname(__filename);
205
+ if (extension === ".ts") {
206
+ return true;
207
+ }
208
+
209
+ // are we running via a ts-node/ts-node-dev shim?
210
+ const lastArg = process.execArgv[process.execArgv.length - 1];
211
+ if (lastArg && path.parse(lastArg).name.indexOf("ts-node") > 0) {
212
+ return true;
213
+ }
214
+
215
+ try {
216
+ /**
217
+ * Are we running in typescript at the moment?
218
+ * see https://github.com/TypeStrong/ts-node/pull/858 for more details
219
+ */
220
+ return process[Symbol.for("ts-node.register.instance")] ||
221
+ (process.env.NODE_ENV === "test" &&
222
+ process.env.ACTIONHERO_TEST_FILE_EXTENSION !== "js")
223
+ ? true
224
+ : false;
225
+ } catch (error) {
226
+ console.error(error);
227
+ return false;
228
+ }
229
+ }
230
+ }