@abtnode/core 1.4.12 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/api/team.js CHANGED
@@ -601,24 +601,20 @@ class TeamAPI extends EventEmitter {
601
601
  const rbac = await this.getRBAC(did);
602
602
 
603
603
  const oldPermissions = await this.getPermissions({ teamDid: did });
604
- await Promise.all(
605
- (oldPermissions || []).map(async ({ name, isProtected }) => {
606
- if (isProtected) {
607
- await rbac.removePermission(name);
608
- }
609
- })
610
- );
611
604
 
612
605
  await Promise.all(
613
606
  (interfaces || []).map(async ({ name, type }) => {
607
+ const permissionName = genPermissionName(name);
614
608
  if (type === 'web') {
615
- await rbac.createPermission({
616
- name: genPermissionName(name),
617
- description: `Access resources under the ${name} interface`,
618
- extra: {
619
- isProtected: true,
620
- },
621
- });
609
+ if (!oldPermissions.some((x) => x.name === permissionName)) {
610
+ await rbac.createPermission({
611
+ name: permissionName,
612
+ description: `Access resources under the ${name} interface`,
613
+ extra: {
614
+ isProtected: true,
615
+ },
616
+ });
617
+ }
622
618
  }
623
619
  })
624
620
  );
@@ -34,6 +34,7 @@ const {
34
34
  fromBlockletSource,
35
35
  } = require('@blocklet/meta/lib/constants');
36
36
  const util = require('../../util');
37
+ const getAccessibleExternalNodeIp = require('../../util/get-accessible-external-node-ip');
37
38
  const {
38
39
  forEachBlocklet,
39
40
  getBlockletDirs,
@@ -900,7 +901,8 @@ class BlockletManager extends BaseBlockletManager {
900
901
  // Put shorter url first
901
902
  return a.from.pathPrefix.length < b.from.pathPrefix ? 1 : -1;
902
903
  });
903
- blocklet.interfaces = getBlockletInterfaces({ blocklet, context, nodeInfo, sites: routingRules });
904
+ const nodeIp = await getAccessibleExternalNodeIp(nodeInfo);
905
+ blocklet.interfaces = getBlockletInterfaces({ blocklet, context, nodeInfo, sites: routingRules, nodeIp });
904
906
 
905
907
  try {
906
908
  const { appId } = blocklet.meta.group === BlockletGroup.gateway ? blocklet.children[0].env : blocklet.env;
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,
@@ -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),
@@ -0,0 +1,34 @@
1
+ const { SLOT_FOR_IP_DNS_SITE } = require('@abtnode/constant');
2
+
3
+ module.exports = async ({ states, node, printInfo }) => {
4
+ printInfo('Migrate ip dns site for blocklet...');
5
+
6
+ const sites = await states.site.getSites();
7
+ let changed = false;
8
+
9
+ // remove system blocklet site of ip-dns-domain if ip is not 888-888-888-888. e.g. static-demo-abc-192-168-3-28.ip.abtnet.io
10
+ // keep system blocklet site of ip-dns-domain only if ip is 888-888-888-888. e.g. static-demo-abc-888-888-888-888.ip.abtnet.io
11
+ for (const site of sites) {
12
+ if (site.isProtected) {
13
+ const ipRegex = /\w{3}-(\d+-\d+-\d+-\d+)\.ip\.abtnet\.io$/;
14
+ const match = ipRegex.exec(site.domain);
15
+ if (match) {
16
+ if (match[1] !== SLOT_FOR_IP_DNS_SITE) {
17
+ // eslint-disable-next-line no-await-in-loop
18
+ await states.site.remove({ _id: site.id });
19
+ printInfo(`Delete site: ${site.domain}`);
20
+ changed = true;
21
+ }
22
+ }
23
+ }
24
+ }
25
+
26
+ if (changed) {
27
+ await node.takeRoutingSnapshot({
28
+ message: 'Migrate ip dns site for blocklet',
29
+ dryRun: false,
30
+ handleRouting: false,
31
+ });
32
+ printInfo('Take routing snapshot');
33
+ }
34
+ };
@@ -24,7 +24,6 @@ const {
24
24
  ROUTING_RULE_TYPES,
25
25
  CERTIFICATE_EXPIRES_OFFSET,
26
26
  CERTIFICATE_EXPIRES_WARNING_OFFSET,
27
- DEFAULT_DASHBOARD_DOMAIN,
28
27
  DEFAULT_DAEMON_PORT,
29
28
  } = require('@abtnode/constant');
30
29
  const {
@@ -45,7 +44,6 @@ const {
45
44
  getWellknownSitePort,
46
45
  } = require('../util');
47
46
  const { getServicesFromBlockletInterface } = require('../util/service');
48
- const { getAccessibleIp, getAccessibleIpDnsDomainForNode } = require('../util/get-accessible-ip');
49
47
  const getIpDnsDomainForBlocklet = require('../util/get-ip-dns-domain-for-blocklet');
50
48
 
51
49
  const Router = require('./index');
@@ -132,6 +130,27 @@ const attachInterfaceUrls = async ({ sites = [], context }) => {
132
130
  });
133
131
  };
