@abtnode/core 1.4.10 → 1.4.14

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.
@@ -8,6 +8,7 @@ const capitalize = require('lodash/capitalize');
8
8
  const diff = require('deep-diff');
9
9
  const { Throttle } = require('stream-throttle');
10
10
  const { isValid: isValidDid } = require('@arcblock/did');
11
+ const { verifyPresentation } = require('@arcblock/vc');
11
12
  const { toBase58 } = require('@ocap/util');
12
13
  const { fromSecretKey } = require('@ocap/wallet');
13
14
 
@@ -15,6 +16,8 @@ const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager');
15
16
  const hashFiles = require('@abtnode/util/lib/hash-files');
16
17
  const downloadFile = require('@abtnode/util/lib/download-file');
17
18
  const Lock = require('@abtnode/util/lib/lock');
19
+ const { getVcFromPresentation } = require('@abtnode/util/lib/vc');
20
+ const { BLOCKLET_PURCHASE_NFT_TYPE } = require('@abtnode/constant');
18
21
 
19
22
  const getBlockletEngine = require('@blocklet/meta/lib/engine');
20
23
  const { isFreeBlocklet } = require('@blocklet/meta/lib/payment');
@@ -31,6 +34,7 @@ const {
31
34
  fromBlockletSource,
32
35
  } = require('@blocklet/meta/lib/constants');
33
36
  const util = require('../../util');
