@abtnode/core 1.17.8-beta-20260108-224855-28496abb → 1.17.8-beta-20260111-112953-aed5ff39

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 (66) hide show
  1. package/lib/api/team/access-key-manager.js +104 -0
  2. package/lib/api/team/invitation-manager.js +461 -0
  3. package/lib/api/team/notification-manager.js +189 -0
  4. package/lib/api/team/oauth-manager.js +60 -0
  5. package/lib/api/team/org-crud-manager.js +202 -0
  6. package/lib/api/team/org-manager.js +56 -0
  7. package/lib/api/team/org-member-manager.js +403 -0
  8. package/lib/api/team/org-query-manager.js +126 -0
  9. package/lib/api/team/org-resource-manager.js +186 -0
  10. package/lib/api/team/passport-manager.js +670 -0
  11. package/lib/api/team/rbac-manager.js +335 -0
  12. package/lib/api/team/session-manager.js +540 -0
  13. package/lib/api/team/store-manager.js +198 -0
  14. package/lib/api/team/tag-manager.js +230 -0
  15. package/lib/api/team/user-auth-manager.js +132 -0
  16. package/lib/api/team/user-manager.js +78 -0
  17. package/lib/api/team/user-query-manager.js +299 -0
  18. package/lib/api/team/user-social-manager.js +354 -0
  19. package/lib/api/team/user-update-manager.js +224 -0
  20. package/lib/api/team/verify-code-manager.js +161 -0
  21. package/lib/api/team.js +439 -3287
  22. package/lib/blocklet/manager/disk/auth-manager.js +68 -0
  23. package/lib/blocklet/manager/disk/backup-manager.js +288 -0
  24. package/lib/blocklet/manager/disk/cleanup-manager.js +157 -0
  25. package/lib/blocklet/manager/disk/component-manager.js +83 -0
  26. package/lib/blocklet/manager/disk/config-manager.js +191 -0
  27. package/lib/blocklet/manager/disk/controller-manager.js +64 -0
  28. package/lib/blocklet/manager/disk/delete-reset-manager.js +328 -0
  29. package/lib/blocklet/manager/disk/download-manager.js +96 -0
  30. package/lib/blocklet/manager/disk/env-config-manager.js +311 -0
  31. package/lib/blocklet/manager/disk/federated-manager.js +651 -0
  32. package/lib/blocklet/manager/disk/hook-manager.js +124 -0
  33. package/lib/blocklet/manager/disk/install-component-manager.js +95 -0
  34. package/lib/blocklet/manager/disk/install-core-manager.js +448 -0
  35. package/lib/blocklet/manager/disk/install-download-manager.js +313 -0
  36. package/lib/blocklet/manager/disk/install-manager.js +36 -0
  37. package/lib/blocklet/manager/disk/install-upgrade-manager.js +340 -0
  38. package/lib/blocklet/manager/disk/job-manager.js +467 -0
  39. package/lib/blocklet/manager/disk/lifecycle-manager.js +26 -0
  40. package/lib/blocklet/manager/disk/notification-manager.js +343 -0
  41. package/lib/blocklet/manager/disk/query-manager.js +562 -0
  42. package/lib/blocklet/manager/disk/settings-manager.js +507 -0
  43. package/lib/blocklet/manager/disk/start-manager.js +611 -0
  44. package/lib/blocklet/manager/disk/stop-restart-manager.js +292 -0
  45. package/lib/blocklet/manager/disk/update-manager.js +153 -0
  46. package/lib/blocklet/manager/disk.js +669 -5796
  47. package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +5 -0
  48. package/lib/blocklet/manager/lock.js +18 -0
  49. package/lib/event/index.js +28 -24
  50. package/lib/router/helper.js +5 -1
  51. package/lib/util/blocklet/app-utils.js +192 -0
  52. package/lib/util/blocklet/blocklet-loader.js +258 -0
  53. package/lib/util/blocklet/config-manager.js +232 -0
  54. package/lib/util/blocklet/did-document.js +240 -0
  55. package/lib/util/blocklet/environment.js +555 -0
  56. package/lib/util/blocklet/health-check.js +449 -0
  57. package/lib/util/blocklet/install-utils.js +365 -0
  58. package/lib/util/blocklet/logo.js +57 -0
  59. package/lib/util/blocklet/meta-utils.js +269 -0
  60. package/lib/util/blocklet/port-manager.js +141 -0
  61. package/lib/util/blocklet/process-manager.js +504 -0
  62. package/lib/util/blocklet/runtime-info.js +105 -0
  63. package/lib/util/blocklet/validation.js +418 -0
  64. package/lib/util/blocklet.js +98 -3066
  65. package/lib/util/wallet-app-notification.js +40 -0
  66. package/package.json +22 -22
