@abtnode/core 1.16.13-beta-0656f92f → 1.16.13-beta-2eb54cbc

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.
@@ -116,6 +116,7 @@ const {
116
116
  getBlockletStatus,
117
117
  shouldSkipComponent,
118
118
  exceedRedemptionPeriod,
119
+ ensureAppPortsNotOccupied,
119
120
  } = require('../../util/blocklet');
120
121
  const states = require('../../states');
121
122
  const BaseBlockletManager = require('./base');
@@ -457,40 +458,66 @@ class BlockletManager extends BaseBlockletManager {
457
458
  return { did, isInstalled: !!blocklet, isRunning };
458
459
  }
459
460
 
460
- async start({ did, throwOnError, checkHealthImmediately = false, e2eMode = false, componentDids }, context) {
461
+ async start(
462
+ { did, throwOnError, checkHealthImmediately = false, e2eMode = false, componentDids: inputComponentDids, atomic },
463
+ context
464
+ ) {
465
+ const blocklet = await this.ensureBlocklet(did, { e2eMode });
466
+
467
+ if (atomic || !blocklet.structVersion) {
468
+ return this._start(
469
+ { did, throwOnError, checkHealthImmediately, e2eMode, componentDids: inputComponentDids },
470
+ context
471
+ );
472
+ }
473
+
474
+ const componentDids = inputComponentDids?.length ? inputComponentDids : blocklet.children.map((x) => x.meta.did);
475
+
476
+ const tasks = componentDids.map((componentDid) =>
477
+ this._start({ did, throwOnError, checkHealthImmediately, e2eMode, componentDids: [componentDid] }, context)
478
+ );
479
+
480
+ return Promise.any(tasks).catch((err) => {
481
+ throw new Error(err.errors.join(', '));
482
+ });
483
+ }
484
+
485
+ async _start(
486
+ { blocklet: inputBlocklet, did, throwOnError, checkHealthImmediately = false, e2eMode = false, componentDids },
487
+ context
488
+ ) {
461
489
  logger.info('start blocklet', { did, componentDids, throwOnError, checkHealthImmediately, e2eMode });
462
490
  // should check blocklet integrity
463
- const blocklet = await this.ensureBlocklet(did, { e2eMode });
491
+ const blocklet1 = inputBlocklet || (await this.ensureBlocklet(did, { e2eMode }));
464
492
 
465
- await this.checkControllerStatus(blocklet, 'start');
493
+ await this.checkControllerStatus(blocklet1, 'start');
494
+
495
+ // validate requirement and engine
496
+ await validateBlocklet(blocklet1);
497
+ await validateBlockletChainInfo(blocklet1);
498
+
499
+ if (!hasRunnableComponent(blocklet1)) {
500
+ throw new Error('No runnable component found');
501
+ }
502
+
503
+ // check required config
504
+ const missingProps = getAppMissingConfigs(blocklet1);
505
+ if (missingProps.length) {
506
+ throw new Error(
507
+ `Missing required configuration to start the blocklet: ${missingProps.map((x) => x.key).join(',')}`
508
+ );
509
+ }
466
510
 
467
511
  try {
468
512
  // blocklet may be manually stopped durning starting
469
513
  // so error message would not be sent if blocklet is stopped
470
514
  // so we need update status first
471
- await states.blocklet.setBlockletStatus(did, BlockletStatus.starting, { componentDids });
472
- blocklet.status = BlockletStatus.starting;
515
+ const doc1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.starting, { componentDids });
516
+ blocklet1.status = BlockletStatus.starting;
517
+ this.emit(BlockletEvents.statusChange, doc1);
473
518
 
474
- // validate requirement and engine
475
- await validateBlocklet(blocklet);
476
- await validateBlockletChainInfo(blocklet);
477
-
478
- if (!hasRunnableComponent(blocklet)) {
479
- throw new Error('No runnable component found');
480
- }
481
-
482
- // check required config
483
- const missingProps = getAppMissingConfigs(blocklet);
484
- if (missingProps.length) {
485
- throw new Error(
486
- `Missing required configuration to start the blocklet: ${missingProps.map((x) => x.key).join(',')}`
487
- );
488
- }
489
-
490
- this.emit(BlockletEvents.statusChange, blocklet);
491
-
492
- if (blocklet.mode === BLOCKLET_MODES.DEVELOPMENT) {
493
- const { logsDir } = blocklet.env;
519
+ if (blocklet1.mode === BLOCKLET_MODES.DEVELOPMENT) {
520
+ const { logsDir } = blocklet1.env;
494
521
 
495
522
  try {
496
523
  fs.removeSync(logsDir);
@@ -501,6 +528,8 @@ class BlockletManager extends BaseBlockletManager {
501
528
  }
502
529
  }
503
530
 
531
+ const blocklet = await ensureAppPortsNotOccupied({ blocklet: blocklet1, componentDids, states, manager: this });
532
+
504
533
  const getHookFn =
505
534
  (hookName) =>
506
535
  (b, { env }) =>
@@ -554,8 +583,8 @@ class BlockletManager extends BaseBlockletManager {
554
583
  }
555
584
 
556
585
  const error = Array.isArray(err) ? err[0] : err;
557
- logger.error('Failed to start blocklet', { error, did, name: blocklet.meta.name });
558
- const description = `Start blocklet ${blocklet.meta.title} failed with error: ${error.message}`;
586
+ logger.error('Failed to start blocklet', { error, did, title: blocklet1.meta.title });
587
+ const description = `Start blocklet ${blocklet1.meta.title} failed with error: ${error.message}`;
559
588
  this._createNotification(did, {
560
589
  title: 'Start Blocklet Failed',
561
590
  description,
@@ -567,6 +596,7 @@ class BlockletManager extends BaseBlockletManager {
567
596
  await this.deleteProcess({ did, componentDids });
568
597
  const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, { componentDids });
569
598
  this.emit(BlockletEvents.startFailed, { ...res, error: { message: error.message } });
599
+ this.emit(BlockletEvents.statusChange, { ...res, error: { message: error.message } });
570
600
 
571
601
  if (throwOnError) {
572
602
  throw new Error(description);
@@ -583,9 +613,9 @@ class BlockletManager extends BaseBlockletManager {
583
613
  const { processId } = blocklet.env;
584
614
 
585
615
  if (updateStatus) {
586
- await states.blocklet.setBlockletStatus(did, BlockletStatus.stopping, { componentDids });
616
+ const doc = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopping, { componentDids });
587
617
  blocklet.status = BlockletStatus.stopping;
588
- this.emit(BlockletEvents.statusChange, blocklet);
618
+ this.emit(BlockletEvents.statusChange, doc);
589
619
  }
590
620
 
591
621
  try {
@@ -1992,6 +2022,7 @@ class BlockletManager extends BaseBlockletManager {
1992
2022
  components: getComponentsInternalInfo(res),
1993
2023
  });
1994
2024
 
2025
+ this.emit(BlockletEvents.statusChange, res);
1995
2026
  this.emit(BlockletEvents.started, res);
1996
2027
  logger.info('blocklet healthy', { did, name, time: Date.now() - startedAt });
1997
2028
  } catch (error) {
@@ -2004,7 +2035,7 @@ class BlockletManager extends BaseBlockletManager {
2004
2035
  logger.error('check blocklet if started failed', { did, name, context, timeout, error });
2005
2036
 
2006
2037
  await this.deleteProcess({ did, componentDids }, context);
2007
- await states.blocklet.setBlockletStatus(did, BlockletStatus.error, { componentDids });
2038
+ const doc = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, { componentDids });
2008
2039
 
2009
2040
  this._createNotification(did, {
2010
2041
  title: 'Blocklet Start Failed',
@@ -2015,6 +2046,7 @@ class BlockletManager extends BaseBlockletManager {
2015
2046
  });
2016
2047
 
2017
2048
  this.emit(BlockletEvents.startFailed, { meta, error });
2049
+ this.emit(BlockletEvents.statusChange, { ...doc, error });
2018
2050
 
2019
2051
  if (throwOnError) {
2020
2052
  throw error;
package/lib/index.js CHANGED
@@ -411,6 +411,9 @@ function ABTNode(options) {
411
411
  return states.auditLog.findPaginated.call(states.auditLog, params);
412
412
  },
413
413
 
414
+ // Insights
415
+ getTrafficInsights: states.trafficInsight.findPaginated.bind(states.trafficInsight),
416
+
414
417
  // Routing
415
418
  routerManager,
416
419
  addRoutingSite,
@@ -4,6 +4,7 @@ const fs = require('fs-extra');
4
4
  const path = require('path');
5
5
  const tar = require('tar');
6
6
  const UUID = require('uuid');
7
+ const dayjs = require('@abtnode/util/lib/dayjs');
7
8
  const isUrl = require('is-url');
8
9
  const get = require('lodash/get');
9
10
  const cloneDeep = require('lodash/cloneDeep');
@@ -18,6 +19,7 @@ const downloadFile = require('@abtnode/util/lib/download-file');
18
19
  const { updateBlockletDocument } = require('@abtnode/util/lib/did-document');
19
20
  const getBlockletInfo = require('@blocklet/meta/lib/info');
20
21
  const { forEachBlockletSync } = require('@blocklet/meta/lib/util');
22
+ const { processLogByDate } = require('@abtnode/analytics');
21
23
  const {
22
24
  DOMAIN_FOR_DEFAULT_SITE,
23
25
  DOMAIN_FOR_IP_SITE_REGEXP,
@@ -37,8 +39,10 @@ const {
37
39
  WELLKNOWN_ACME_CHALLENGE_PREFIX,
38
40
  WELLKNOWN_DID_RESOLVER_PREFIX,
39
41
  WELLKNOWN_PING_PREFIX,
42
+ WELLKNOWN_ANALYTICS_PREFIX,
40
43
  LOG_RETAIN_IN_DAYS,
41
44
  EVENTS,
45
+ DEFAULT_IP_DOMAIN,
42
46
  } = require('@abtnode/constant');
43
47
  const {
44
48
  BLOCKLET_DYNAMIC_PATH_PREFIX,
@@ -51,8 +55,9 @@ const {
51
55
  BLOCKLET_MODES,
52
56
  } = require('@blocklet/constant');
53
57
 
54
- // eslint-disable-next-line global-require
55
- const logger = require('@abtnode/logger')(`${require('../../package.json').name}:router:helper`);
58
+ const pkg = require('../../package.json');
59
+ // eslint-disable-next-line
60
+ const logger = require('@abtnode/logger')(`${pkg.name}:router:helper`);
56
61
  const {
57
62
  getProviderFromNodeInfo,
58
63
  getHttpsCertInfo,
@@ -69,39 +74,79 @@ const Router = require('./index');
69
74
  const states = require('../states');
70
75
  const { getBlockletDomainGroupName, getDidFromDomainGroupName } = require('../util/router');
71
76
  const { getBlockletKnownAs, getBlockletDidDomainList } = require('../util/blocklet');
77
+ const { toCamelCase } = require('../util/index');
78
+ const { get: getIp } = require('../util/ip');
72
79
 
73
80
  const isServiceFeDevelopment = process.env.ABT_NODE_SERVICE_FE_PORT;
74
81
 
75
82
  const hasRuleByPrefix = (site, value) => site.rules.find((x) => x.isProtected && get(x, 'from.pathPrefix') === value);
76
83
 
84
+ const pingWellknownRule = {
85
+ isProtected: true,
86
+ from: { pathPrefix: WELLKNOWN_PING_PREFIX },
87
+ to: {
88
+ type: ROUTING_RULE_TYPES.DIRECT_RESPONSE,
89
+ response: {
90
+ status: 200,
91
+ contentType: 'application/javascript',
92
+ body: "'pong'",
93
+ },
94
+ },
95
+ };
96
+
97
+ const analyticsWellknownRule = {
98
+ isProtected: true,
99
+ from: { pathPrefix: WELLKNOWN_ANALYTICS_PREFIX },
100
+ to: {
101
+ type: ROUTING_RULE_TYPES.DIRECT_RESPONSE,
102
+ response: {
103
+ status: 200,
104
+ contentType: 'text/html',
105
+ body: pkg.version,
106
+ },
107
+ },
108
+ };
109
+
77
110
  /**
78
111
  * replace 888-888-888-888 with accessible ip for domain
79
112
  */
80
- const attachRuntimeDomainAliases = async ({ sites = [], context = {}, node }) => {
113
+ const attachRuntimeDomainAliases = async ({ sites = [], context = {} }) => {
81
114
  if (!sites) {
82
115
  return [];
83
116
  }
84
117
 
85
118
  let ip;
86
119
  const ipRegex = /\d+[-.]\d+[-.]\d+[-.]\d+/;
87
- const match = ipRegex.exec(context.hostname);
120
+ const match = ipRegex.exec(context.hostname || '');
88
121
  if (match) {
89
122
  ip = match[0];
90
- } else if (node) {
91
- const nodeInfo = await node.read();
92
- const nodeIp = await getAccessibleExternalNodeIp(nodeInfo);
123
+ } else {
124
+ const nodeIp = await getAccessibleExternalNodeIp();
93
125
  if (nodeIp) {
94
126
  ip = nodeIp;
95
127
  }
96
128
  }
97
129
 
130
+ if (!ip) {
131
+ const result = await getIp();
132
+ if (result) {
133
+ ip = result.internal;
134
+ }
135
+ }
136
+
137
+ logger.info('attachRuntimeDomainAliases', { ip });
138
+
98
139
  const getDomainAliases = (site) =>
99
140
  (site.domainAliases || []).map((domain) => {
100
141
  if (!domain.value) {
101
142
  return domain;
102
143
  }
103
- if (domain.value.includes(SLOT_FOR_IP_DNS_SITE) && ip) {
104
- domain.value = replaceSlotToIp(domain.value, ip);
144
+ if (ip) {
145
+ if (domain.value.includes(SLOT_FOR_IP_DNS_SITE)) {
146
+ domain.value = replaceSlotToIp(domain.value, ip);
147
+ } else if (domain.value === DEFAULT_IP_DOMAIN) {
148
+ domain.value = `${ip.split('.').join('-')}${domain.value.substring(2)}`;
149
+ }
105
150
  }
106
151
  return domain;
107
152
  });
@@ -629,6 +674,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
629
674
  const blockletState = states.blocklet;
630
675
  const siteState = states.site;
631
676
  const notification = states.notification;
677
+ const trafficInsight = states.trafficInsight;
632
678
 
633
679
  // site level duplication detection
634
680
 
@@ -778,46 +824,12 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
778
824
  to: proxyTarget,
779
825
  };
780
826
 
781
- const pingWellknownRule = {
782
- isProtected: true,
783
- from: { pathPrefix: WELLKNOWN_PING_PREFIX },
784
- to: {
785
- type: ROUTING_RULE_TYPES.DIRECT_RESPONSE,
786
- response: {
787
- status: 200,
788
- contentType: 'application/javascript',
789
- body: "'pong'",
790
- },
791
- port: info.port,
792
- },
793
- };
794
-
795
827
  if (site) {
796
- const didResolverRuleUpdateRes = await upsertSiteRule(
797
- {
798
- site,
799
- rule: didResolverWellknownRule,
800
- },
801
- context
802
- );
803
-
804
- const acmeRuleUpdateRes = await upsertSiteRule(
805
- {
806
- site,
807
- rule: acmeChallengeWellknownRule,
808
- },
809
- context
810
- );
811
-
812
- const pingRuleRes = await upsertSiteRule(
813
- {
814
- site,
815
- rule: pingWellknownRule,
816
- },
817
- context
818
- );
819
-
820
- return didResolverRuleUpdateRes || acmeRuleUpdateRes || pingRuleRes;
828
+ const didResolverRuleUpdateRes = await upsertSiteRule({ site, rule: didResolverWellknownRule }, context);
829
+ const acmeRuleUpdateRes = await upsertSiteRule({ site, rule: acmeChallengeWellknownRule }, context);
830
+ const pingRuleRes = await upsertSiteRule({ site, rule: pingWellknownRule }, context);
831
+ const analyticsRuleRes = await upsertSiteRule({ site, rule: analyticsWellknownRule }, context);
832
+ return didResolverRuleUpdateRes || acmeRuleUpdateRes || pingRuleRes || analyticsRuleRes;
821
833
  }
822
834
 
823
835
  await routerManager.addRoutingSite(
@@ -826,7 +838,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
826
838
  domain: DOMAIN_FOR_INTERNAL_SITE,
827
839
  port: await getWellknownSitePort(),
828
840
  name: NAME_FOR_WELLKNOWN_SITE,
829
- rules: [didResolverWellknownRule, acmeChallengeWellknownRule, pingWellknownRule],
841
+ rules: [didResolverWellknownRule, acmeChallengeWellknownRule, pingWellknownRule, analyticsWellknownRule],
830
842
  isProtected: true,
831
843
  },
832
844
  skipCheckDynamicBlacklist: true,
@@ -893,13 +905,19 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
893
905
 
894
906
  try {
895
907
  const result = await siteState.update({ id: dashboardSite.id }, { $set: { domainAliases } });
896
-
897
908
  updatedResult.push(result);
898
909
  } catch (error) {
899
910
  logger.error('add dashboard domain rule failed', { error });
900
911
  console.error('Add dashboard domain rule failed:', error);
901
912
  }
902
913
 
914
+ try {
915
+ const result = await upsertSiteRule({ site: dashboardSite, rule: analyticsWellknownRule }, context);
916
+ updatedResult.push(result);
917
+ } catch (error) {
918
+ logger.error('add dashboard analytics rule failed', { error });
919
+ }
920
+
903
921
  const defaultRule = sites.find((x) => x.domain === DOMAIN_FOR_DEFAULT_SITE);
904
922
  if (!defaultRule) {
905
923
  try {
@@ -1265,6 +1283,73 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1265
1283
  }
1266
1284
  };
1267
1285
 
1286
+ const analyzeRouterLog = async () => {
1287
+ const info = await nodeState.read();
1288
+ const sites = await getRoutingSites({});
1289
+ const providerName = get(info, 'routing.provider', null);
1290
+ if (!providerName || !providers[providerName]) {
1291
+ logger.warn('No router provider instance found');
1292
+ return;
1293
+ }
1294
+
1295
+ const groups = [];
1296
+ const server = sites.find((x) =>
1297
+ x.rules.some((rule) => rule.to.type === ROUTING_RULE_TYPES.DAEMON && rule.to.did === info.did)
1298
+ );
1299
+ if (server) {
1300
+ groups.push({
1301
+ did: info.did,
1302
+ type: 'server',
1303
+ hosts: server.domainAliases.map((d) => d.value).filter(Boolean),
1304
+ });
1305
+ }
1306
+
1307
+ // blocklets
1308
+ sites
1309
+ .filter((x) => x.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX))
1310
+ .forEach((site) => {
1311
+ groups.push({
1312
+ did: site.domain.replace(BLOCKLET_SITE_GROUP_SUFFIX, ''),
1313
+ type: 'blocklet',
1314
+ hosts: site.domainAliases.map((d) => d.value).filter(Boolean),
1315
+ });
1316
+ });
1317
+ logger.info('Prepare analyze router logs', groups);
1318
+
1319
+ const logDir = providers[providerName].getLogDir();
1320
+ const doAnalyze = async (date, groupsRaw) => {
1321
+ logger.info('Start analyze router logs', { date });
1322
+ try {
1323
+ let results = await processLogByDate(logDir, dataDirs.tmp, dataDirs.data, date, groupsRaw);
1324
+ logger.info('Done analyze router logs', { date, results });
1325
+
1326
+ results = await Promise.all(
1327
+ results
1328
+ .filter((x) => x.result)
1329
+ .map((x) => ({ did: x.did, date, ...toCamelCase(x.result) }))
1330
+ .map((x) => trafficInsight.upsert({ did: x.did, date: x.date }, x))
1331
+ );
1332
+ logger.info('Done insert insight results', { date, results });
1333
+ } catch (err) {
1334
+ logger.error('Failed to analyze router logs', { date, error: err });
1335
+ }
1336
+ };
1337
+
1338
+ const analyzeLock = path.join(logDir, '.analyze.lock');
1339
+ if (fs.existsSync(analyzeLock)) {
1340
+ // FIXME: @wangshijun how do we support real time logs
1341
+ const date = dayjs().subtract(1, 'day').format('YYYY-MM-DD');
1342
+ await doAnalyze(date, cloneDeep(groups));
1343
+ } else {
1344
+ fs.writeFileSync(analyzeLock, '1');
1345
+ for (let i = 1; i <= 30; i++) {
1346
+ const date = dayjs().subtract(i, 'day').format('YYYY-MM-DD');
1347
+ // eslint-disable-next-line no-await-in-loop
1348
+ await doAnalyze(date, cloneDeep(groups));
1349
+ }
1350
+ }
1351
+ };
1352
+
1268
1353
  const updateNodeRouting = async (params, context = {}) => {
1269
1354
  const info = await nodeState.read();
1270
1355
  const { snapshotHash: oldSnapshotHash } = info.routing || {};
@@ -1396,7 +1481,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1396
1481
  return attachRuntimeDomainAliases({
1397
1482
  sites: await ensureLatestInfo(sites, { withDefaultCors }),
1398
1483
  context,
1399
- node: nodeState,
1400
1484
  });
1401
1485
  };
1402
1486
 
@@ -1405,7 +1489,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1405
1489
  return attachRuntimeDomainAliases({
1406
1490
  sites: await ensureLatestInfo(sites, { withDefaultCors }),
1407
1491
  context,
1408
- node: nodeState,
1409
1492
  });
1410
1493
  };
1411
1494
 
@@ -1490,7 +1573,13 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1490
1573
  name: 'rotate-log-files',
1491
1574
  time: '1 0 0 * * *', // rotate at 00:00:01 every day
1492
1575
  fn: rotateRouterLog,
1493
- options: { runOnInit: false },
1576
+ options: { runOnInit: process.env.ABT_NODE_JOB_NAME === 'rotate-log-files' },
1577
+ },
1578
+ {
1579
+ name: 'analyze-log-files',
1580
+ time: '0 5 0 * * *', // analyze at 00:05:00 every day
1581
+ fn: analyzeRouterLog,
1582
+ options: { runOnInit: process.env.ABT_NODE_JOB_NAME === 'analyze-log-files' },
1494
1583
  },
1495
1584
  ],
1496
1585
 
@@ -14,6 +14,7 @@ const {
14
14
  getDisplayName,
15
15
  forEachBlocklet,
16
16
  forEachBlockletSync,
17
+ forEachComponentV2,
17
18
  forEachComponentV2Sync,
18
19
  getBlockletServices,
19
20
  } = require('@blocklet/meta/lib/util');
@@ -24,6 +25,7 @@ const {
24
25
  BLOCKLET_DEFAULT_PORT_NAME,
25
26
  BlockletGroup,
26
27
  } = require('@blocklet/constant');
28
+ const { refreshPorts } = require('@abtnode/util/lib/port');
27
29
  const { APP_STRUCT_VERSION } = require('@abtnode/constant');
28
30
 
29
31
  const logger = require('@abtnode/logger')('@abtnode/core:states:blocklet');
@@ -343,6 +345,9 @@ class BlockletState extends BaseState {
343
345
  }
344
346
  }
345
347
 
348
+ /**
349
+ * assign ports for blocklet during install/upgrade workflow
350
+ */
346
351
  async getBlockletPorts({
347
352
  interfaces = [],
348
353
  alreadyAssigned = {},
@@ -351,32 +356,7 @@ class BlockletState extends BaseState {
351
356
  defaultPort = 0,
352
357
  } = {}) {
353
358
  try {
354
- const blocklets = await this.getBlocklets({}, { port: 1, ports: 1, meta: 1, children: 1 });
355
-
356
- const occupiedExternalPorts = new Map();
357
- const occupiedInternalPorts = new Map();
358
-
359
- const calcPortsFromBlocklet = (blocklet) => {
360
- occupiedInternalPorts.set(Number(blocklet.port), true);
361
-
362
- if (blocklet.ports && typeof blocklet.ports === 'object') {
363
- Object.keys(blocklet.ports).forEach((key) => {
364
- occupiedInternalPorts.set(Number(blocklet.ports[key]), true);
365
- });
366
- }
367
-
368
- if (Array.isArray(blocklet.meta.interfaces)) {
369
- blocklet.meta.interfaces.forEach((x) => {
370
- if (x.port && x.port.external) {
371
- occupiedExternalPorts.set(Number(x.port.external), true);
372
- }
373
- });
374
- }
375
- };
376
-
377
- for (const blocklet of blocklets) {
378
- await forEachBlocklet(blocklet, calcPortsFromBlocklet);
379
- }
359
+ const { occupiedExternalPorts, occupiedInternalPorts } = await this._getOccupiedPorts();
380
360
 
381
361
  const wantedPorts = uniq(
382
362
  interfaces
@@ -437,6 +417,30 @@ class BlockletState extends BaseState {
437
417
  }
438
418
  }
439
419
 
420
+ /**
421
+ * refresh ports for blocklet if occupied during starting workflow
422
+ */
423
+ async refreshBlockletPorts(did, componentDids = []) {
424
+ const blocklet = await this.getBlocklet(did);
425
+ if (!blocklet) {
426
+ throw new Error('Blocklet does not exist');
427
+ }
428
+
429
+ const { occupiedExternalPorts, occupiedInternalPorts } = await this._getOccupiedPorts();
430
+
431
+ await forEachComponentV2(blocklet, async (component) => {
432
+ if (!shouldSkipComponent(component.meta.did, componentDids)) {
433
+ component.ports = await refreshPorts(component.ports, {
434
+ blackList: [...occupiedExternalPorts.keys(), ...occupiedInternalPorts.keys()],
435
+ });
436
+ }
437
+ });
438
+
439
+ return this.updateBlocklet(did, {
440
+ children: blocklet.children,
441
+ });
442
+ }
443
+
440
444
  async getServices() {
441
445
  const blocklets = await this.getBlocklets({}, { meta: 1, children: 1, ports: 1 });
442
446
  const services = [];
@@ -515,12 +519,16 @@ class BlockletState extends BaseState {
515
519
  const doc = await this.getBlocklet(did);
516
520
 
517
521
  if (doc.meta?.group === BlockletGroup.gateway && !doc.children?.length) {
518
- return this.updateBlocklet(did, { status });
522
+ const res = await this.updateBlocklet(did, { status });
523
+ statusLock.release();
524
+ return res;
519
525
  }
520
526
 
521
527
  // for backward compatibility
522
528
  if (!doc.structVersion && !doc.children?.length) {
523
- return this.updateBlocklet(did, { status });
529
+ const res = await this.updateBlocklet(did, { status });
530
+ statusLock.release();
531
+ return res;
524
532
  }
525
533
 
526
534
  // update children status
@@ -657,10 +665,43 @@ class BlockletState extends BaseState {
657
665
 
658
666
  _getStatusLock(did) {
659
667
  if (!this.statusLocks.has(did)) {
660
- this.statusLocks[did] = new Lock();
668
+ this.statusLocks.set(did, new Lock());
661
669
  }
662
670
 
663
- return this.statusLocks[did];
671
+ return this.statusLocks.get(did);
672
+ }
673
+
674
+ async _getOccupiedPorts() {
675
+ const blocklets = await this.getBlocklets({}, { port: 1, ports: 1, meta: 1, children: 1 });
676
+
677
+ const occupiedExternalPorts = new Map();
678
+ const occupiedInternalPorts = new Map();
679
+
680
+ const calcPortsFromBlocklet = (blocklet) => {
681
+ occupiedInternalPorts.set(Number(blocklet.port), true);
682
+
683
+ if (blocklet.ports && typeof blocklet.ports === 'object') {
684
+ Object.keys(blocklet.ports).forEach((key) => {
685
+ occupiedInternalPorts.set(Number(blocklet.ports[key]), true);
686
+ });
687
+ }
688
+
689
+ if (Array.isArray(blocklet.meta.interfaces)) {
690
+ blocklet.meta.interfaces.forEach((x) => {
691
+ if (x.port && x.port.external) {
692
+ occupiedExternalPorts.set(Number(x.port.external), true);
693
+ }
694
+ });
695
+ }
696
+ };
697
+
698
+ for (const blocklet of blocklets) {
699
+ await forEachBlocklet(blocklet, calcPortsFromBlocklet);
700
+ }
701
+ return {
702
+ occupiedExternalPorts,
703
+ occupiedInternalPorts,
704
+ };
664
705
  }
665
706
  }
666
707
 
@@ -15,6 +15,8 @@ const CacheState = require('./cache');
15
15
  const AuditLogState = require('./audit-log');
16
16
  const JobState = require('./job');
17
17
  const BackupState = require('./backup');
18
+ const TrafficInsightState = require('./traffic-insight');
19
+
18
20
  const { getDbFilePath } = require('../util');
19
21
 
20
22
  const models = getServerModels();
@@ -38,6 +40,7 @@ const init = (dataDirs, config = {}) => {
38
40
  const auditLogState = new AuditLogState(models.AuditLog, config);
39
41
  const jobState = new JobState(models.Job, config);
40
42
  const backupState = new BackupState(models.Backup, config);
43
+ const trafficInsight = new TrafficInsightState(models.TrafficInsight, config);
41
44
 
42
45
  return {
43
46
  node: nodeState,
@@ -53,6 +56,7 @@ const init = (dataDirs, config = {}) => {
53
56
  auditLog: auditLogState,
54
57
  job: jobState,
55
58
  backup: backupState,
59
+ trafficInsight,
56
60
  };
57
61
  };
58
62
 
@@ -0,0 +1,25 @@
1
+ const { Op } = require('sequelize');
2
+ const BaseState = require('./base');
3
+
4
+ /**
5
+ * @extends BaseState<import('@abtnode/models').TrafficInsightState>
6
+ */
7
+ class TrafficInsight extends BaseState {
8
+ findPaginated({ did = '', startDate = '', endDate = '', paging = { pageSize: 30 } } = {}) {
9
+ const where = {};
10
+ if (did) {
11
+ where.did = did;
12
+ }
13
+ if (startDate) {
14
+ where.date = { [Op.gte]: startDate };
15
+ }
16
+ if (endDate) {
17
+ where.date = where.date || {};
18
+ where.date[Op.lte] = endDate;
19
+ }
20
+
21
+ return super.paginate({ where }, { date: -1 }, paging);
22
+ }
23
+ }
24
+
25
+ module.exports = TrafficInsight;
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('fs-extra');
4
4
  const path = require('path');
5
- const dayjs = require('dayjs');
5
+ const dayjs = require('@abtnode/util/lib/dayjs');
6
6
  const shelljs = require('shelljs');
7
7
  const os = require('os');
8
8
  const tar = require('tar');
@@ -26,7 +26,6 @@ const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet');
26
26
  const pm2 = require('@abtnode/util/lib/async-pm2');
27
27
  const sleep = require('@abtnode/util/lib/sleep');
28
28
  const getPm2ProcessInfo = require('@abtnode/util/lib/get-pm2-process-info');
29
- const killProcessOccupiedPorts = require('@abtnode/util/lib/kill-process-occupied-ports');
30
29
  const { formatEnv } = require('@abtnode/util/lib/security');
31
30
  const ensureEndpointHealthy = require('@abtnode/util/lib/ensure-endpoint-healthy');
32
31
  const CustomError = require('@abtnode/util/lib/custom-error');
@@ -49,6 +48,7 @@ const {
49
48
  getComponentConfig,
50
49
  } = require('@blocklet/resolver');
51
50
  const formatBackSlash = require('@abtnode/util/lib/format-back-slash');
51
+ const { isPortsOccupiedByOtherProcess, killProcessOccupiedPorts } = require('@abtnode/util/lib/port');
52
52
  const { toSvg: createDidLogo } =
53
53
  process.env.NODE_ENV !== 'test' ? require('@arcblock/did-motif') : require('@arcblock/did-motif/dist/did-motif.cjs');
54
54
  const { createBlockiesSvg } = require('@blocklet/meta/lib/blockies');
@@ -80,6 +80,7 @@ const {
80
80
  findWebInterface,
81
81
  forEachBlockletSync,
82
82
  forEachChildSync,
83
+ forEachComponentV2,
83
84
  forEachComponentV2Sync,
84
85
  getSharedConfigObj,
85
86
  getComponentName,
@@ -569,7 +570,7 @@ const startBlockletProcess = async (
569
570
  };
570
571
 
571
572
  const clusterMode = get(b.meta, 'capabilities.clusterMode', false);
572
- if (clusterMode && blocklet.mode !== BLOCKLET_MODES.DEVELOPMENT) {
573
+ if (clusterMode && b.mode !== BLOCKLET_MODES.DEVELOPMENT) {
573
574
  const clusterSize = Number(blocklet.configObj.BLOCKLET_CLUSTER_SIZE) || +process.env.ABT_NODE_MAX_CLUSTER_SIZE;
574
575
  options.execMode = 'cluster';
575
576
  options.mergeLogs = true;
@@ -1768,6 +1769,48 @@ const exceedRedemptionPeriod = (expirationDate) => {
1768
1769
  return dayjs().diff(dayjs(expirationDate), 'day') > EXPIRED_BLOCKLET_DATA_RETENTION_DAYS;
1769
1770
  };
1770
1771
 
1772
+ const ensureAppPortsNotOccupied = async ({
1773
+ blocklet,
1774
+ componentDids: inputDids,
1775
+ states,
1776
+ manager,
1777
+ checkPortsFn = isPortsOccupiedByOtherProcess,
1778
+ }) => {
1779
+ const { did } = blocklet.meta;
1780
+ const componentDids = [];
1781
+ await forEachComponentV2(blocklet, async (b) => {
1782
+ try {
1783
+ if (shouldSkipComponent(b.meta.did, inputDids)) {
1784
+ return;
1785
+ }
1786
+
1787
+ const { processId } = b.env;
1788
+ const { ports } = b;
1789
+ if (
1790
+ await checkPortsFn({
1791
+ ports,
1792
+ pm2ProcessId: processId,
1793
+ printError: logger.error.bind(logger),
1794
+ })
1795
+ ) {
1796
+ componentDids.push(b.meta.did);
1797
+ }
1798
+ } catch (error) {
1799
+ logger.error('Failed to check ports occupied', { error });
1800
+ }
1801
+ });
1802
+
1803
+ let newBlocklet = blocklet;
1804
+ if (componentDids.length) {
1805
+ await states.blocklet.refreshBlockletPorts(did, componentDids);
1806
+ logger.info('refresh component ports', { did, componentDids });
1807
+ await manager._updateBlockletEnvironment(did);
1808
+ newBlocklet = await manager.ensureBlocklet(did);
1809
+ }
1810
+
1811
+ return newBlocklet;
1812
+ };
1813
+
1771
1814
  module.exports = {
1772
1815
  updateBlockletFallbackLogo,
1773
1816
  forEachBlocklet,
@@ -1829,4 +1872,5 @@ module.exports = {
1829
1872
  shouldSkipComponent,
1830
1873
  getBlockletURLForLauncher,
1831
1874
  exceedRedemptionPeriod,
1875
+ ensureAppPortsNotOccupied,
1832
1876
  };
package/lib/util/index.js CHANGED
@@ -4,6 +4,7 @@ const path = require('path');
4
4
  const dns = require('dns');
5
5
  const crypto = require('crypto');
6
6
  const shell = require('shelljs');
7
+ const camelCase = require('lodash/camelCase');
7
8
  const get = require('lodash/get');
8
9
  const pickBy = require('lodash/pickBy');
9
10
  const { isFromPublicKey } = require('@arcblock/did');
@@ -469,6 +470,27 @@ const isServerSite = (domain) =>
469
470
 
470
471
  const getDbFilePath = (filePath) => (process.env.NODE_ENV === 'test' ? `${filePath}:memory:` : filePath);
471
472
 
473
+ const toCamelCase = (obj) => {
474
+ if (typeof obj !== 'object' || obj === null) {
475
+ return obj;
476
+ }
477
+
478
+ if (Array.isArray(obj)) {
479
+ return obj.map(toCamelCase);
480
+ }
481
+
482
+ const converted = {};
483
+
484
+ // eslint-disable-next-line no-restricted-syntax
485
+ for (const key in obj) {
486
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
487
+ converted[camelCase(key)] = toCamelCase(obj[key]);
488
+ }
489
+ }
490
+
491
+ return converted;
492
+ };
493
+
472
494
  const lib = {
473
495
  validateOwner,
474
496
  getProviderFromNodeInfo,
@@ -505,6 +527,7 @@ const lib = {
505
527
  isBlockletSite,
506
528
  isServerSite,
507
529
  getDbFilePath,
530
+ toCamelCase,
508
531
  };
509
532
 
510
533
  module.exports = lib;
@@ -3,7 +3,7 @@
3
3
  */
4
4
  const fs = require('fs-extra');
5
5
  const path = require('path');
6
- const moment = require('moment-timezone');
6
+ const dayjs = require('@abtnode/util/lib/dayjs');
7
7
  const zlib = require('zlib');
8
8
  const log = require('@abtnode/logger');
9
9
 
@@ -70,11 +70,11 @@ module.exports = class LogRotate {
70
70
  */
71
71
  proceed(file, callback = () => {}) {
72
72
  // set default final time
73
- let finalTime = moment().subtract(1, 'day').format(this.dateFormat);
73
+ let finalTime = dayjs().subtract(1, 'day').format(this.dateFormat);
74
74
  // check for a timezone
75
75
  if (this.tz) {
76
76
  try {
77
- finalTime = moment().tz(this.tz).subtract(1, 'day').format(this.dateFormat);
77
+ finalTime = dayjs().tz(this.tz).subtract(1, 'day').format(this.dateFormat);
78
78
  } catch (err) {
79
79
  // use default
80
80
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.13-beta-0656f92f",
6
+ "version": "1.16.13-beta-2eb54cbc",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,18 +19,19 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "Apache-2.0",
21
21
  "dependencies": {
22
- "@abtnode/auth": "1.16.13-beta-0656f92f",
23
- "@abtnode/certificate-manager": "1.16.13-beta-0656f92f",
24
- "@abtnode/constant": "1.16.13-beta-0656f92f",
25
- "@abtnode/cron": "1.16.13-beta-0656f92f",
26
- "@abtnode/logger": "1.16.13-beta-0656f92f",
27
- "@abtnode/models": "1.16.13-beta-0656f92f",
28
- "@abtnode/queue": "1.16.13-beta-0656f92f",
29
- "@abtnode/rbac": "1.16.13-beta-0656f92f",
30
- "@abtnode/router-provider": "1.16.13-beta-0656f92f",
31
- "@abtnode/static-server": "1.16.13-beta-0656f92f",
32
- "@abtnode/timemachine": "1.16.13-beta-0656f92f",
33
- "@abtnode/util": "1.16.13-beta-0656f92f",
22
+ "@abtnode/analytics": "1.16.13-beta-2eb54cbc",
23
+ "@abtnode/auth": "1.16.13-beta-2eb54cbc",
24
+ "@abtnode/certificate-manager": "1.16.13-beta-2eb54cbc",
25
+ "@abtnode/constant": "1.16.13-beta-2eb54cbc",
26
+ "@abtnode/cron": "1.16.13-beta-2eb54cbc",
27
+ "@abtnode/logger": "1.16.13-beta-2eb54cbc",
28
+ "@abtnode/models": "1.16.13-beta-2eb54cbc",
29
+ "@abtnode/queue": "1.16.13-beta-2eb54cbc",
30
+ "@abtnode/rbac": "1.16.13-beta-2eb54cbc",
31
+ "@abtnode/router-provider": "1.16.13-beta-2eb54cbc",
32
+ "@abtnode/static-server": "1.16.13-beta-2eb54cbc",
33
+ "@abtnode/timemachine": "1.16.13-beta-2eb54cbc",
34
+ "@abtnode/util": "1.16.13-beta-2eb54cbc",
34
35
  "@arcblock/did": "1.18.84",
35
36
  "@arcblock/did-auth": "1.18.84",
36
37
  "@arcblock/did-ext": "^1.18.84",
@@ -41,10 +42,10 @@
41
42
  "@arcblock/pm2-events": "^0.0.5",
42
43
  "@arcblock/validator": "^1.18.84",
43
44
  "@arcblock/vc": "1.18.84",
44
- "@blocklet/constant": "1.16.13-beta-0656f92f",
45
- "@blocklet/meta": "1.16.13-beta-0656f92f",
46
- "@blocklet/resolver": "1.16.13-beta-0656f92f",
47
- "@blocklet/sdk": "1.16.13-beta-0656f92f",
45
+ "@blocklet/constant": "1.16.13-beta-2eb54cbc",
46
+ "@blocklet/meta": "1.16.13-beta-2eb54cbc",
47
+ "@blocklet/resolver": "1.16.13-beta-2eb54cbc",
48
+ "@blocklet/sdk": "1.16.13-beta-2eb54cbc",
48
49
  "@did-space/client": "^0.2.117",
49
50
  "@fidm/x509": "^1.2.1",
50
51
  "@ocap/mcrypto": "1.18.84",
@@ -56,7 +57,6 @@
56
57
  "axon": "^2.0.3",
57
58
  "chalk": "^4.1.2",
58
59
  "cross-spawn": "^7.0.3",
59
- "dayjs": "^1.11.7",
60
60
  "deep-diff": "^1.0.2",
61
61
  "detect-port": "^1.5.1",
62
62
  "escape-string-regexp": "^4.0.0",
@@ -73,7 +73,6 @@
73
73
  "kill-port": "^2.0.1",
74
74
  "lodash": "^4.17.21",
75
75
  "lru-cache": "^6.0.0",
76
- "moment-timezone": "^0.5.37",
77
76
  "node-stream-zip": "^1.15.0",
78
77
  "p-limit": "^3.1.0",
79
78
  "p-retry": "4.6.1",
@@ -97,5 +96,5 @@
97
96
  "express": "^4.18.2",
98
97
  "jest": "^27.5.1"
99
98
  },
100
- "gitHead": "07996e8ec6fd2141ee3e93dd699ed7fd434bc0ad"
99
+ "gitHead": "7adf96c2eb31762d8eb14b8121fce9865d6a458c"
101
100
  }