37
+ const getAccessibleExternalNodeIp = require('../../util/get-accessible-external-node-ip');
34
38
  const {
35
39
  forEachBlocklet,
36
40
  getBlockletDirs,
@@ -57,6 +61,7 @@ const {
57
61
  getRuntimeInfo,
58
62
  mergeMeta,
59
63
  getUpdateMetaList,
64
+ getRuntimeEnvironments,
60
65
  } = require('../../util/blocklet');
61
66
  const states = require('../../states');
62
67
  const BlockletRegistry = require('../registry');
@@ -135,45 +140,46 @@ class BlockletManager extends BaseBlockletManager {
135
140
  */
136
141
  async install(params, context) {
137
142
  logger.debug('install blocklet', { params, context });
138
- if (params.installId) {
139
- const { installId, sync } = params;
140
- return this._installFromId({ installId, sync }, context);
143
+ if (params.url) {
144
+ return this._installFromUrl({ url: params.url, sync: params.sync }, context);
141
145
  }
146
+
142
147
  if (params.file) {
143
148
  const { file, did, diffVersion, deleteSet } = params;
144
149
  return this._installFromUpload({ file, did, diffVersion, deleteSet, context });
145
150
  }
146
151
 
147
152
  if (params.did) {
148
- return this._installFromRegistry(params.did, context);
153
+ return this._installFromRegistry({ did: params.did }, context);
149
154
  }
150
155
 
151
156
  throw new Error('Can only install blocklet from url/file/did');
152
157
  }
153
158
 
154
159
  // eslint-disable-next-line no-unused-vars
155
- async installFromUrl({ url }, context) {
156
- logger.info('Install from url', { from: url });
160
+ async getMetaFromUrl({ url }, context) {
161
+ const meta = await getBlockletMetaFromUrl(url);
157
162
 
158
- try {
159
- const meta = await getBlockletMetaFromUrl(url);
163
+ return meta;
164
+ }
160
165
 
161
- const { id: installId } = await this.session.start({
162
- meta,
163
- metaSource: url,
164
- });
166
+ async installBlockletFromVc({ vcPresentation, challenge }, context) {
167
+ logger.info('Install from vc');
168
+ const vc = getVcFromPresentation(vcPresentation);
165
169
 
166
- const exist = await this.state.getBlocklet(meta.did);
170
+ // FIXME: 这里的 trustedIssuers 相当于相信任何 VC,需要想更安全的方法
171
+ verifyPresentation({ presentation: vcPresentation, trustedIssuers: [get(vc, 'issuer.id')], challenge });
167
172
 
168
- return {
169
- installId,
170
- meta,
171
- existingMeta: exist ? exist.meta : null,
172
- };
173
- } catch (error) {
174
- logger.error('Install from url error', { url, error });
175
- throw error;
173
+ if (!vc.type.includes(BLOCKLET_PURCHASE_NFT_TYPE)) {
174
+ throw new Error(`Expect ${BLOCKLET_PURCHASE_NFT_TYPE} VC type`);
176
175
  }
176
+
177
+ const blockletUrl = get(vc, 'credentialSubject.purchased.blocklet.url');
178
+ const urlObject = new URL(blockletUrl);
179
+ const did = get(vc, 'credentialSubject.purchased.blocklet.id');
180
+ const registry = urlObject.origin;
181
+
182
+ return this._installFromRegistry({ did, registry }, { ...context, blockletPurchaseVerified: true });
177
183
  }
178
184
 
179
185
  async start({ did, checkHealthImmediately = false, throwOnError }, context) {
@@ -216,7 +222,7 @@ class BlockletManager extends BaseBlockletManager {
216
222
  hooks.preStart(b, {
217
223
  appDir: b.env.appDir,
218
224
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
219
- env: { ...nodeEnvironments, ...b.systemEnvironments, ...b.userEnvironments },
225
+ env: getRuntimeEnvironments(b, nodeEnvironments),
220
226
  did, // root blocklet did,
221
227
  progress: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT,
222
228
  }),
@@ -288,7 +294,7 @@ class BlockletManager extends BaseBlockletManager {
288
294
  hooks.preStop({
289
295
  appDir: b.env.appDir,
290
296
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
291
- env: { ...nodeEnvironments, ...b.systemEnvironments, ...b.userEnvironments },
297
+ env: getRuntimeEnvironments(b, nodeEnvironments),
292
298
  did, // root blocklet did
293
299
  notification: this.notification,
294
300
  context,
@@ -361,7 +367,7 @@ class BlockletManager extends BaseBlockletManager {
361
367
  hooks.preUninstall({
362
368
  appDir: b.env.appDir,
363
369
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
364
- env: { ...nodeEnvironments, ...b.systemEnvironments, ...b.userEnvironments },
370
+ env: getRuntimeEnvironments(b, nodeEnvironments),
365
371
  did, // root blocklet did
366
372
  notification: this.notification,
367
373
  context,
@@ -472,8 +478,12 @@ class BlockletManager extends BaseBlockletManager {
472
478
  if (x.key === 'BLOCKLET_APP_SK') {
473
479
  try {
474
480
  fromSecretKey(x.value);
475
- } catch (err) {
476
- throw new Error('Invalid custom blocklet secret key');
481
+ } catch {
482
+ try {
483
+ fromSecretKey(x.value, 'eth');
484
+ } catch {
485
+ throw new Error('Invalid custom blocklet secret key');
486
+ }
477
487
  }
478
488
  }
479
489
 
@@ -483,13 +493,13 @@ class BlockletManager extends BaseBlockletManager {
483
493
  }
484
494
  }
485
495
 
486
- blocklet.userEnvironments[x.key] = x.value;
496
+ blocklet.configObj[x.key] = x.value;
487
497
  });
488
498
  await hooks.preConfig({
489
499
  appDir: blocklet.env.appDir,
490
500
  hooks: Object.assign(blocklet.meta.hooks || {}, blocklet.meta.scripts || {}),
491
501
  exitOnError: true,
492
- env: { ...nodeEnvironments, ...blocklet.systemEnvironments, ...blocklet.userEnvironments },
502
+ env: { ...getRuntimeEnvironments(blocklet, nodeEnvironments), ...blocklet.configObj },
493
503
  notification: this.notification,
494
504
  did,
495
505
  context,
@@ -891,7 +901,8 @@ class BlockletManager extends BaseBlockletManager {
891
901
  // Put shorter url first
892
902
  return a.from.pathPrefix.length < b.from.pathPrefix ? 1 : -1;
893
903
  });
894
- blocklet.interfaces = getBlockletInterfaces({ blocklet, context, nodeInfo, sites: routingRules });
904
+ const nodeIp = await getAccessibleExternalNodeIp(nodeInfo);
905
+ blocklet.interfaces = getBlockletInterfaces({ blocklet, context, nodeInfo, sites: routingRules, nodeIp });
895
906
 
896
907
  try {
897
908
  const { appId } = blocklet.meta.group === BlockletGroup.gateway ? blocklet.children[0].env : blocklet.env;
@@ -1116,12 +1127,12 @@ class BlockletManager extends BaseBlockletManager {
1116
1127
  const nodeInfo = await this.node.read();
1117
1128
 
1118
1129
  const rootSystemEnvironments = getRootSystemEnvironments(blockletWithEnv, nodeInfo);
1119
- const rootUserEnvironment = blockletWithEnv.userEnvironments;
1120
- const overwrittenEnvironments = getOverwrittenEnvironments(blockletWithEnv);
1130
+ const rootConfig = blockletWithEnv.configObj;
1131
+ const overwrittenEnvironments = getOverwrittenEnvironments(blockletWithEnv, nodeInfo);
1121
1132
 
1122
1133
  // fill environments to blocklet and blocklet.children
1123
1134
  blocklet.environments = formatEnvironments({
1124
- ...rootUserEnvironment,
1135
+ ...rootConfig,
1125
1136
  ...getSystemEnvironments(blockletWithEnv),
1126
1137
  ...rootSystemEnvironments,
1127
1138
  ...overwrittenEnvironments,
@@ -1130,11 +1141,12 @@ class BlockletManager extends BaseBlockletManager {
1130
1141
  for (const child of blocklet.children) {
1131
1142
  const childWithEnv = blockletWithEnv.children.find((x) => x.meta.did === child.meta.did);
1132
1143
  if (childWithEnv) {
1144
+ const childConfig = childWithEnv.configObj;
1133
1145
  child.environments = formatEnvironments({
1134
- ...childWithEnv.userEnvironments,
1135
- ...rootUserEnvironment, // FIXME: use options or hooks to make merge logic flexible
1136
- ...getSystemEnvironments(childWithEnv),
1137
- ...rootSystemEnvironments,
1146
+ ...childConfig, // custom env of child blocklet
1147
+ ...rootConfig, // // custom env of root blocklet FIXME: use options or hooks to make merge logic flexible
1148
+ ...getSystemEnvironments(childWithEnv), // system env of child blocklet
1149
+ ...rootSystemEnvironments, // system env of root blocklet
1138
1150
  ...overwrittenEnvironments,
1139
1151
  });
1140
1152
  }
@@ -1152,15 +1164,15 @@ class BlockletManager extends BaseBlockletManager {
1152
1164
  }
1153
1165
  }
1154
1166
 
1155
- async _installFromRegistry(did, context) {
1167
+ async _installFromRegistry({ did, registry }, context) {
1156
1168
  logger.debug('start install blocklet', { did });
1157
1169
  if (!isValidDid(did)) {
1158
1170
  throw new Error('Blocklet did is invalid');
1159
1171
  }
1160
1172
 
1161
- const registryUrl = await states.node.getBlockletRegistry();
1173
+ const registryUrl = registry || (await states.node.getBlockletRegistry());
1162
1174
  const info = await BlockletRegistry.getRegistryMeta(registryUrl);
1163
- const blocklet = await this.registry.getBlocklet(did);
1175
+ const blocklet = await this.registry.getBlocklet(did, registryUrl);
1164
1176
  if (!blocklet) {
1165
1177
  throw new Error('Can not install blocklet that not found in registry');
1166
1178
  }
@@ -1183,15 +1195,13 @@ class BlockletManager extends BaseBlockletManager {
1183
1195
  });
1184
1196
  }
1185
1197
 
1186
- async _installFromId({ installId: id, sync }, context) {
1187
- logger.debug('start install blocklet', { installId: id });
1188
- if (!id) {
1189
- throw new Error('Install id should not be empty');
1190
- }
1198
+ async _installFromUrl({ url, sync }, context) {
1199
+ logger.debug('start install blocklet', { url });
1200
+
1201
+ const meta = await getBlockletMetaFromUrl(url);
1191
1202
 
1192
- const { meta, metaSource } = await this.session.end(id);
1193
1203
  if (!meta) {
1194
- throw new Error(`Can not install blocklet that not found by installId: ${id}`);
1204
+ throw new Error(`Can not install blocklet that not found by url: ${url}`);
1195
1205
  }
1196
1206
 
1197
1207
  // upgrade
@@ -1200,7 +1210,7 @@ class BlockletManager extends BaseBlockletManager {
1200
1210
  return this._upgrade({
1201
1211
  meta,
1202
1212
  source: BlockletSource.url,
1203
- deployedFrom: metaSource,
1213
+ deployedFrom: url,
1204
1214
  sync,
1205
1215
  context,
1206
1216
  });
@@ -1210,7 +1220,7 @@ class BlockletManager extends BaseBlockletManager {
1210
1220
  return this._install({
1211
1221
  meta,
1212
1222
  source: BlockletSource.url,
1213
- deployedFrom: metaSource,
1223
+ deployedFrom: url,
1214
1224
  sync,
1215
1225
  context,
1216
1226
  });
@@ -1738,7 +1748,7 @@ class BlockletManager extends BaseBlockletManager {
1738
1748
  const postInstall = (b) =>
1739
1749
  hooks.postInstall({
1740
1750
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1741
- env: { ...nodeEnvironments, ...b.systemEnvironments, ...b.userEnvironments },
1751
+ env: getRuntimeEnvironments(b, nodeEnvironments),
1742
1752
  appDir: blocklet.env.appDir,
1743
1753
  did, // root blocklet did
1744
1754
  notification: this.notification,
@@ -1824,7 +1834,7 @@ class BlockletManager extends BaseBlockletManager {
1824
1834
  const postInstall = (b) =>
1825
1835
  hooks.postInstall({
1826
1836
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1827
- env: { ...nodeEnvironments, ...b.systemEnvironments, ...b.userEnvironments },
1837
+ env: getRuntimeEnvironments(b, nodeEnvironments),
1828
1838
  appDir: b.env.appDir,
1829
1839
  did, // root blocklet did
1830
1840
  notification: this.notification,
@@ -57,8 +57,8 @@ class BlockletRegistry {
57
57
  return list.some((x) => x.did === id || x.name === id);
58
58
  }
59
59
 
60
- getBlocklet(id) {
61
- return this.getBlockletMeta({ did: id });
60
+ getBlocklet(id, registryUrl) {
61
+ return this.getBlockletMeta({ did: id, registryUrl });
62
62
  }
63
63
 
64
64
  // eslint-disable-next-line no-unused-vars
package/lib/index.js CHANGED
@@ -129,7 +129,6 @@ function ABTNode(options) {
129
129
  getRoutingSites,
130
130
  getSnapshotSites,
131
131
  ensureDashboardRouting,
132
- ensureIpDnsRouting,
133
132
  ensureBlockletRouting,
134
133
  ensureBlockletRoutingForUpgrade,
135
134
  removeBlockletRouting,
@@ -185,7 +184,7 @@ function ABTNode(options) {
185
184
 
186
185
  // Blocklet manager
187
186
  installBlocklet: blockletManager.install.bind(blockletManager),
188
- installBlockletFromUrl: blockletManager.installFromUrl.bind(blockletManager),
187
+ installBlockletFromVc: blockletManager.installBlockletFromVc.bind(blockletManager),
189
188
  startBlocklet: blockletManager.start.bind(blockletManager),
190
189
  stopBlocklet: blockletManager.stop.bind(blockletManager),
191
190
  reloadBlocklet: blockletManager.reload.bind(blockletManager),
@@ -199,6 +198,7 @@ function ABTNode(options) {
199
198
  checkChildBlockletsForUpdates: blockletManager.checkChildrenForUpdates.bind(blockletManager),
200
199
  updateChildBlocklets: blockletManager.updateChildren.bind(blockletManager),
201
200
  getLatestBlockletVersion: blockletManager.getLatestBlockletVersion.bind(blockletManager),
201
+ getBlockletMetaFromUrl: blockletManager.getMetaFromUrl.bind(blockletManager),
202
202
 
203
203
  deleteBlockletProcess: blockletManager.deleteProcess.bind(blockletManager),
204
204
 
@@ -214,7 +214,6 @@ function ABTNode(options) {
214
214
 
215
215
  // Registry
216
216
  listBlocklets: blockletRegistry.listBlocklets.bind(blockletRegistry),
217
- getBlockletMeta: blockletRegistry.getBlockletMeta.bind(blockletRegistry),
218
217
  getRegistryMeta: BlockletRegistry.getRegistryMeta,
219
218
 
220
219
  // Node State
@@ -305,7 +304,6 @@ function ABTNode(options) {
305
304
  takeRoutingSnapshot,
306
305
  getSitesFromSnapshot,
307
306
  ensureDashboardRouting,
308
- ensureIpDnsRouting,
309
307
 
310
308
  addDomainAlias: routerManager.addDomainAlias.bind(routerManager),
311
309
  deleteDomainAlias: routerManager.deleteDomainAlias.bind(routerManager),
@@ -4,6 +4,7 @@ const fs = require('fs-extra');
4
4
  const path = require('path');
5
5
  const tar = require('tar');
6
6
  const get = require('lodash/get');
7
+ const joinUrl = require('url-join');
7
8
  const { getProvider } = require('@abtnode/router-provider');
8
9
  const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
9
10
  const getTmpDirectory = require('@abtnode/util/lib/get-tmp-directory');
@@ -23,7 +24,7 @@ const {
23
24
  ROUTING_RULE_TYPES,
24
25
  CERTIFICATE_EXPIRES_OFFSET,
25
26
  CERTIFICATE_EXPIRES_WARNING_OFFSET,
26
- DEFAULT_DASHBOARD_DOMAIN,
27
+ DEFAULT_DAEMON_PORT,
27
28
  } = require('@abtnode/constant');
28
29
  const {
29
30
  BLOCKLET_DYNAMIC_PATH_PREFIX,
@@ -43,7 +44,6 @@ const {
43
44
  getWellknownSitePort,
44
45
  } = require('../util');
45
46
  const { getServicesFromBlockletInterface } = require('../util/service');
46
- const { getAccessibleIp, getAccessibleIpDnsDomainForNode } = require('../util/get-accessible-ip');
47
47
  const getIpDnsDomainForBlocklet = require('../util/get-ip-dns-domain-for-blocklet');
48
48
 
49
49
  const Router = require('./index');
@@ -208,24 +208,25 @@ const ensureLatestInterfaceInfo = async (sites = []) => {
208
208
 
209
209
  const ensureWellknownRule = async (sites) => {
210
210
  const tempSites = sites || [];
211
+ const wellknownPort = await getWellknownSitePort();
212
+
211
213
  for (const site of tempSites) {
212
214
  // 不向 default site & ip site & wellknown site 添加 wellknown rule
213
- const isBasicSites = [
215
+ const isBasicSite = [
214
216
  DOMAIN_FOR_INTERNAL_SITE,
215
217
  DOMAIN_FOR_IP_SITE,
216
218
  DOMAIN_FOR_DEFAULT_SITE,
217
219
  DOMAIN_FOR_IP_SITE_REGEXP,
218
220
  ].includes(site.domain);
219
221
 
220
- if (!isBasicSites) {
222
+ if (!isBasicSite) {
221
223
  const isExists = site.rules.find((x) => x.from.pathPrefix === WELLKNOWN_PATH_PREFIX);
222
224
 
223
225
  if (!isExists) {
224
226
  site.rules.push({
225
227
  from: { pathPrefix: WELLKNOWN_PATH_PREFIX },
226
228
  to: {
227
- // eslint-disable-next-line no-await-in-loop
228
- port: await getWellknownSitePort(),
229
+ port: wellknownPort,
229
230
  type: ROUTING_RULE_TYPES.GENERAL_PROXY,
230
231
  interfaceName: BLOCKLET_INTERFACE_WELLKNOWN,
231
232
  },
@@ -318,27 +319,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
318
319
  const httpsCertState = states.certificate;
319
320
  const notification = states.notification;
320
321
 
321
- let _nodeDomainRequest = null; // singleton of node-domain-request
322
- const _getIpDnsDomainForNode = ({ nodeInfo = {} } = {}) => {
323
- const timeout = process.env.NODE_ENV === 'test' ? 500 : 5000;
324
- if (_nodeDomainRequest) {
325
- return _nodeDomainRequest;
326
- }
327
-
328
- const req = getAccessibleIpDnsDomainForNode({ nodeInfo, timeout });
329
- _nodeDomainRequest = req;
330
-
331
- req
332
- .then(() => {
333
- _nodeDomainRequest = null;
334
- })
335
- .catch(() => {
336
- _nodeDomainRequest = null;
337
- });
338
-
339
- return req;
340
- };
341
-
342
322
  // site level duplication detection
343
323
  const hasRuleByPrefix = (site, value) => site.rules.find((x) => x.isProtected && get(x, 'from.pathPrefix') === value);
344
324
 
@@ -408,25 +388,47 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
408
388
 
409
389
  const addWellknownSite = async (sites, context) => {
410
390
  const site = (sites || []).find((x) => x.name === NAME_FOR_WELLKNOWN_SITE);
411
- if (site) {
412
- return false;
413
- }
414
391
 
415
392
  try {
416
- await routerManager.addRoutingSite(
417
- {
418
- site: {
419
- domain: DOMAIN_FOR_INTERNAL_SITE,
420
- port: await getWellknownSitePort(),
421
- name: NAME_FOR_WELLKNOWN_SITE,
422
- rules: [],
423
- isProtected: true,
424
- },
425
- skipCheckDynamicBlacklist: true,
426
- skipValidation: true,
393
+ const pathPrefix = joinUrl(WELLKNOWN_PATH_PREFIX, '/did.json');
394
+ const didResolverWellknownRule = {
395
+ from: { pathPrefix },
396
+ to: {
397
+ port: DEFAULT_DAEMON_PORT,
398
+ type: ROUTING_RULE_TYPES.GENERAL_PROXY,
399
+ interfaceName: BLOCKLET_INTERFACE_WELLKNOWN,
427
400
  },
428
- context
429
- );
401
+ };
402
+
403
+ if (site) {
404
+ if (site.rules.find((r) => r.from.pathPrefix === normalizePathPrefix(pathPrefix))) {
405
+ return false;
406
+ }
407
+
408
+ await routerManager.addRoutingRule(
409
+ {
410
+ id: site.id,
411
+ rule: didResolverWellknownRule,
412
+ skipProtectedRuleChecking: true,
413
+ },
414
+ context
415
+ );
416
+ } else {
417
+ await routerManager.addRoutingSite(
418
+ {
419
+ site: {
420
+ domain: DOMAIN_FOR_INTERNAL_SITE,
421
+ port: await getWellknownSitePort(),
422
+ name: NAME_FOR_WELLKNOWN_SITE,
423
+ rules: [didResolverWellknownRule],
424
+ isProtected: true,
425
+ },
426
+ skipCheckDynamicBlacklist: true,
427
+ skipValidation: true,
428
+ },
429
+ context
430
+ );
431
+ }
430
432
 
431
433
  return true;
432
434
  } catch (err) {
@@ -555,13 +557,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
555
557
  const _ensureBlockletSites = async (blocklet, sites, nodeInfo, context = {}) => {
556
558
  const interfaces = (blocklet.meta.interfaces || []).filter((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
557
559
 
558
- const nodeDomain = await _getIpDnsDomainForNode({ nodeInfo });
559
-
560
- if (!nodeDomain) {
561
- logger.error('IpDnsDomain of abtnode does not found');
562
- return false;
563
- }
564
-
565
560
  const getPrefix = (str) => {
566
561
  if (!str || str === '*') {
567
562
  return '/';
@@ -571,34 +566,57 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
571
566
 
572
567
  const changes = await Promise.all(
573
568
  interfaces.map(async (x) => {
574
- let changed = false; // if state db changed
569
+ const domain = getIpDnsDomainForBlocklet(blocklet, x);
570
+ const pathPrefix = getPrefix(x.prefix);
571
+ const rule = {
572
+ from: { pathPrefix },
573
+ to: {
574
+ port: findInterfacePortByName(blocklet, x.name),
575
+ did: blocklet.meta.did,
576
+ type: ROUTING_RULE_TYPES.BLOCKLET,
577
+ interfaceName: x.name, // root blocklet interface
578
+ },
579
+ isProtected: true,
580
+ };
575
581
 
576
- const domain = getIpDnsDomainForBlocklet(blocklet, x, nodeDomain);
582
+ let changed = false; // if state db changed
577
583
 
578
- if (!(await states.site.domainExists(domain))) {
584
+ const exist = await states.site.findOne({ domain });
585
+ if (!exist) {
579
586
  await routerManager.addRoutingSite(
580
587
  {
581
588
  site: {
582
589
  domain,
583
590
  isProtected: true,
584
- rules: [
585
- {
586
- from: { pathPrefix: getPrefix(x.prefix) },
587
- to: {
588
- port: findInterfacePortByName(blocklet, x.name),
589
- did: blocklet.meta.did,
590
- type: ROUTING_RULE_TYPES.BLOCKLET,
591
- interfaceName: x.name, // root blocklet interface
592
- },
593
- isProtected: true,
594
- },
595
- ],
591
+ rules: [rule],
596
592
  },
597
593
  skipCheckDynamicBlacklist: true,
598
594
  },
599
595
  context
600
596
  );
597
+ logger.info('add routing site', { site: domain });
601
598
 
599
+ changed = true;
600
+ } else {
601
+ const existRule = (exist.rules || []).find((y) => get(y, 'from.pathPrefix') === pathPrefix);
602
+ if (existRule) {
603
+ await routerManager.updateRoutingRule({
604
+ id: exist.id,
605
+ rule: {
606
+ ...rule,
607
+ id: exist.id,
608
+ },
609
+ skipProtectedRuleChecking: true,
610
+ });
611
+ logger.info('update routing rule for site', { site: domain });
612
+ } else {
613
+ await routerManager.addRoutingRule({
614
+ id: exist.id,
615
+ rule,
616
+ skipProtectedRuleChecking: true,
617
+ });
618
+ logger.info('add routing rule for site', { site: domain });
619
+ }
602
620
  changed = true;
603
621
  }
604
622
 
@@ -614,7 +632,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
614
632
  *
615
633
  * @returns {boolean} if routing changed
616
634
  */
617
- const _ensureBlockletRulesForWellknownSite = async (blocklet, sites, nodeInfo, context = {}) => {
635
+ const _ensureBlockletRulesForWellknownSite = async (blocklet, sites, context = {}) => {
618
636
  const wellknownSite = sites.find((x) => x.name === NAME_FOR_WELLKNOWN_SITE);
619
637
  if (!wellknownSite) {
620
638
  return false;
@@ -748,14 +766,13 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
748
766
  };
749
767
 
750
768
  const _removeBlockletSites = async (blocklet, nodeInfo, context = {}) => {
751
- const nodeDomain = await _getIpDnsDomainForNode({ nodeInfo });
752
769
  const interfaces = (blocklet.meta.interfaces || []).filter((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
753
770
 
754
771
  const changes = await Promise.all(
755
772
  interfaces.map(async (x) => {
756
773
  let changed = false;
757
774
 
758
- const domain = getIpDnsDomainForBlocklet(blocklet, x, nodeDomain);
775
+ const domain = getIpDnsDomainForBlocklet(blocklet, x);
759
776
 
760
777
  const site = await states.site.findOne({ domain });
761
778
  if (
@@ -799,11 +816,20 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
799
816
 
800
817
  const sites = await siteState.getSites();
801
818
 
802
- const changes = await Promise.all([
803
- _ensureBlockletRulesForDashboardSite(blocklet, sites, nodeInfo, context),
804
- _ensureBlockletRulesForWellknownSite(blocklet, sites, nodeInfo, context),
819
+ const tasks = [
820
+ _ensureBlockletRulesForWellknownSite(blocklet, sites, context),
805
821
  _ensureBlockletSites(blocklet, sites, nodeInfo, context),
806
- ]);
822
+ ];
823
+
824
+ if (nodeInfo.mode === NODE_MODES.DEBUG || ['e2e', 'test'].includes(process.env.NODE_ENV)) {
825
+ logger.info('Add blocklet endpoint in dashboard site', {
826
+ nodeMode: nodeInfo.mode,
827
+ NODE_ENV: process.env.NODE_ENV,
828
+ });
829
+ tasks.push(_ensureBlockletRulesForDashboardSite(blocklet, sites, nodeInfo, context));
830
+ }
831
+
832
+ const changes = await Promise.all(tasks);
807
833
 
808
834
  return changes.some(Boolean);
809
835
  };
@@ -821,72 +847,14 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
821
847
  return false;
822
848
  }
823
849
 
824
- const interfaces = (blocklet.meta.interfaces || []).filter((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
825
- const hasInterface = (name) => interfaces.some((x) => x.name === name);
826
-
827
- // If no interfaces found, all routing rules should be removed, this rarely happens, but this logic is needed
828
- if (interfaces.length === 0) {
829
- return routerManager.deleteRoutingRulesItemByDid({ did: blocklet.meta.did }, context);
830
- }
831
-
832
- // First, we need ensure system rules for blocklet on dashboard site
833
- let changed = await ensureBlockletRouting(blocklet, context);
834
-
835
- // Then, we need to remove rules whose corresponding interface is not there any more
836
- const sites = await siteState.getRulesByDid(blocklet.meta.did);
837
- for (const site of sites) {
838
- const rulesToRemove = [];
839
- const rulesToUpdate = [];
840
- for (const rule of site.rules) {
841
- if (
842
- rule.to.type !== ROUTING_RULE_TYPES.BLOCKLET ||
843
- !rule.to.interfaceName ||
844
- rule.to.did !== blocklet.meta.did
845
- ) {
846
- // eslint-disable-next-line no-continue
847
- continue;
848
- }
849
-
850
- if (hasInterface(rule.to.interfaceName) === false) {
851
- rulesToRemove.push(rule.id);
852
- }
853
-
854
- if (rule.to.type === ROUTING_RULE_TYPES.BLOCKLET && rule.id === rule.groupId && !rule.isProtected) {
855
- rulesToUpdate.push(rule);
856
- }
857
- }
858
-
859
- for (const rule of rulesToUpdate) {
860
- // eslint-disable-next-line no-await-in-loop
861
- await routerManager.updateRoutingRule({ id: site.id, rule });
862
- changed = true;
863
- }
864
-
865
- if (rulesToRemove.length === 0) {
866
- // eslint-disable-next-line no-continue
867
- continue;
868
- }
869
-
870
- try {
871
- // eslint-disable-next-line no-await-in-loop
872
- await states.site.update(
873
- { _id: site.id },
874
- { $set: { rules: site.rules.filter((x) => rulesToRemove.includes(x.id) === false) } }
875
- );
876
- changed = true;
877
- logger.info('remove routing rule since interface does not exist', { rulesToRemove, did: blocklet.meta.did });
878
- } catch (err) {
879
- logger.error('failed to remove routing rule since interface does not exist', {
880
- rulesToRemove,
881
- did: blocklet.meta.did,
882
- error: err,
883
- });
884
- }
885
- }
886
-
887
- await _removeBlockletSites(blocklet, nodeInfo, context);
850
+ await routerManager.deleteRoutingRulesItemByDid(
851
+ { did: blocklet.meta.did, ruleFilter: (x) => x.isProtected },
852
+ context
853
+ );
854
+ await ensureBlockletRouting(blocklet, context);
888
855
 
889
- return changed;
856
+ // Always return true to trigger update of provider
857
+ return true;
890
858
  };
891
859
 
892
860
  /**
@@ -903,51 +871,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
903
871
  return ruleChanged || siteChanged;
904
872
  };
905
873
 
906
- /**
907
- * Refresh ip of in ip-dns-domain if ip changed
908
- */
909
- const ensureIpDnsRouting = async (context = {}, output) => {
910
- const nodeInfo = await nodeState.read();
911
- const sites = await siteState.getSites();
912
-
913
- const ip = await getAccessibleIp(nodeInfo);
914
- if (!ip) {
915
- logger.error('Cannot get get accessible ip for this node');
916
- return false;
917
- }
918
-
919
- const formattedIp = ip.replace(/\./g, '-');
920
- const suffix = DEFAULT_DASHBOARD_DOMAIN.replace(/^\*/, '');
921
- const domainSuffixRegex = new RegExp(`(\\d+-\\d+-\\d+-\\d+)${suffix.replace(/\./g, '\\.')}$`); // xxx-xxx-xxx-xxx.ip.abtnet.io
922
- const domainSuffix = `${formattedIp}${suffix}`;
923
-
924
- const changes = [];
925
-
926
- sites.forEach((x) => {
927
- const match = domainSuffixRegex.exec(x.domain);
928
- if (match && match[1] !== formattedIp) {
929
- const domain = x.domain.replace(domainSuffixRegex, domainSuffix);
930
- changes.push({ id: x.id, domain });
931
- }
932
- });
933
-
934
- if (changes.length) {
935
- for (const change of changes) {
936
- // eslint-disable-next-line no-await-in-loop
937
- await siteState.update({ _id: change.id }, { $set: { domain: change.domain } });
938
- if (typeof output === 'function') {
939
- output(`Blocklet endpoint was updated: ${change.domain}`);
940
- }
941
- }
942
-
943
- const hash = await takeRoutingSnapshot({ message: 'ensure ip dns routing rules', dryRun: false }, context);
944
- logger.info('take routing snapshot on ensure dashboard routing rules', { changes, hash });
945
- return true;
946
- }
947
-
948
- return false;
949
- };
950
-
951
874
  async function readRoutingSites() {
952
875
  const info = await nodeState.read();
953
876
  let snapshotHash = get(info, 'routing.snapshotHash', null);
@@ -1257,7 +1180,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1257
1180
  getCertificates,
1258
1181
  getSitesFromSnapshot,
1259
1182
  checkDomain,
1260
- ensureIpDnsRouting,
1261
1183
 
1262
1184
  getRoutingCrons: () => [
1263
1185
  {
@@ -48,6 +48,23 @@ const mergeAllowedOrigins = (domain, allowedOrigins) => {
48
48
  return origins;
49
49
  };
50
50
 
51
+ const getRoutingTable = ({ sites, nodeInfo }) => {
52
+ let routingTable = Router.formatSites(sites);
53
+ routingTable = expandSites(routingTable);
54
+
55
+ // put dashboardDomain to last, to let blockletDomain match first
56
+ // e.g.
57
+ // dashboardDomain: 192-168-3-2.ip.abtnet.io
58
+ // blockletDomain: static-demo-xxx-192-168-3-2.ip.abtnet.io
59
+ const dashboardDomain = get(nodeInfo, 'routing.dashboardDomain', '');
60
+ const index = routingTable.findIndex((x) => x.domain === dashboardDomain);
61
+ if (index > -1) {
62
+ routingTable.push(...routingTable.splice(index, 1));
63
+ }
64
+
65
+ return routingTable;
66
+ };
67
+
51
68
  class Router {
52
69
  /**
53
70
  * Router
@@ -77,12 +94,13 @@ class Router {
77
94
 
78
95
  logger.info('updateRoutingTable sites:', { sites });
79
96
 
80
- this.routingTable = Router.formatSites(sites);
97
+ this.routingTable = getRoutingTable({ sites, nodeInfo });
98
+
81
99
  logger.info('updateRoutingTable routingTable:', { routingTable: this.routingTable });
82
100
  logger.info('updateRoutingTable certificates:', { certificates: certificates.map((item) => item.domain) });
83
101
 
84
102
  await this.provider.update({
85
- routingTable: expandSites(this.routingTable),
103
+ routingTable: this.routingTable,
86
104
  certificates,
87
105
  globalHeaders: headers,
88
106
  services,
@@ -218,5 +236,6 @@ Router.flattenSitesToRules = (sites = [], info = {}) => {
218
236
  };
219
237
 
220
238
  Router._expandSites = expandSites; // eslint-disable-line no-underscore-dangle
239
+ Router._getRoutingTable = getRoutingTable; // eslint-disable-line no-underscore-dangle
221
240
 
222
241
  module.exports = Router;
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable no-await-in-loop */
2
2
  /* eslint-disable function-paren-newline */
3
3
  /* eslint-disable no-underscore-dangle */
4
+ const omit = require('lodash/omit');
4
5
  const uniq = require('lodash/uniq');
5
6
  const detectPort = require('detect-port');
6
7
  const Lock = require('@abtnode/util/lib/lock');
@@ -128,8 +129,6 @@ class BlockletState extends BaseState {
128
129
  // get ports
129
130
  await lock.acquire();
130
131
 
131
- delete meta.htmlAst;
132
-
133
132
  const ports = await this.getBlockletPorts({ interfaces: sanitized.interfaces || [] });
134
133
 
135
134
  const children = await this.fillChildrenPorts(this.getChildrenFromMetas(childrenMeta), {
@@ -140,7 +139,7 @@ class BlockletState extends BaseState {
140
139
  this.db.insert(
141
140
  {
142
141
  mode,
143
- meta: sanitized,
142
+ meta: omit(sanitized, ['htmlAst']),
144
143
  status,
145
144
  source,
146
145
  deployedFrom,
@@ -177,13 +176,15 @@ class BlockletState extends BaseState {
177
176
  }
178
177
 
179
178
  try {
179
+ fixPerson(meta);
180
+ fixInterfaces(meta);
181
+ const sanitized = validateBlockletMeta(meta);
182
+
180
183
  // get ports
181
184
  await lock.acquire();
182
185
 
183
- delete meta.htmlAst;
184
-
185
186
  const ports = await this.getBlockletPorts({
186
- interfaces: meta.interfaces || [],
187
+ interfaces: sanitized.interfaces || [],
187
188
  skipOccupiedCheckPorts: getExternalPortsFromMeta(doc.meta),
188
189
  });
189
190
  Object.keys(ports).forEach((p) => {
@@ -197,7 +198,7 @@ class BlockletState extends BaseState {
197
198
  // add to db
198
199
  const newDoc = await this.updateById(doc._id, {
199
200
  $set: {
200
- meta,
201
+ meta: omit(sanitized, ['htmlAst']),
201
202
  source,
202
203
  deployedFrom,
203
204
  children,
@@ -183,17 +183,18 @@ const getBlockletDirs = (blocklet, { rootBlocklet, dataDirs, ensure = false } =
183
183
  };
184
184
 
185
185
  /**
186
- * set 'configs', userEnvironments', 'systemEnvironments' to blocklet
186
+ * set 'configs', configObj', 'environmentObj' to blocklet
187
187
  * @param {*} blocklet
188
188
  * @param {*} configs
189
189
  */
190
190
  const fillBlockletConfigs = (blocklet, configs) => {
191
191
  blocklet.configs = configs || [];
192
- blocklet.userEnvironments = blocklet.configs.reduce((acc, x) => {
192
+ blocklet.configObj = blocklet.configs.reduce((acc, x) => {
193
193
  acc[x.key] = x.value;
194
194
  return acc;
195
195
  }, {});
196
- blocklet.systemEnvironments = (blocklet.environments || []).reduce((acc, x) => {
196
+ blocklet.userEnvironments = blocklet.configObj; // deprecated
197
+ blocklet.environmentObj = (blocklet.environments || []).reduce((acc, x) => {
197
198
  acc[x.key] = x.value;
198
199
  return acc;
199
200
  }, {});
@@ -222,56 +223,67 @@ const ensureBlockletExpanded = async (meta, appDir) => {
222
223
  const getRootSystemEnvironments = (blocklet, nodeInfo) => {
223
224
  const { did, name, title, description } = blocklet.meta;
224
225
  let wallet;
225
- let blockletAppSK;
226
- let blockletAppId;
226
+ let appSk;
227
+ let appId;
228
+ let appName = title || name;
229
+ let appDescription = description || '';
227
230
 
228
231
  if (process.env.NODE_ENV !== 'test') {
229
- const result = getBlockletInfo(blocklet, nodeInfo.sk);
232
+ const keys = Object.keys(BLOCKLET_CONFIGURABLE_KEY);
233
+ const result = getBlockletInfo(
234
+ {
235
+ meta: blocklet.meta,
236
+ environments: keys.map((key) => ({ key, value: blocklet.configObj[key] })).filter((x) => x.value),
237
+ },
238
+ nodeInfo.sk
239
+ );
230
240
  wallet = result.wallet;
231
- blockletAppSK = toHex(wallet.secretKey);
232
- blockletAppId = wallet.toAddress();
241
+ appSk = toHex(wallet.secretKey);
242
+ appId = wallet.toAddress();
243
+ appName = appName || result.name;
244
+ appDescription = appDescription || result.description;
233
245
  } else {
234
- blockletAppSK = 'AppSK:FIXME:WalletWorksFailedInJest';
235
- blockletAppId = 'AppID:FIXME:WalletWorksFailedInJest';
246
+ appSk = 'AppSK:FIXME:WalletWorksFailedInJest';
247
+ appId = 'AppID:FIXME:WalletWorksFailedInJest';
236
248
  }
237
249
 
238
250
  return {
239
251
  BLOCKLET_DID: did,
240
- BLOCKLET_APP_SK: blockletAppSK,
241
- BLOCKLET_APP_ID: blockletAppId,
242
- BLOCKLET_APP_NAME: title || name,
243
- BLOCKLET_APP_DESCRIPTION: description,
252
+ BLOCKLET_APP_SK: appSk,
253
+ BLOCKLET_APP_ID: appId,
254
+ BLOCKLET_APP_NAME: appName,
255
+ BLOCKLET_APP_DESCRIPTION: appDescription,
244
256
  };
245
257
  };
246
258
 
247
- const getOverwrittenEnvironments = (blocklet) => {
259
+ const getOverwrittenEnvironments = (blocklet, nodeInfo) => {
248
260
  const result = {};
249
- if (!blocklet || !blocklet.userEnvironments) {
261
+ if (!blocklet || !blocklet.configObj) {
250
262
  return result;
251
263
  }
252
264
 
253
265
  Object.keys(BLOCKLET_CONFIGURABLE_KEY).forEach((x) => {
254
- if (!blocklet.userEnvironments[x]) {
266
+ if (!blocklet.configObj[x]) {
255
267
  return;
256
268
  }
257
269
 
258
- result[x] = blocklet.userEnvironments[x];
270
+ result[x] = blocklet.configObj[x];
259
271
  });
260
272
 
261
273
  const keys = ['BLOCKLET_APP_SK', 'BLOCKLET_WALLET_TYPE'];
262
- const isAppDidRewritten = keys.some((key) => blocklet.userEnvironments[key]);
274
+ const isAppDidRewritten = keys.some((key) => blocklet.configObj[key]);
263
275
  if (!isAppDidRewritten) {
264
276
  return result;
265
277
  }
266
278
 
267
279
  // We use user configuration here without any validation since the validation is done during update phase
268
- const { wallet } = getBlockletInfo({
269
- meta: blocklet.meta,
270
- environments: keys.map((key) => ({
271
- key,
272
- value: blocklet.userEnvironments[key] || blocklet.systemEnvironments[key],
273
- })),
274
- });
280
+ const { wallet } = getBlockletInfo(
281
+ {
282
+ meta: blocklet.meta,
283
+ environments: keys.map((key) => ({ key, value: blocklet.configObj[key] })).filter((x) => x.value),
284
+ },
285
+ nodeInfo.sk
286
+ );
275
287
  result.BLOCKLET_APP_ID = wallet.toAddress();
276
288
 
277
289
  return result;
@@ -297,6 +309,21 @@ const getSystemEnvironments = (blocklet) => {
297
309
  };
298
310
  };
299
311
 
312
+ const getRuntimeEnvironments = (blocklet, nodeEnvironments) => {
313
+ // pm2 will force inject env variables of daemon process to blocklet process
314
+ // we can only rewrite these private env variables to empty
315
+ const safeNodeEnvironments = nodePrivateEnvs.reduce((o, x) => {
316
+ o[x] = '';
317
+ return o;
318
+ }, {});
319
+
320
+ return {
321
+ ...blocklet.environmentObj,
322
+ ...nodeEnvironments,
323
+ ...safeNodeEnvironments,
324
+ };
325
+ };
326
+
300
327
  const isUsefulError = (err) => err && err.message !== 'process or namespace not found';
301
328
 
302
329
  const getHealthyCheckTimeout = (blocklet, { checkHealthImmediately } = {}) => {
@@ -376,14 +403,7 @@ const startBlockletProcess = async (blocklet, { preStart = noop, nodeEnvironment
376
403
  const { appMain, appId, appCwd, logsDir } = b.env;
377
404
 
378
405
  // get env
379
- // pm2 will force inject env variables of daemon process to blocklet process
380
- // we can only rewrite these private env variables to empty
381
- const safeNodeEnvironments = nodePrivateEnvs.reduce((o, x) => {
382
- o[x] = '';
383
- return o;
384
- }, {});
385
-
386
- const env = { ...nodeEnvironments, ...b.systemEnvironments, ...b.userEnvironments, ...safeNodeEnvironments };
406
+ const env = getRuntimeEnvironments(b, nodeEnvironments);
387
407
 
388
408
  // run hook
389
409
  await preStart(b);
@@ -408,7 +428,7 @@ const startBlockletProcess = async (blocklet, { preStart = noop, nodeEnvironment
408
428
 
409
429
  const clusterMode = get(b.meta, 'capabilities.clusterMode', false);
410
430
  if (clusterMode && blocklet.mode !== BLOCKLET_MODES.DEVELOPMENT) {
411
- const clusterSize = Number(blocklet.userEnvironments.BLOCKLET_CLUSTER_SIZE) || Number.POSITIVE_INFINITY;
431
+ const clusterSize = Number(blocklet.configObj.BLOCKLET_CLUSTER_SIZE) || Number.POSITIVE_INFINITY;
412
432
  options.execMode = 'cluster';
413
433
  options.mergeLogs = true;
414
434
  options.instances = Math.min(os.cpus().length, clusterSize);
@@ -872,6 +892,7 @@ module.exports = {
872
892
  getRootSystemEnvironments,
873
893
  getSystemEnvironments,
874
894
  getOverwrittenEnvironments,
895
+ getRuntimeEnvironments,
875
896
  validateBlocklet,
876
897
  fillBlockletConfigs,
877
898
  ensureBlockletExpanded,
@@ -0,0 +1,41 @@
1
+ const joinUrl = require('url-join');
2
+ const axios = require('@abtnode/util/lib/axios');
3
+ const { DEFAULT_DASHBOARD_DOMAIN, DEFAULT_ADMIN_PATH } = require('@abtnode/constant');
4
+ const { get: getIp } = require('./ip');
5
+
6
+ const getNodeDomain = (ip) => (ip ? DEFAULT_DASHBOARD_DOMAIN.replace(/^\*/, ip.replace(/\./g, '-')) : '');
7
+
8
+ let accessibleIp = null; // cache
9
+
10
+ const timeout = process.env.NODE_ENV === 'test' ? 500 : 5000;
11
+
12
+ // check if node dashboard https endpoint return 2xx
13
+ // FIXME: check connected by wellknown endpoint
14
+ const checkConnected = async ({ ip, nodeInfo }) => {
15
+ const { adminPath = DEFAULT_ADMIN_PATH } = nodeInfo.routing || {};
16
+ const origin = `https://${getNodeDomain(ip)}`;
17
+ const endpoint = joinUrl(origin, adminPath);
18
+ await axios.get(endpoint, { timeout });
19
+ };
20
+
21
+ /**
22
+ * Get accessible external ip of abtnode
23
+ */
24
+ const getAccessibleExternalNodeIp = async (nodeInfo) => {
25
+ const { external } = await getIp();
26
+
27
+ if (accessibleIp === external) {
28
+ return accessibleIp;
29
+ }
30
+
31
+ try {
32
+ await checkConnected({ ip: external, nodeInfo });
33
+ accessibleIp = external;
34
+ } catch {
35
+ accessibleIp = null;
36
+ }
37
+
38
+ return accessibleIp;
39
+ };
40
+
41
+ module.exports = getAccessibleExternalNodeIp;
@@ -1,14 +1,17 @@
1
1
  const slugify = require('slugify');
2
+ const { DEFAULT_IP_DNS_DOMAIN_SUFFIX, SLOT_FOR_IP_DNS_SITE } = require('@abtnode/constant');
2
3
 
3
4
  const formatName = (name) => slugify(name.replace(/^[@./-]/, '').replace(/[@./]/g, '-'));
4
5
 
5
6
  const hiddenInterfaceNames = ['public', 'publicUrl'];
6
7
 
7
- const getIpDnsDomainForBlocklet = (blocklet, blockletInterface, nodeIpDnsDomain) => {
8
+ const getIpDnsDomainForBlocklet = (blocklet, blockletInterface) => {
8
9
  const nameSuffix = blocklet.meta.did.slice(-3).toLowerCase();
9
10
  const iName = hiddenInterfaceNames.includes(blockletInterface.name) ? '' : blockletInterface.name.toLowerCase();
10
11
 
11
- return `${formatName(blocklet.meta.name)}-${nameSuffix}${iName ? '-' : ''}${iName}-${nodeIpDnsDomain}`;
12
+ return `${formatName(blocklet.meta.name)}-${nameSuffix}${
13
+ iName ? '-' : ''
14
+ }${iName}-${SLOT_FOR_IP_DNS_SITE}.${DEFAULT_IP_DNS_DOMAIN_SUFFIX}`;
12
15
  };
13
16
 
14
17
  module.exports = getIpDnsDomainForBlocklet;
package/lib/util/index.js CHANGED
@@ -27,6 +27,8 @@ const {
27
27
  DEFAULT_HTTP_PORT,
28
28
  DEFAULT_HTTPS_PORT,
29
29
  ROUTING_RULE_TYPES,
30
+ SLOT_FOR_IP_DNS_SITE,
31
+ DEFAULT_IP_DNS_DOMAIN_SUFFIX,
30
32
  } = require('@abtnode/constant');
31
33
 
32
34
  const DEFAULT_WELLKNOWN_PORT = 8088;
@@ -71,34 +73,42 @@ const getInterfaceUrl = ({ baseUrl, url, version }) => {
71
73
 
72
74
  const trimSlash = (str = '') => str.replace(/^\/+/, '').replace(/\/+$/, '');
73
75
 
74
- const getBlockletHost = ({ domain, context }) => {
76
+ const getBlockletHost = ({ domain, context, nodeIp }) => {
77
+ const { protocol, port } = context || {};
78
+
75
79
  if (domain === DOMAIN_FOR_DEFAULT_SITE) {
76
80
  return '';
77
81
  }
78
82
 
79
83
  let tmpDomain = domain;
80
84
  if ([DOMAIN_FOR_IP_SITE, DOMAIN_FOR_IP_SITE_REGEXP].includes(domain) || !domain) {
81
- tmpDomain = context.hostname;
85
+ tmpDomain = context.hostname || '';
82
86
  }
83
87
 
84
- if (!context.port) {
85
- return tmpDomain;
88
+ if (tmpDomain.includes(SLOT_FOR_IP_DNS_SITE)) {
89
+ const ipRegex = /\d+[-.]\d+[-.]\d+[-.]\d+/;
90
+ const match = ipRegex.exec(context.hostname);
91
+ if (match) {
92
+ const ip = match[0].replace(/\./g, '-');
93
+ tmpDomain = tmpDomain.replace(SLOT_FOR_IP_DNS_SITE, ip);
94
+ } else if (nodeIp) {
95
+ tmpDomain = tmpDomain.replace(SLOT_FOR_IP_DNS_SITE, nodeIp.replace(/\./g, '-'));
96
+ }
86
97
  }
87
98
 
88
- const tmpPort = Number(context.port);
89
- if (context.protocol === 'https') {
90
- if (tmpPort === 443) {
91
- return tmpDomain; // 不展示 https 中的 443 端口
92
- }
99
+ if (!port) {
100
+ return tmpDomain;
101
+ }
93
102
 
94
- return '';
103
+ if (protocol === 'https' && Number(port) === 443) {
104
+ return tmpDomain; // 不展示 https 中的 443 端口
95
105
  }
96
106
 
97
- if (tmpPort === 80) {
107
+ if (Number(port) === 80) {
98
108
  return tmpDomain; // 不展示 http 中的 80 端
99
109
  }
100
110
 
101
- return `${tmpDomain}:${context.port}`;
111
+ return `${tmpDomain}:${port}`;
102
112
  };
103
113
 
104
114
  /**
@@ -145,15 +155,19 @@ const getAuthConfig = (rule, blocklet) => {
145
155
  return auth.config;
146
156
  };
147
157
 
148
- const getBlockletBaseUrls = ({ routingEnabled = false, port, sites = [], context = {}, blocklet }) => {
149
- const protocol = 'http'; // TODO: 这里固定为 http, 因为判断 url 是不是 https 和证书相关,这里实现的话比较复杂
158
+ const getBlockletBaseUrls = ({ routingEnabled = false, port, sites = [], context = {}, blocklet, nodeIp }) => {
150
159
  let baseUrls = [];
151
160
 
152
161
  if (routingEnabled && Array.isArray(sites) && sites.length > 0) {
153
162
  baseUrls = sites
154
163
  .map((site) => {
155
- const host = getBlockletHost({ domain: site.from.domain, context });
164
+ const host = getBlockletHost({ domain: site.from.domain, context, nodeIp });
156
165
  if (host) {
166
+ let protocol = 'http'; // TODO: 这里固定为 http, 因为判断 url 是不是 https 和证书相关,这里实现的话比较复杂
167
+ if (host.includes(DEFAULT_IP_DNS_DOMAIN_SUFFIX)) {
168
+ protocol = 'https';
169
+ }
170
+
157
171
  return {
158
172
  ruleId: site.id,
159
173
  baseUrl: `${protocol}://${host}/${trimSlash(site.from.pathPrefix)}`,
@@ -167,14 +181,14 @@ const getBlockletBaseUrls = ({ routingEnabled = false, port, sites = [], context
167
181
  .filter(Boolean);
168
182
  }
169
183
 
170
- if (!baseUrls.length && isIp(context.hostname) && protocol !== 'https') {
171
- baseUrls = [{ baseUrl: `${protocol}://${context.hostname}:${port}` }];
184
+ if (!baseUrls.length && isIp(context.hostname)) {
185
+ baseUrls = [{ baseUrl: `http://${context.hostname}:${port}` }];
172
186
  }
173
187
 
174
188
  return baseUrls;
175
189
  };
176
190
 
177
- const getBlockletInterfaces = ({ blocklet, context, nodeInfo, sites }) => {
191
+ const getBlockletInterfaces = ({ blocklet, context, nodeInfo, sites, nodeIp }) => {
178
192
  const interfaces = [];
179
193
  (blocklet.meta.interfaces || []).forEach((x) => {
180
194
  if (x.port && x.port.external) {
@@ -202,6 +216,7 @@ const getBlockletInterfaces = ({ blocklet, context, nodeInfo, sites }) => {
202
216
  }),
203
217
  context,
204
218
  blocklet,
219
+ nodeIp,
205
220
  });
206
221
 
207
222
  baseUrls.forEach(({ baseUrl, ruleId, interfaceName, authConfig }) => {
@@ -140,7 +140,11 @@ const doUpgrade = async (session) => {
140
140
  await goNextState(NODE_UPGRADE_PROGRESS.COMPLETE);
141
141
  await sleep(5000);
142
142
  await states.node.updateNodeInfo({ nextVersion: '', version: to, upgradeSessionId: '' });
143
- await states.node.exitMode(NODE_MODES.MAINTENANCE);
143
+ try {
144
+ await states.node.exitMode(NODE_MODES.MAINTENANCE);
145
+ } catch (error) {
146
+ logger.error('Failed to exit maintenance mode', { error });
147
+ }
144
148
  }
145
149
  await lock.release();
146
150
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.4.10",
6
+ "version": "1.4.14",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,27 +19,28 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@abtnode/constant": "1.4.10",
23
- "@abtnode/cron": "1.4.10",
24
- "@abtnode/event-hub": "1.4.10",
25
- "@abtnode/logger": "1.4.10",
26
- "@abtnode/queue": "1.4.10",
27
- "@abtnode/rbac": "1.4.10",
28
- "@abtnode/router-provider": "1.4.10",
29
- "@abtnode/static-server": "1.4.10",
30
- "@abtnode/timemachine": "1.4.10",
31
- "@abtnode/util": "1.4.10",
32
- "@arcblock/did": "^1.13.2",
22
+ "@abtnode/constant": "1.4.14",
23
+ "@abtnode/cron": "1.4.14",
24
+ "@abtnode/event-hub": "1.4.14",
25
+ "@abtnode/logger": "1.4.14",
26
+ "@abtnode/queue": "1.4.14",
27
+ "@abtnode/rbac": "1.4.14",
28
+ "@abtnode/router-provider": "1.4.14",
29
+ "@abtnode/static-server": "1.4.14",
30
+ "@abtnode/timemachine": "1.4.14",
31
+ "@abtnode/util": "1.4.14",
32
+ "@arcblock/did": "^1.13.15",
33
33
  "@arcblock/pm2-events": "^0.0.5",
34
- "@blocklet/meta": "1.4.10",
34
+ "@arcblock/vc": "^1.13.15",
35
+ "@blocklet/meta": "1.4.14",
35
36
  "@fidm/x509": "^1.2.1",
36
37
  "@nedb/core": "^1.1.0",
37
38
  "@nedb/multi": "^1.1.0",
38
- "@ocap/mcrypto": "^1.13.2",
39
- "@ocap/util": "^1.13.2",
40
- "@ocap/wallet": "^1.13.2",
39
+ "@ocap/mcrypto": "^1.13.15",
40
+ "@ocap/util": "^1.13.15",
41
+ "@ocap/wallet": "^1.13.15",
41
42
  "@slack/webhook": "^5.0.3",
42
- "axios": "^0.21.1",
43
+ "axios": "^0.21.4",
43
44
  "axon": "^2.0.3",
44
45
  "chalk": "^4.0.0",
45
46
  "deep-diff": "^1.0.2",
@@ -71,5 +72,5 @@
71
72
  "express": "^4.17.1",
72
73
  "jest": "^26.4.2"
73
74
  },
74
- "gitHead": "1a03df394c4c430975a369ebb4dadcf2e2e085bb"
75
+ "gitHead": "9176b462ff92c787e18a8b218f06a451aad4cf1d"
75
76
  }
@@ -1,42 +0,0 @@
1
- const joinUrl = require('url-join');
2
- const axios = require('@abtnode/util/lib/axios');
3
- const getIp = require('@abtnode/util/lib/get-ip');
4
- const { DEFAULT_DASHBOARD_DOMAIN, DEFAULT_ADMIN_PATH } = require('@abtnode/constant');
5
-
6
- const getNodeDomain = (ip) => (ip ? DEFAULT_DASHBOARD_DOMAIN.replace(/^\*/, ip.replace(/\./g, '-')) : '');
7
-
8
- const getAccessibleIp = async ({ nodeInfo = {}, timeout = 5000 } = {}) => {
9
- const { adminPath = DEFAULT_ADMIN_PATH } = nodeInfo.routing || {};
10
-
11
- // check if node dashboard https endpoint return 2xx
12
- // FIXME: check connected by wellknown endpoint
13
- const checkConnected = async ({ ip }) => {
14
- const origin = `https://${getNodeDomain(ip)}`;
15
- const endpoint = joinUrl(origin, adminPath);
16
- await axios.get(endpoint, { timeout });
17
- };
18
-
19
- const { internal, external } = await getIp({ timeout });
20
-
21
- if (!external) {
22
- return internal;
23
- }
24
-
25
- try {
26
- await checkConnected({ ip: external });
27
- return external;
28
- } catch {
29
- return internal;
30
- }
31
- };
32
-
33
- const getAccessibleIpDnsDomainForNode = async ({ nodeInfo = {}, timeout = 5000 } = {}) => {
34
- const accessibleIp = await getAccessibleIp({ nodeInfo, timeout });
35
-
36
- return getNodeDomain(accessibleIp);
37
- };
38
-
39
- module.exports = {
40
- getAccessibleIp,
41
- getAccessibleIpDnsDomainForNode,
42
- };