@@ -0,0 +1,449 @@
1
+ /* eslint-disable no-await-in-loop */
2
+ /**
3
+ * Health Check Module
4
+ *
5
+ * Functions for checking blocklet process health status
6
+ * Extracted from blocklet.js for better modularity
7
+ */
8
+
9
+ const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet:health-check');
10
+ const sleep = require('@abtnode/util/lib/sleep');
11
+ const ensureEndpointHealthy = require('@abtnode/util/lib/ensure-endpoint-healthy');
12
+ const promiseSpawn = require('@abtnode/util/lib/promise-spawn');
13
+ const { forEachBlocklet, hasStartEngine } = require('@blocklet/meta/lib/util');
14
+ const { getBlockletEngine } = require('@blocklet/meta/lib/engine');
15
+
16
+ const {
17
+ BlockletStatus,
18
+ BlockletGroup,
19
+ BLOCKLET_MODES,
20
+ BLOCKLET_INTERFACE_TYPE_WEB,
21
+ BLOCKLET_INTERFACE_TYPE_DOCKER,
22
+ } = require('@blocklet/constant');
23
+
24
+ const { getProcessInfo, shouldSkipComponent } = require('./process-manager');
25
+
26
+ /**
27
+ * Get actual listening port from Docker container or process
28
+ * @param {string} processId - PM2 process ID
29
+ * @param {object} blocklet - Blocklet object with meta and env
30
+ * @returns {Promise<number|null>} Actual port number or null if not found
31
+ */
32
+ const getActualListeningPort = async (processId, blocklet) => {
33
+ try {
34
+ const info = await getProcessInfo(processId, { timeout: 3_000 });
35
+ const dockerName = info.pm2_env?.env?.dockerName;
36
+
37
+ if (dockerName) {
38
+ // For Docker containers, get actual port from docker inspect
39
+ try {
40
+ // Get port mapping from docker inspect
41
+ const inspectCmd = `docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{$p}} {{end}}' ${dockerName}`;
42
+ const portMappings = await promiseSpawn(inspectCmd, { mute: true });
43
+
44
+ if (portMappings) {
45
+ const ports = portMappings.trim().split(/\s+/).filter(Boolean);
46
+ for (const portMapping of ports) {
47
+ const port = parseInt(portMapping.split('/')[0], 10);
48
+ if (port && !Number.isNaN(port)) {
49
+ try {
50
+ const portCmd = `docker port ${dockerName} ${portMapping}`;
51
+ const hostPortOutput = await promiseSpawn(portCmd, { mute: true });
52
+ const match = hostPortOutput.match(/:(\d+)$/);
53
+ if (match) {
54
+ const actualPort = parseInt(match[1], 10);
55
+ if (actualPort && !Number.isNaN(actualPort)) {
56
+ logger.info('Got actual Docker port from container', {
57
+ processId,
58
+ dockerName,
59
+ containerPort: port,
60
+ hostPort: actualPort,
61
+ });
62
+ return actualPort;
63
+ }
64
+ }
65
+ } catch (err) {
66
+ logger.debug('Failed to get port from docker port command', { error: err.message });
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ // Fallback: try to get from NetworkSettings.Ports directly
73
+ const inspectPortsCmd = `docker inspect --format='{{json .NetworkSettings.Ports}}' ${dockerName}`;
74
+ const portsJson = await promiseSpawn(inspectPortsCmd, { mute: true });
75
+ if (portsJson) {
76
+ const portsObj = JSON.parse(portsJson);
77
+ // Find the primary port (usually BLOCKLET_PORT)
78
+ const webInterface = (blocklet?.meta?.interfaces || []).find(
79
+ (x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB || x.type === BLOCKLET_INTERFACE_TYPE_DOCKER
80
+ );
81
+ const expectedContainerPort = webInterface?.containerPort || webInterface?.port;
82
+
83
+ if (expectedContainerPort) {
84
+ const portKey = `${expectedContainerPort}/tcp`;
85
+ if (portsObj[portKey] && portsObj[portKey][0]) {
86
+ const hostPort = parseInt(portsObj[portKey][0].HostPort, 10);
87
+ if (hostPort && !Number.isNaN(hostPort)) {
88
+ logger.info('Got actual Docker port from NetworkSettings', {
89
+ processId,
90
+ dockerName,
91
+ containerPort: expectedContainerPort,
92
+ hostPort,
93
+ });
94
+ return hostPort;
95
+ }
96
+ }
97
+ }
98
+ }
99
+ } catch (error) {
100
+ logger.debug('Failed to get Docker port mapping', { error: error.message, processId, dockerName });
101
+ }
102
+ }
103
+
104
+ return null;
105
+ } catch (error) {
106
+ logger.debug('Failed to get actual listening port', { error: error.message, processId });
107
+ return null;
108
+ }
109
+ };
110
+
111
+ /**
112
+ * Get healthy check timeout configuration
113
+ * @param {object} blocklet - Blocklet object
114
+ * @param {object} options - Options
115
+ * @param {boolean} options.checkHealthImmediately - Whether to check immediately
116
+ * @param {Array} options.componentDids - Component DIDs to filter
117
+ * @returns {{ startTimeout: number, minConsecutiveTime: number }}
118
+ */
119
+ const getHealthyCheckTimeout = (blocklet, { checkHealthImmediately, componentDids } = {}) => {
120
+ let minConsecutiveTime = 3000;
121
+ if (process.env.NODE_ENV === 'test' && process.env.ABT_NODE_TEST_MIN_CONSECUTIVE_TIME !== undefined) {
122
+ minConsecutiveTime = +process.env.ABT_NODE_TEST_MIN_CONSECUTIVE_TIME;
123
+ } else if (checkHealthImmediately) {
124
+ minConsecutiveTime = 3000;
125
+ }
126
+
127
+ if (process.env.BLOCKLET_START_TIMEOUT) {
128
+ return {
129
+ startTimeout: +process.env.BLOCKLET_START_TIMEOUT * 1000,
130
+ minConsecutiveTime,
131
+ };
132
+ }
133
+ if (blocklet.mode === BLOCKLET_MODES.DEVELOPMENT) {
134
+ return {
135
+ startTimeout: 3600 * 1000,
136
+ minConsecutiveTime: 3000,
137
+ };
138
+ }
139
+
140
+ const children = componentDids?.length
141
+ ? blocklet.children.filter((child) => componentDids.includes(child.meta.did))
142
+ : blocklet.children;
143
+
144
+ // Let's wait for at least 1 minute for the blocklet to go live
145
+ let startTimeout =
146
+ Math.max(blocklet.meta?.timeout?.start || 60, ...(children || []).map((child) => child.meta?.timeout?.start || 0)) *
147
+ 1000;
148
+
149
+ if (process.env.NODE_ENV === 'test') {
150
+ startTimeout = 10 * 1000;
151
+ }
152
+
153
+ return {
154
+ startTimeout,
155
+ minConsecutiveTime,
156
+ };
157
+ };
158
+
159
+ /**
160
+ * Internal function to check process health
161
+ * @param {object} blocklet - Blocklet component
162
+ * @param {object} options - Check options
163
+ * @param {number} options.minConsecutiveTime - Minimum consecutive healthy time
164
+ * @param {number} options.timeout - Timeout in ms
165
+ * @param {boolean} options.logToTerminal - Whether to log to terminal
166
+ * @param {boolean} options.isGreen - Whether checking green deployment
167
+ * @param {string} options.appDid - App DID for logging
168
+ * @param {Function} findInterfacePortByName - Function to find port by interface name
169
+ * @returns {Promise<void>}
170
+ */
171
+ const _checkProcessHealthy = async (
172
+ blocklet,
173
+ { minConsecutiveTime, timeout, logToTerminal, isGreen = false, appDid, findInterfacePortByName }
174
+ ) => {
175
+ const { meta, ports, greenPorts, env } = blocklet;
176
+ const { name } = meta;
177
+ const processId = isGreen ? `${env.processId}-green` : env.processId;
178
+
179
+ const webInterface = (meta.interfaces || []).find((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
180
+ const dockerInterface = (meta.interfaces || []).find((x) => x.type === BLOCKLET_INTERFACE_TYPE_DOCKER);
181
+
182
+ if (!webInterface && !dockerInterface) {
183
+ // TODO: how do we check healthy for service interfaces
184
+ throw new Error(`Blocklet ${name} does not have any web interface`);
185
+ }
186
+
187
+ try {
188
+ // ensure pm2 status is 'online'
189
+ const getStatus = async () => {
190
+ try {
191
+ const info = await getProcessInfo(processId, { timeout: 3_000 });
192
+ return { status: info.pm2_env.status, envPort: info.pm2_env.BLOCKLET_PORT };
193
+ } catch (err) {
194
+ logger.error('blocklet checkStart error', { appDid, error: err, processId, name });
195
+ return { status: '', envPort: null };
196
+ }
197
+ };
198
+
199
+ // eslint-disable-next-line prefer-const
200
+ let { status, envPort } = await getStatus();
201
+
202
+ for (let i = 0; i < 20 && status !== 'online'; i++) {
203
+ const t = process.env.NODE_ENV !== 'test' ? 500 : 30;
204
+ await sleep(t);
205
+ ({ status, envPort } = await getStatus());
206
+ }
207
+
208
+ if (status !== 'online') {
209
+ throw new Error('process not start within 10s');
210
+ }
211
+
212
+ // Get actual listening port from Docker container or process
213
+ // This avoids using stale port after port refresh
214
+ const actualPort = await getActualListeningPort(processId, blocklet);
215
+
216
+ // Port priority: actual port > pm2 env port > database port
217
+ const port =
218
+ actualPort ||
219
+ envPort ||
220
+ findInterfacePortByName({ meta, ports: isGreen ? greenPorts : ports }, (webInterface || dockerInterface).name);
221
+
222
+ if (logToTerminal) {
223
+ logger.info(
224
+ // eslint-disable-next-line no-nested-ternary
225
+ `Checking endpoint healthy for ${meta.title}, port: ${port}${actualPort ? ' (actual)' : envPort ? ' (from pm2 env)' : ' (from db)'}, minConsecutiveTime: ${
226
+ minConsecutiveTime / 1000
227
+ }s, timeout: ${timeout / 1000}s`
228
+ );
229
+ }
230
+
231
+ if (
232
+ actualPort &&
233
+ actualPort !== envPort &&
234
+ actualPort !==
235
+ (isGreen
236
+ ? greenPorts?.[webInterface?.port || dockerInterface?.port]
237
+ : ports?.[webInterface?.port || dockerInterface?.port])
238
+ ) {
239
+ logger.info('Port mismatch detected, using actual port for health check', {
240
+ processId,
241
+ appDid,
242
+ actualPort,
243
+ envPort,
244
+ dbPort: isGreen
245
+ ? greenPorts?.[webInterface?.port || dockerInterface?.port]
246
+ : ports?.[webInterface?.port || dockerInterface?.port],
247
+ });
248
+ }
249
+
250
+ try {
251
+ await ensureEndpointHealthy({
252
+ port,
253
+ protocol: webInterface ? 'http' : 'tcp',
254
+ minConsecutiveTime,
255
+ timeout,
256
+ doConsecutiveCheck: blocklet.mode !== BLOCKLET_MODES.DEVELOPMENT,
257
+ waitTCP: !webInterface,
258
+ shouldAbort: async () => {
259
+ // Check if pm2 process exists and is online
260
+ try {
261
+ const info = await getProcessInfo(processId, { timeout: 3_000 });
262
+ const currentStatus = info.pm2_env.status;
263
+ if (currentStatus !== 'online') {
264
+ throw new Error(`pm2 process ${processId} status is ${currentStatus}, not online`);
265
+ }
266
+ } catch (err) {
267
+ // If process doesn't exist or has error, abort immediately
268
+ logger.error('pm2 process check failed in shouldAbort', { appDid, error: err, processId, name });
269
+ const isProcessNotExist =
270
+ err.message &&
271
+ (err.message.includes('not found') ||
272
+ err.message.includes('does not exist') ||
273
+ err.message.includes('not running'));
274
+ if (isProcessNotExist) {
275
+ throw new Error(`pm2 process ${processId} (${name}) died or does not exist: ${err.message}`);
276
+ }
277
+ throw new Error(`pm2 process ${processId} (${name}) check failed: ${err.message}`);
278
+ }
279
+ },
280
+ });
281
+ } catch (error) {
282
+ const isProcessDead =
283
+ error.message &&
284
+ (error.message.includes('pm2 process') ||
285
+ error.message.includes('died') ||
286
+ error.message.includes('does not exist'));
287
+ if (isProcessDead) {
288
+ logger.error('blocklet process died during health check', {
289
+ appDid,
290
+ processId,
291
+ name,
292
+ port,
293
+ error: error.message,
294
+ });
295
+ throw error;
296
+ }
297
+ logger.error('ensure endpoint healthy failed', {
298
+ appDid,
299
+ port,
300
+ minConsecutiveTime,
301
+ timeout,
302
+ error: error.message,
303
+ });
304
+ throw error;
305
+ }
306
+ } catch (error) {
307
+ logger.error('start blocklet failed', { processId, name });
308
+ throw error;
309
+ }
310
+ };
311
+
312
+ /**
313
+ * Check if all blocklet components are healthy
314
+ * @param {object} blocklet - Root blocklet
315
+ * @param {object} options - Options
316
+ * @param {number} options.minConsecutiveTime - Minimum consecutive healthy time
317
+ * @param {number} options.timeout - Timeout in ms
318
+ * @param {Array} options.componentDids - Component DIDs to check
319
+ * @param {Function} options.setBlockletRunning - Callback when blocklet becomes running
320
+ * @param {boolean} options.isGreen - Whether checking green deployment
321
+ * @param {string} options.appDid - App DID for logging
322
+ * @param {Function} options.findInterfacePortByName - Function to find port by interface name
323
+ * @returns {Promise<void>}
324
+ */
325
+ const checkBlockletProcessHealthy = async (
326
+ blocklet,
327
+ {
328
+ minConsecutiveTime,
329
+ timeout,
330
+ componentDids,
331
+ setBlockletRunning,
332
+ isGreen = false,
333
+ appDid,
334
+ findInterfacePortByName,
335
+ } = {}
336
+ ) => {
337
+ await forEachBlocklet(
338
+ blocklet,
339
+ async (b) => {
340
+ if (b.meta.group === BlockletGroup.gateway) {
341
+ return;
342
+ }
343
+
344
+ // components that relies on another engine component should not be checked
345
+ const engine = getBlockletEngine(b.meta);
346
+ if (engine.interpreter === 'blocklet') {
347
+ return;
348
+ }
349
+
350
+ if (!hasStartEngine(b.meta)) {
351
+ return;
352
+ }
353
+
354
+ if (shouldSkipComponent(b.meta.did, componentDids)) {
355
+ logger.info('skip check component healthy', { processId: b.env.processId });
356
+ return;
357
+ }
358
+
359
+ const logToTerminal = [blocklet.mode, b.mode].includes(BLOCKLET_MODES.DEVELOPMENT);
360
+
361
+ const startedAt = Date.now();
362
+
363
+ await _checkProcessHealthy(b, {
364
+ minConsecutiveTime,
365
+ timeout,
366
+ logToTerminal,
367
+ isGreen,
368
+ appDid,
369
+ findInterfacePortByName,
370
+ });
371
+
372
+ logger.info('done check component healthy', { processId: b.env.processId, time: Date.now() - startedAt });
373
+
374
+ if (setBlockletRunning) {
375
+ try {
376
+ await setBlockletRunning(b.meta.did);
377
+ } catch (error) {
378
+ logger.error(`Failed to set blocklet as running for DID: ${b.meta.name || b.meta.did}`, { error });
379
+ }
380
+ }
381
+ },
382
+ { parallel: true }
383
+ );
384
+ };
385
+
386
+ /**
387
+ * Check if a blocklet should have health check
388
+ * @param {object} blocklet - Blocklet component
389
+ * @returns {boolean}
390
+ */
391
+ const shouldCheckHealthy = (blocklet) => {
392
+ if (blocklet.meta.group === BlockletGroup.gateway) {
393
+ return false;
394
+ }
395
+
396
+ // components that relies on another engine component should not be checked
397
+ const engine = getBlockletEngine(blocklet.meta);
398
+ if (engine.interpreter === 'blocklet') {
399
+ return false;
400
+ }
401
+
402
+ return hasStartEngine(blocklet.meta);
403
+ };
404
+
405
+ /**
406
+ * Check if blocklet port is healthy
407
+ * @param {object} blocklet - Blocklet object
408
+ * @param {object} options - Options
409
+ * @param {number} options.minConsecutiveTime - Minimum consecutive healthy time (default 3000ms)
410
+ * @param {number} options.timeout - Timeout in ms (default 10s)
411
+ * @returns {Promise<void>}
412
+ */
413
+ const isBlockletPortHealthy = async (blocklet, { minConsecutiveTime = 3000, timeout = 10 * 1000 } = {}) => {
414
+ if (!blocklet) {
415
+ return;
416
+ }
417
+ const { environments } = blocklet;
418
+ const webInterface = (blocklet.meta?.interfaces || []).find((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
419
+ const dockerInterface = (blocklet.meta?.interfaces || []).find((x) => x.type === BLOCKLET_INTERFACE_TYPE_DOCKER);
420
+ const key = webInterface?.port || dockerInterface?.port || 'BLOCKLET_PORT';
421
+
422
+ let port = blocklet.greenStatus === BlockletStatus.running ? blocklet.greenPorts?.[key] : blocklet.ports?.[key];
423
+
424
+ if (!port) {
425
+ const keyPort = webInterface?.port || dockerInterface?.port;
426
+ port = environments?.find((e) => e.key === keyPort)?.value;
427
+ }
428
+
429
+ if (!port) {
430
+ return;
431
+ }
432
+
433
+ await ensureEndpointHealthy({
434
+ port,
435
+ protocol: webInterface ? 'http' : 'tcp',
436
+ minConsecutiveTime,
437
+ timeout,
438
+ doConsecutiveCheck: false,
439
+ });
440
+ };
441
+
442
+ module.exports = {
443
+ getActualListeningPort,
444
+ getHealthyCheckTimeout,
445
+ _checkProcessHealthy,
446
+ checkBlockletProcessHealthy,
447
+ shouldCheckHealthy,
448
+ isBlockletPortHealthy,
449
+ };