134
132
 
133
+ const addCorsToSite = (site, rawUrl) => {
134
+ if (!site || !rawUrl) {
135
+ return;
136
+ }
137
+ try {
138
+ const url = new URL(rawUrl);
139
+ if (!Array.isArray(site.corsAllowedOrigins)) {
140
+ site.corsAllowedOrigins = [];
141
+ }
142
+
143
+ if (!site.corsAllowedOrigins.includes(url.hostname)) {
144
+ site.corsAllowedOrigins.push(url.hostname);
145
+ }
146
+ } catch (err) {
147
+ // Do nothing
148
+ }
149
+ };
150
+
151
+ const isBasicSite = (domain) =>
152
+ [DOMAIN_FOR_INTERNAL_SITE, DOMAIN_FOR_IP_SITE, DOMAIN_FOR_DEFAULT_SITE, DOMAIN_FOR_IP_SITE_REGEXP].includes(domain);
153
+
135
154
  const ensureLatestNodeInfo = async (sites = [], { withDefaultCors = true } = {}) => {
136
155
  const info = await states.node.read();
137
156
  return sites.map((site) => {
@@ -152,26 +171,11 @@ const ensureLatestNodeInfo = async (sites = [], { withDefaultCors = true } = {})
152
171
  site.domain = DOMAIN_FOR_IP_SITE_REGEXP;
153
172
 
154
173
  if (withDefaultCors) {
155
- const addDefaultCors = (rawUrl) => {
156
- if (!rawUrl) {
157
- return;
158
- }
159
- try {
160
- const url = new URL(rawUrl);
161
- if (!Array.isArray(site.corsAllowedOrigins)) {
162
- site.corsAllowedOrigins = [];
163
- }
164
-
165
- site.corsAllowedOrigins.push(url.hostname);
166
- } catch (err) {
167
- // Do nothing
168
- }
169
- };
170
174
  // Allow CORS from "Install on ABT Node"
171
- addDefaultCors(info.registerUrl);
175
+ addCorsToSite(site, info.registerUrl);
172
176
 
173
177
  // Allow CORS from "Web Wallet"
174
- addDefaultCors(info.webWalletUrl);
178
+ addCorsToSite(site, info.webWalletUrl);
175
179
  }
176
180
  }
177
181
 
@@ -214,14 +218,7 @@ const ensureWellknownRule = async (sites) => {
214
218
 
215
219
  for (const site of tempSites) {
216
220
  // 不向 default site & ip site & wellknown site 添加 wellknown rule
217
- const isBasicSite = [
218
- DOMAIN_FOR_INTERNAL_SITE,
219
- DOMAIN_FOR_IP_SITE,
220
- DOMAIN_FOR_DEFAULT_SITE,
221
- DOMAIN_FOR_IP_SITE_REGEXP,
222
- ].includes(site.domain);
223
-
224
- if (!isBasicSite) {
221
+ if (!isBasicSite(site.domain)) {
225
222
  const isExists = site.rules.find((x) => x.from.pathPrefix === WELLKNOWN_PATH_PREFIX);
226
223
 
227
224
  if (!isExists) {
@@ -241,9 +238,21 @@ const ensureWellknownRule = async (sites) => {
241
238
  return tempSites;
242
239
  };
243
240
 
241
+ const ensureCorsForWebWallet = async (sites) => {
242
+ const info = await states.node.read();
243
+ for (const site of sites) {
244
+ if (!isBasicSite(site.domain)) {
245
+ // Allow CORS from "Web Wallet"
246
+ addCorsToSite(site, info.webWalletUrl);
247
+ }
248
+ }
249
+ return sites;
250
+ };
251
+
244
252
  const ensureLatestInfo = async (sites = [], { withDefaultCors = true } = {}) => {
245
253
  let result = await ensureLatestNodeInfo(sites, { withDefaultCors });
246
254
  result = await ensureWellknownRule(result);
255
+ result = await ensureCorsForWebWallet(result);
247
256
  return ensureLatestInterfaceInfo(result);
248
257
  };
249
258
 
@@ -321,27 +330,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
321
330
  const httpsCertState = states.certificate;
322
331
  const notification = states.notification;
323
332
 
324
- let _nodeDomainRequest = null; // singleton of node-domain-request
325
- const _getIpDnsDomainForNode = ({ nodeInfo = {} } = {}) => {
326
- const timeout = process.env.NODE_ENV === 'test' ? 500 : 5000;
327
- if (_nodeDomainRequest) {
328
- return _nodeDomainRequest;
329
- }
330
-
331
- const req = getAccessibleIpDnsDomainForNode({ nodeInfo, timeout });
332
- _nodeDomainRequest = req;
333
-
334
- req
335
- .then(() => {
336
- _nodeDomainRequest = null;
337
- })
338
- .catch(() => {
339
- _nodeDomainRequest = null;
340
- });
341
-
342
- return req;
343
- };
344
-
345
333
  // site level duplication detection
346
334
  const hasRuleByPrefix = (site, value) => site.rules.find((x) => x.isProtected && get(x, 'from.pathPrefix') === value);
347
335
 
@@ -580,13 +568,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
580
568
  const _ensureBlockletSites = async (blocklet, sites, nodeInfo, context = {}) => {
581
569
  const interfaces = (blocklet.meta.interfaces || []).filter((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
582
570
 
583
- const nodeDomain = await _getIpDnsDomainForNode({ nodeInfo });
584
-
585
- if (!nodeDomain) {
586
- logger.error('IpDnsDomain of abtnode does not found');
587
- return false;
588
- }
589
-
590
571
  const getPrefix = (str) => {
591
572
  if (!str || str === '*') {
592
573
  return '/';
@@ -596,34 +577,57 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
596
577
 
597
578
  const changes = await Promise.all(
598
579
  interfaces.map(async (x) => {
599
- let changed = false; // if state db changed
580
+ const domain = getIpDnsDomainForBlocklet(blocklet, x);
581
+ const pathPrefix = getPrefix(x.prefix);
582
+ const rule = {
583
+ from: { pathPrefix },
584
+ to: {
585
+ port: findInterfacePortByName(blocklet, x.name),
586
+ did: blocklet.meta.did,
587
+ type: ROUTING_RULE_TYPES.BLOCKLET,
588
+ interfaceName: x.name, // root blocklet interface
589
+ },
590
+ isProtected: true,
591
+ };
600
592
 
601
- const domain = getIpDnsDomainForBlocklet(blocklet, x, nodeDomain);
593
+ let changed = false; // if state db changed
602
594
 
603
- if (!(await states.site.domainExists(domain))) {
595
+ const exist = await states.site.findOne({ domain });
596
+ if (!exist) {
604
597
  await routerManager.addRoutingSite(
605
598
  {
606
599
  site: {
607
600
  domain,
608
601
  isProtected: true,
609
- rules: [
610
- {
611
- from: { pathPrefix: getPrefix(x.prefix) },
612
- to: {
613
- port: findInterfacePortByName(blocklet, x.name),
614
- did: blocklet.meta.did,
615
- type: ROUTING_RULE_TYPES.BLOCKLET,
616
- interfaceName: x.name, // root blocklet interface
617
- },
618
- isProtected: true,
619
- },
620
- ],
602
+ rules: [rule],
621
603
  },
622
604
  skipCheckDynamicBlacklist: true,
623
605
  },
624
606
  context
625
607
  );
608
+ logger.info('add routing site', { site: domain });
626
609
 
610
+ changed = true;
611
+ } else {
612
+ const existRule = (exist.rules || []).find((y) => get(y, 'from.pathPrefix') === pathPrefix);
613
+ if (existRule) {
614
+ await routerManager.updateRoutingRule({
615
+ id: exist.id,
616
+ rule: {
617
+ ...rule,
618
+ id: exist.id,
619
+ },
620
+ skipProtectedRuleChecking: true,
621
+ });
622
+ logger.info('update routing rule for site', { site: domain });
623
+ } else {
624
+ await routerManager.addRoutingRule({
625
+ id: exist.id,
626
+ rule,
627
+ skipProtectedRuleChecking: true,
628
+ });
629
+ logger.info('add routing rule for site', { site: domain });
630
+ }
627
631
  changed = true;
628
632
  }
629
633
 
@@ -773,14 +777,13 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
773
777
  };
774
778
 
775
779
  const _removeBlockletSites = async (blocklet, nodeInfo, context = {}) => {
776
- const nodeDomain = await _getIpDnsDomainForNode({ nodeInfo });
777
780
  const interfaces = (blocklet.meta.interfaces || []).filter((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
778
781
 
779
782
  const changes = await Promise.all(
780
783
  interfaces.map(async (x) => {
781
784
  let changed = false;
782
785
 
783
- const domain = getIpDnsDomainForBlocklet(blocklet, x, nodeDomain);
786
+ const domain = getIpDnsDomainForBlocklet(blocklet, x);
784
787
 
785
788
  const site = await states.site.findOne({ domain });
786
789
  if (
@@ -824,46 +827,44 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
824
827
 
825
828
  const sites = await siteState.getSites();
826
829
 
827
- const changes = await Promise.all([
828
- _ensureBlockletRulesForDashboardSite(blocklet, sites, nodeInfo, context),
830
+ const tasks = [
829
831
  _ensureBlockletRulesForWellknownSite(blocklet, sites, context),
830
832
  _ensureBlockletSites(blocklet, sites, nodeInfo, context),
831
- ]);
833
+ ];
834
+
835
+ if (nodeInfo.mode === NODE_MODES.DEBUG || ['e2e', 'test'].includes(process.env.NODE_ENV)) {
836
+ logger.info('Add blocklet endpoint in dashboard site', {
837
+ nodeMode: nodeInfo.mode,
838
+ NODE_ENV: process.env.NODE_ENV,
839
+ });
840
+ tasks.push(_ensureBlockletRulesForDashboardSite(blocklet, sites, nodeInfo, context));
841
+ }
842
+
843
+ const changes = await Promise.all(tasks);
832
844
 
833
845
  return changes.some(Boolean);
834
846
  };
835
847
 
836
848
  /**
837
- * Update routing for blocklet when blocklet is upgraded
838
- * This function should be called after `ensureDashboardRouting`
849
+ * remove custom rules of blocklet in old interface does not exist
850
+ * update custom rules of blocklet
839
851
  *
840
852
  * @returns {boolean} if routing changed
841
853
  */
842
- const ensureBlockletRoutingForUpgrade = async (blocklet, context = {}) => {
843
- const nodeInfo = await nodeState.read();
844
- const provider = getProviderFromNodeInfo(nodeInfo);
845
- if (provider === ROUTER_PROVIDER_NONE) {
846
- return false;
847
- }
848
-
854
+ const ensureBlockletCustomRouting = async (blocklet) => {
849
855
  const interfaces = (blocklet.meta.interfaces || []).filter((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
850
856
  const hasInterface = (name) => interfaces.some((x) => x.name === name);
851
-
852
- // If no interfaces found, all routing rules should be removed, this rarely happens, but this logic is needed
853
- if (interfaces.length === 0) {
854
- return routerManager.deleteRoutingRulesItemByDid({ did: blocklet.meta.did }, context);
855
- }
856
-
857
- // First, we need ensure system rules for blocklet on dashboard site
858
- let changed = await ensureBlockletRouting(blocklet, context);
859
-
860
- // Then, we need to remove rules whose corresponding interface is not there any more
861
857
  const sites = await siteState.getRulesByDid(blocklet.meta.did);
858
+ let changed = false;
859
+
862
860
  for (const site of sites) {
863
861
  const rulesToRemove = [];
864
862
  const rulesToUpdate = [];
863
+
864
+ // get rule to remove and to update
865
865
  for (const rule of site.rules) {
866
866
  if (
867
+ rule.isProtected ||
867
868
  rule.to.type !== ROUTING_RULE_TYPES.BLOCKLET ||
868
869
  !rule.to.interfaceName ||
869
870
  rule.to.did !== blocklet.meta.did
@@ -874,103 +875,77 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
874
875
 
875
876
  if (hasInterface(rule.to.interfaceName) === false) {
876
877
  rulesToRemove.push(rule.id);
877
- }
878
-
879
- if (rule.to.type === ROUTING_RULE_TYPES.BLOCKLET && rule.id === rule.groupId && !rule.isProtected) {
878
+ } else {
880
879
  rulesToUpdate.push(rule);
881
880
  }
882
881
  }
883
882
 
883
+ // update rule
884
884
  for (const rule of rulesToUpdate) {
885
885
  // eslint-disable-next-line no-await-in-loop
886
886
  await routerManager.updateRoutingRule({ id: site.id, rule });
887
887
  changed = true;
888
888
  }
889
889
 
890
- if (rulesToRemove.length === 0) {
891
- // eslint-disable-next-line no-continue
892
- continue;
893
- }
894
-
895
- try {
896
- // eslint-disable-next-line no-await-in-loop
897
- await states.site.update(
898
- { _id: site.id },
899
- { $set: { rules: site.rules.filter((x) => rulesToRemove.includes(x.id) === false) } }
900
- );
901
- changed = true;
902
- logger.info('remove routing rule since interface does not exist', { rulesToRemove, did: blocklet.meta.did });
903
- } catch (err) {
904
- logger.error('failed to remove routing rule since interface does not exist', {
905
- rulesToRemove,
906
- did: blocklet.meta.did,
907
- error: err,
908
- });
890
+ // delete rule
891
+ if (rulesToRemove.length > 0) {
892
+ try {
893
+ // eslint-disable-next-line no-await-in-loop
894
+ await states.site.update(
895
+ { _id: site.id },
896
+ { $set: { rules: site.rules.filter((x) => rulesToRemove.includes(x.id) === false) } }
897
+ );
898
+ changed = true;
899
+ logger.info('remove routing rule since interface does not exist', { rulesToRemove, did: blocklet.meta.did });
900
+ } catch (err) {
901
+ logger.error('failed to remove routing rule since interface does not exist', {
902
+ rulesToRemove,
903
+ did: blocklet.meta.did,
904
+ error: err,
905
+ });
906
+ }
909
907
  }
910
908
  }
911
909
 
912
- await _removeBlockletSites(blocklet, nodeInfo, context);
913
-
914
910
  return changed;
915
911
  };
916
912
 
917
913
  /**
918
- * Remove routing for blocklet
914
+ * Update routing for blocklet when blocklet is upgraded
915
+ * This function should be called after `ensureDashboardRouting`
919
916
  *
920
917
  * @returns {boolean} if routing changed
921
918
  */
922
- const removeBlockletRouting = async (blocklet, context = {}) => {
919
+ const ensureBlockletRoutingForUpgrade = async (blocklet, context = {}) => {
923
920
  const nodeInfo = await nodeState.read();
921
+ const provider = getProviderFromNodeInfo(nodeInfo);
922
+ if (provider === ROUTER_PROVIDER_NONE) {
923
+ return false;
924
+ }
924
925
 
925
- const ruleChanged = await routerManager.deleteRoutingRulesItemByDid({ did: blocklet.meta.did }, context);
926
- const siteChanged = await _removeBlockletSites(blocklet, nodeInfo, context);
926
+ await routerManager.deleteRoutingRulesItemByDid(
927
+ { did: blocklet.meta.did, ruleFilter: (x) => x.isProtected },
928
+ context
929
+ );
930
+ await ensureBlockletCustomRouting(blocklet, context);
931
+ await ensureBlockletRouting(blocklet, context);
927
932
 
928
- return ruleChanged || siteChanged;
933
+ // Always return true to trigger update of provider
934
+ return true;
929
935
  };
930
936
 
931
937
  /**
932
- * Refresh ip of in ip-dns-domain if ip changed
938
+ * Remove routing for blocklet
939
+ *
940
+ * @returns {boolean} if routing changed
933
941
  */
934
- const ensureIpDnsRouting = async (context = {}, output) => {
942
+ const removeBlockletRouting = async (blocklet, context = {}) => {
935
943
  const nodeInfo = await nodeState.read();
936
- const sites = await siteState.getSites();
937
-
938
- const ip = await getAccessibleIp(nodeInfo);
939
- if (!ip) {
940
- logger.error('Cannot get get accessible ip for this node');
941
- return false;
942
- }
943
-
944
- const formattedIp = ip.replace(/\./g, '-');
945
- const suffix = DEFAULT_DASHBOARD_DOMAIN.replace(/^\*/, '');
946
- const domainSuffixRegex = new RegExp(`(\\d+-\\d+-\\d+-\\d+)${suffix.replace(/\./g, '\\.')}$`); // xxx-xxx-xxx-xxx.ip.abtnet.io
947
- const domainSuffix = `${formattedIp}${suffix}`;
948
-
949
- const changes = [];
950
-
951
- sites.forEach((x) => {
952
- const match = domainSuffixRegex.exec(x.domain);
953
- if (match && match[1] !== formattedIp) {
954
- const domain = x.domain.replace(domainSuffixRegex, domainSuffix);
955
- changes.push({ id: x.id, domain });
956
- }
957
- });
958
-
959
- if (changes.length) {
960
- for (const change of changes) {
961
- // eslint-disable-next-line no-await-in-loop
962
- await siteState.update({ _id: change.id }, { $set: { domain: change.domain } });
963
- if (typeof output === 'function') {
964
- output(`Blocklet endpoint was updated: ${change.domain}`);
965
- }
966
- }
967
944
 
968
- const hash = await takeRoutingSnapshot({ message: 'ensure ip dns routing rules', dryRun: false }, context);
969
- logger.info('take routing snapshot on ensure dashboard routing rules', { changes, hash });
970
- return true;
971
- }
945
+ const ruleChanged = await routerManager.deleteRoutingRulesItemByDid({ did: blocklet.meta.did }, context);
946
+ const siteChanged = await _removeBlockletSites(blocklet, nodeInfo, context);
972
947
 
973
- return false;
948
+ return ruleChanged || siteChanged;
974
949
  };
975
950
 
976
951
  async function readRoutingSites() {
@@ -1138,8 +1113,14 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1138
1113
  return result;
1139
1114
  };
1140
1115
 
1116
+ /**
1117
+ * @param {string} message
1118
+ * @param {boolean} dryRun
1119
+ * @param {boolean} handleRouting if NOT dryRun and handleRouting is true, will run handleRouting() after update routing
1120
+ * @returns
1121
+ */
1141
1122
  // eslint-disable-next-line no-unused-vars
1142
- const takeRoutingSnapshot = async ({ message, dryRun = true }, context = {}) => {
1123
+ const takeRoutingSnapshot = async ({ message, dryRun = true, handleRouting: handle = true }, context = {}) => {
1143
1124
  const msg = decodeURIComponent(message);
1144
1125
 
1145
1126
  if (!msg) {
@@ -1158,8 +1139,10 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1158
1139
  if (!dryRun) {
1159
1140
  const nodeInfo = await nodeState.read();
1160
1141
  const result = await nodeState.updateNodeRouting({ ...nodeInfo.routing, snapshotHash: hash });
1161
- await handleRouting(nodeInfo);
1162
- logger.debug('takeRoutingSnapshot', { dryRun, result });
1142
+ if (handle) {
1143
+ await handleRouting(nodeInfo);
1144
+ }
1145
+ logger.debug('takeRoutingSnapshot', { dryRun, result, handleRouting: handle });
1163
1146
  }
1164
1147
 
1165
1148
  return hash;
@@ -1282,7 +1265,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1282
1265
  getCertificates,
1283
1266
  getSitesFromSnapshot,
1284
1267
  checkDomain,
1285
- ensureIpDnsRouting,
1286
1268
 
1287
1269
  getRoutingCrons: () => [
1288
1270
  {
@@ -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,
@@ -205,6 +206,7 @@ class BlockletState extends BaseState {
205
206
  },
206
207
  });
207
208
  lock.release();
209
+ this.emit('upgrade', newDoc);
208
210
  resolve(newDoc);
209
211
  } catch (err) {
210
212
  lock.release();
@@ -38,12 +38,14 @@ class TeamManager extends EventEmitter {
38
38
 
39
39
  async init() {
40
40
  // listen blocklet state
41
- this.states.blocklet.on('add', ({ meta: { did } }) => {
42
- this.cache[did] = {
43
- rbac: null,
44
- user: null,
45
- session: null,
46
- };
41
+ ['add', 'upgrade'].forEach((event) => {
42
+ this.states.blocklet.on(event, ({ meta: { did } }) => {
43
+ this.cache[did] = {
44
+ rbac: null,
45
+ user: null,
46
+ session: null,
47
+ };
48
+ });
47
49
  });
48
50
 
49
51
  this.states.blocklet.on('remove', ({ meta: { did } }) => {
@@ -223,25 +223,36 @@ const ensureBlockletExpanded = async (meta, appDir) => {
223
223
  const getRootSystemEnvironments = (blocklet, nodeInfo) => {
224
224
  const { did, name, title, description } = blocklet.meta;
225
225
  let wallet;
226
- let blockletAppSK;
227
- let blockletAppId;
226
+ let appSk;
227
+ let appId;
228
+ let appName = title || name;
229
+ let appDescription = description || '';
228
230
 
229
231
  if (process.env.NODE_ENV !== 'test') {
230
- 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
+ );
231
240
  wallet = result.wallet;
232
- blockletAppSK = toHex(wallet.secretKey);
233
- blockletAppId = wallet.toAddress();
241
+ appSk = toHex(wallet.secretKey);
242
+ appId = wallet.toAddress();
243
+ appName = appName || result.name;
244
+ appDescription = appDescription || result.description;
234
245
  } else {
235
- blockletAppSK = 'AppSK:FIXME:WalletWorksFailedInJest';
236
- blockletAppId = 'AppID:FIXME:WalletWorksFailedInJest';
246
+ appSk = 'AppSK:FIXME:WalletWorksFailedInJest';
247
+ appId = 'AppID:FIXME:WalletWorksFailedInJest';
237
248
  }
238
249
 
239
250
  return {
240
251
  BLOCKLET_DID: did,
241
- BLOCKLET_APP_SK: blockletAppSK,
242
- BLOCKLET_APP_ID: blockletAppId,
243
- BLOCKLET_APP_NAME: title || name,
244
- BLOCKLET_APP_DESCRIPTION: description,
252
+ BLOCKLET_APP_SK: appSk,
253
+ BLOCKLET_APP_ID: appId,
254
+ BLOCKLET_APP_NAME: appName,
255
+ BLOCKLET_APP_DESCRIPTION: appDescription,
245
256
  };
246
257
  };
247
258
 
@@ -269,10 +280,7 @@ const getOverwrittenEnvironments = (blocklet, nodeInfo) => {
269
280
  const { wallet } = getBlockletInfo(
270
281
  {
271
282
  meta: blocklet.meta,
272
- environments: keys.map((key) => ({
273
- key,
274
- value: blocklet.configObj[key] || blocklet.environmentObj[key],
275
- })),
283
+ environments: keys.map((key) => ({ key, value: blocklet.configObj[key] })).filter((x) => x.value),
276
284
  },
277
285
  nodeInfo.sk
278
286
  );
@@ -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.12",
6
+ "version": "1.5.1",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,26 +19,26 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@abtnode/constant": "1.4.12",
23
- "@abtnode/cron": "1.4.12",
24
- "@abtnode/event-hub": "1.4.12",
25
- "@abtnode/logger": "1.4.12",
26
- "@abtnode/queue": "1.4.12",
27
- "@abtnode/rbac": "1.4.12",
28
- "@abtnode/router-provider": "1.4.12",
29
- "@abtnode/static-server": "1.4.12",
30
- "@abtnode/timemachine": "1.4.12",
31
- "@abtnode/util": "1.4.12",
32
- "@arcblock/did": "^1.13.8",
22
+ "@abtnode/constant": "1.5.1",
23
+ "@abtnode/cron": "1.5.1",
24
+ "@abtnode/event-hub": "1.5.1",
25
+ "@abtnode/logger": "1.5.1",
26
+ "@abtnode/queue": "1.5.1",
27
+ "@abtnode/rbac": "1.5.1",
28
+ "@abtnode/router-provider": "1.5.1",
29
+ "@abtnode/static-server": "1.5.1",
30
+ "@abtnode/timemachine": "1.5.1",
31
+ "@abtnode/util": "1.5.1",
32
+ "@arcblock/did": "^1.13.20",
33
33
  "@arcblock/pm2-events": "^0.0.5",
34
- "@arcblock/vc": "^1.13.8",
35
- "@blocklet/meta": "1.4.12",
34
+ "@arcblock/vc": "^1.13.20",
35
+ "@blocklet/meta": "1.5.1",
36
36
  "@fidm/x509": "^1.2.1",
37
37
  "@nedb/core": "^1.1.0",
38
38
  "@nedb/multi": "^1.1.0",
39
- "@ocap/mcrypto": "^1.13.8",
40
- "@ocap/util": "^1.13.8",
41
- "@ocap/wallet": "^1.13.8",
39
+ "@ocap/mcrypto": "^1.13.20",
40
+ "@ocap/util": "^1.13.20",
41
+ "@ocap/wallet": "^1.13.20",
42
42
  "@slack/webhook": "^5.0.3",
43
43
  "axios": "^0.21.4",
44
44
  "axon": "^2.0.3",
@@ -72,5 +72,5 @@
72
72
  "express": "^4.17.1",
73
73
  "jest": "^26.4.2"
74
74
  },
75
- "gitHead": "677dcef3be9130feeca3334d475202a5e11a7323"
75
+ "gitHead": "8219f2128aaa199a52c100e6c90ea4a90d15ad15"
76
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
- };