@abtnode/core 1.8.60 → 1.8.62

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
@@ -595,7 +595,13 @@ class TeamAPI extends EventEmitter {
595
595
  async getPassportIssuances({ teamDid, ownerDid }) {
596
596
  const state = await this.getSessionState(teamDid);
597
597
 
598
- const list = await state.find({ type: 'passport-issuance', ownerDid });
598
+ const query = { type: 'passport-issuance' };
599
+
600
+ if (ownerDid) {
601
+ query.ownerDid = ownerDid;
602
+ }
603
+
604
+ const list = await state.find(query);
599
605
 
600
606
  return list.map((d) => ({
601
607
  // eslint-disable-next-line no-underscore-dangle
@@ -12,14 +12,12 @@ const capitalize = require('lodash/capitalize');
12
12
  const { Throttle } = require('stream-throttle');
13
13
  const LRU = require('lru-cache');
14
14
  const joi = require('joi');
15
- const isUrl = require('is-url');
16
15
  const { isNFTExpired, getNftExpirationDate } = require('@abtnode/util/lib/nft');
17
16
  const didDocument = require('@abtnode/util/lib/did-document');
18
17
  const { sign } = require('@arcblock/jwt');
19
18
  const { isValid: isValidDid } = require('@arcblock/did');
20
19
  const { verifyPresentation } = require('@arcblock/vc');
21
- const { toBase58, isHex } = require('@ocap/util');
22
- const { fromSecretKey } = require('@ocap/wallet');
20
+ const { toBase58 } = require('@ocap/util');
23
21
  const { toSvg: createDidLogo } =
24
22
  process.env.NODE_ENV !== 'test' ? require('@arcblock/did-motif') : require('@arcblock/did-motif/dist/did-motif.cjs');
25
23
  const getBlockletInfo = require('@blocklet/meta/lib/info');
@@ -55,13 +53,7 @@ const validateBlockletEntry = require('@blocklet/meta/lib/entry');
55
53
  const toBlockletDid = require('@blocklet/meta/lib/did');
56
54
  const { validateMeta } = require('@blocklet/meta/lib/validate');
57
55
  const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
58
- const {
59
- titleSchema,
60
- descriptionSchema,
61
- mountPointSchema,
62
- logoSchema,
63
- environmentNameSchema,
64
- } = require('@blocklet/meta/lib/schema');
56
+ const { titleSchema, mountPointSchema, environmentNameSchema } = require('@blocklet/meta/lib/schema');
65
57
  const hasReservedKey = require('@blocklet/meta/lib/has-reserved-key');
66
58
 
67
59
  const { toExternalBlocklet } = toBlockletDid;
@@ -86,7 +78,6 @@ const {
86
78
  BLOCKLET_META_FILE,
87
79
  BLOCKLET_CONFIGURABLE_KEY,
88
80
  } = require('@blocklet/constant');
89
- const { isEmpty } = require('lodash');
90
81
  const util = require('../../util');
91
82
  const {
92
83
  refresh: refreshAccessibleExternalNodeIp,
@@ -125,6 +116,9 @@ const {
125
116
  ensureEnvDefault,
126
117
  getConfigFromPreferences,
127
118
  consumeServerlessNFT,
119
+ validateAppConfig,
120
+ checkDuplicateAppSk,
121
+ checkDuplicateMountPoint,
128
122
  } = require('../../util/blocklet');
129
123
  const StoreUtil = require('../../util/store');
130
124
  const states = require('../../states');
@@ -978,77 +972,7 @@ class BlockletManager extends BaseBlockletManager {
978
972
  throw new Error(`Cannot set ${x.key} to child blocklet`);
979
973
  }
980
974
 
981
- if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK) {
982
- try {
983
- fromSecretKey(x.value);
984
- } catch {
985
- try {
986
- fromSecretKey(x.value, 'eth');
987
- } catch {
988
- throw new Error('Invalid custom blocklet secret key');
989
- }
990
- }
991
-
992
- // Ensure sk is not used by other blocklets, otherwise we may encounter appDid collision
993
- const blocklets = await states.blocklet.getBlocklets({});
994
- const others = blocklets.filter((b) => b.meta.did !== did);
995
- if (
996
- others.some(
997
- (b) => b.environments.find((e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK).value === x.value
998
- )
999
- ) {
1000
- throw new Error('Invalid custom blocklet secret key: already used by another blocklet');
1001
- }
1002
- }
1003
-
1004
- if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_NAME) {
1005
- x.value = await titleSchema.validateAsync(x.value);
1006
- }
1007
-
1008
- if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_DESCRIPTION) {
1009
- x.value = await descriptionSchema.validateAsync(x.value);
1010
- }
1011
-
1012
- if (
1013
- [
1014
- BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO,
1015
- BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_RECT,
1016
- BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_SQUARE,
1017
- BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_FAVICON,
1018
- ].includes(x.key)
1019
- ) {
1020
- x.value = await logoSchema.validateAsync(x.value);
1021
- }
1022
-
1023
- if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_WALLET_TYPE) {
1024
- if (['default', 'eth'].includes(x.value) === false) {
1025
- throw new Error('Invalid blocklet wallet type, only "default" and "eth" are supported');
1026
- }
1027
- }
1028
-
1029
- if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_DELETABLE) {
1030
- if (['yes', 'no'].includes(x.value) === false) {
1031
- throw new Error('BLOCKLET_DELETABLE must be either "yes" or "no"');
1032
- }
1033
- }
1034
-
1035
- if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_PASSPORT_COLOR) {
1036
- if (x.value && x.value !== 'auto') {
1037
- if (x.value.length !== 7 || !isHex(x.value.slice(-6))) {
1038
- throw new Error('BLOCKLET_PASSPORT_COLOR must be a hex encoded color, eg. #ffeeaa');
1039
- }
1040
- }
1041
- }
1042
-
1043
- if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT) {
1044
- if (isEmpty(x.value)) {
1045
- throw new Error(`${x.key} can not be empty`);
1046
- }
1047
-
1048
- if (!isUrl(x.value)) {
1049
- throw new Error(`${x.key}(${x.value}) is not a valid http address`);
1050
- }
1051
- }
975
+ await validateAppConfig(x, rootDid, states);
1052
976
  } else if (!BLOCKLET_CONFIGURABLE_KEY[x.key] && !isPreferenceKey(x)) {
1053
977
  if (!(blocklet.meta.environments || []).some((y) => y.name === x.key)) {
1054
978
  // forbid unknown format key
@@ -1221,20 +1145,26 @@ class BlockletManager extends BaseBlockletManager {
1221
1145
  }
1222
1146
 
1223
1147
  const rootDid = blocklet.meta.did;
1148
+ const isRootComponent = !did;
1224
1149
 
1225
- const { children } = blocklet;
1226
- const component = children.find((x) => x.meta.did === did);
1227
-
1150
+ const component = isRootComponent ? blocklet : blocklet.children.find((x) => x.meta.did === did);
1228
1151
  if (!component) {
1229
1152
  throw new Error('component does not exist');
1230
1153
  }
1231
1154
 
1232
- if (!component.dynamic) {
1155
+ if (!isRootComponent && !component.dynamic) {
1233
1156
  throw new Error('cannot update mountPoint of non-dynamic component');
1234
1157
  }
1235
1158
 
1159
+ if (isRootComponent && component.group === BlockletGroup.gateway) {
1160
+ throw new Error('cannot update mountPoint of gateway blocklet');
1161
+ }
1162
+
1163
+ checkDuplicateMountPoint(blocklet, mountPoint);
1164
+
1236
1165
  component.mountPoint = mountPoint;
1237
- await states.blocklet.updateBlocklet(rootDid, { children });
1166
+
1167
+ await states.blocklet.updateBlocklet(rootDid, { mountPoint: blocklet.mountPoint, children: blocklet.children });
1238
1168
 
1239
1169
  this.emit(BlockletEvents.upgraded, { blocklet, context: { ...context, createAuditLog: false } }); // trigger router refresh
1240
1170
 
@@ -2362,6 +2292,9 @@ class BlockletManager extends BaseBlockletManager {
2362
2292
  });
2363
2293
  await this._setConfigsFromMeta(did);
2364
2294
 
2295
+ // check duplicate appSk
2296
+ await checkDuplicateAppSk({ did, states });
2297
+
2365
2298
  // fake install bundle
2366
2299
  const bundleDir = getBundleDir(this.installDir, meta);
2367
2300
  fs.mkdirSync(bundleDir, { recursive: true });
@@ -2479,6 +2412,9 @@ class BlockletManager extends BaseBlockletManager {
2479
2412
  await this._setConfigsFromMeta(meta.did);
2480
2413
  await validateBlocklet(blocklet);
2481
2414
 
2415
+ // check duplicate appSk
2416
+ await checkDuplicateAppSk({ did: meta.did, states });
2417
+
2482
2418
  // download
2483
2419
  await this._downloadBlocklet(
2484
2420
  blocklet,
@@ -2743,6 +2679,9 @@ class BlockletManager extends BaseBlockletManager {
2743
2679
 
2744
2680
  await this._setConfigsFromMeta(did);
2745
2681
 
2682
+ // check duplicate appSk
2683
+ await checkDuplicateAppSk({ did, states });
2684
+
2746
2685
  logger.info('blocklet added to database', { did: meta.did });
2747
2686
 
2748
2687
  const blocklet1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
package/lib/event.js CHANGED
@@ -11,6 +11,7 @@ const eventHub =
11
11
  process.env.NODE_ENV === 'test' ? require('@arcblock/event-hub/single') : require('@arcblock/event-hub');
12
12
 
13
13
  const states = require('./states');
14
+ const { isBeforeInstalled } = require('./util');
14
15
 
15
16
  const routingSnapshotPrefix = (blocklet) => (blocklet.mode === BLOCKLET_MODES.DEVELOPMENT ? '[DEV] ' : '');
16
17
 
@@ -237,10 +238,12 @@ module.exports = ({
237
238
  logger.info('take snapshot after updated blocklet app', { event: eventName, did: blocklet.meta.did, hash });
238
239
  }
239
240
 
240
- try {
241
- await blockletManager.runtimeMonitor.monit(blocklet.meta.did);
242
- } catch (error) {
243
- logger.error('monit runtime info failed', { error });
241
+ if (blocklet.status && !isBeforeInstalled(blocklet.status)) {
242
+ try {
243
+ await blockletManager.runtimeMonitor.monit(blocklet.meta.did);
244
+ } catch (error) {
245
+ logger.error('monit runtime info failed', { eventName, error });
246
+ }
244
247
  }
245
248
 
246
249
  if (payload.blocklet && !payload.meta) {
@@ -258,13 +258,19 @@ const ensureWellknownRule = async (sites) => {
258
258
  if (blockletRules.length) {
259
259
  // get pathPrefix for blocklet-service
260
260
  const rootBlockletRule = blockletRules.find((x) => x.to.did === x.to.componentId);
261
- const pathPrefix = joinUrl(rootBlockletRule?.from?.pathPrefix || '/', WELLKNOWN_SERVICE_PATH_PREFIX);
261
+
262
+ const servicePathPrefix = joinUrl(
263
+ // rootBlockletRule?.from?.pathPrefix is for backwards compatibility
264
+ // rootBlockletRule?.from?.groupPathPrefix 在旧的场景中可能不为 '/' (blocklet 只能挂载在 relative prefix 下)
265
+ rootBlockletRule?.from?.groupPathPrefix || rootBlockletRule?.from?.pathPrefix || '/',
266
+ WELLKNOWN_SERVICE_PATH_PREFIX
267
+ );
262
268
 
263
269
  // requests for /.well-known/service will stay in blocklet-service and never proxy back to blocklet
264
270
  // so any rule is ok to be cloned
265
- if (!site.rules.some((x) => x.from.pathPrefix === pathPrefix)) {
271
+ if (!site.rules.some((x) => x.from.pathPrefix === servicePathPrefix)) {
266
272
  const rule = cloneDeep(rootBlockletRule || blockletRules[0]);
267
- rule.from.pathPrefix = pathPrefix;
273
+ rule.from.pathPrefix = servicePathPrefix;
268
274
  rule.isProtected = true;
269
275
  site.rules.push(rule);
270
276
  }
@@ -162,6 +162,10 @@ class Router {
162
162
  getLogFilesForToday() {
163
163
  return this.provider.getLogFilesForToday();
164
164
  }
165
+
166
+ getLogDir() {
167
+ return this.provider.getLogDir();
168
+ }
165
169
  }
166
170
 
167
171
  Router.formatSites = (sites = []) => {
@@ -11,6 +11,7 @@ const get = require('lodash/get');
11
11
  const { EventEmitter } = require('events');
12
12
  const uuid = require('uuid');
13
13
  const isUrl = require('is-url');
14
+ const joinUrl = require('url-join');
14
15
  const cloneDeep = require('lodash/cloneDeep');
15
16
  const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
16
17
  const logger = require('@abtnode/logger')('@abtnode/core:router:manager');
@@ -570,7 +571,8 @@ class RouterManager extends EventEmitter {
570
571
  rule.groupId = rule.id;
571
572
  rule.from.pathPrefix = normalizePathPrefix(rule.from.pathPrefix);
572
573
  if (rule.to.type === ROUTING_RULE_TYPES.BLOCKLET) {
573
- rule.from.groupPathPrefix = rule.from.pathPrefix;
574
+ // pathPrefix of root blocklet maybe changed to another prefix than '/', so use old groupPathPrefix first
575
+ rule.from.groupPathPrefix = rule.from.groupPathPrefix || rule.from.pathPrefix;
574
576
  rule.to.componentId = rule.to.did;
575
577
  }
576
578
  if (rule.to.url) {
@@ -580,18 +582,32 @@ class RouterManager extends EventEmitter {
580
582
 
581
583
  /**
582
584
  * get all rules to be add or update to site from root rule
583
- * @param {*} rule
585
+ * @param {*} rootRule
584
586
  */
585
- async getRulesForMutation(rule) {
586
- if (rule.to.type !== ROUTING_RULE_TYPES.BLOCKLET) {
587
- return [rule];
587
+ async getRulesForMutation(rootRule) {
588
+ if (rootRule.to.type !== ROUTING_RULE_TYPES.BLOCKLET) {
589
+ return [rootRule];
588
590
  }
589
591
 
590
592
  const rules = [];
591
- let occupied = false;
592
593
 
593
594
  // get child rules
594
- const blocklet = await states.blocklet.getBlocklet(rule.to.did);
595
+ const blocklet = await states.blocklet.getBlocklet(rootRule.to.did);
596
+
597
+ // blocklet may be mounted in relative prefix (for old usage), so blockletPrefix may not be '/'
598
+ // blocklet prefix is the origin pathPrefix in rootRule
599
+ const blockletPrefix = normalizePathPrefix(rootRule.from.pathPrefix);
600
+
601
+ // root component's mountPoint may not be '/'
602
+ const rootComponentPrefix = joinUrl(blockletPrefix, blocklet.mountPoint || '/');
603
+ rootRule.from.pathPrefix = normalizePathPrefix(rootComponentPrefix);
604
+
605
+ const isOccupiable = blocklet.meta.group === BlockletGroup.gateway;
606
+
607
+ if (!isOccupiable) {
608
+ rules.push(rootRule);
609
+ }
610
+
595
611
  forEachChildSync(blocklet, (component, { id, ancestors }) => {
596
612
  if (component.meta.group === BlockletGroup.gateway) {
597
613
  return;
@@ -611,38 +627,40 @@ class RouterManager extends EventEmitter {
611
627
  return;
612
628
  }
613
629
 
614
- const pathPrefix = path.join(rule.from.pathPrefix, ...ancestors.map((x) => x.mountPoint || ''), mountPoint);
615
- const isRootPath = normalizePathPrefix(pathPrefix) === normalizePathPrefix(rule.from.pathPrefix);
616
- if (isRootPath) {
617
- occupied = true;
630
+ const pathPrefix = path.join(
631
+ blockletPrefix,
632
+ // level 1 component should be independent with root component
633
+ ...ancestors.slice(1).map((x) => x.mountPoint || ''),
634
+ mountPoint
635
+ );
636
+
637
+ const occupied = normalizePathPrefix(pathPrefix) === normalizePathPrefix(rootRule.from.pathPrefix);
638
+
639
+ if (occupied && !isOccupiable) {
640
+ return;
618
641
  }
619
642
 
620
643
  // if is root path, child rule become root rule
621
644
  const childRule = {
622
- id: isRootPath ? rule.id : uuid.v4(),
623
- groupId: rule.id,
645
+ id: occupied ? rootRule.id : uuid.v4(),
646
+ groupId: rootRule.id,
624
647
  from: {
625
648
  pathPrefix: normalizePathPrefix(pathPrefix),
626
- groupPathPrefix: rule.from.pathPrefix,
649
+ groupPathPrefix: blockletPrefix,
627
650
  },
628
651
  to: {
629
652
  type: ROUTING_RULE_TYPES.BLOCKLET,
630
653
  port: findInterfacePortByName(component, childWebInterface.name),
631
- did: rule.to.did, // root component did
632
- interfaceName: rule.to.interfaceName, // root component interface
654
+ did: rootRule.to.did, // root component did
655
+ interfaceName: rootRule.to.interfaceName, // root component interface
633
656
  componentId: id,
634
657
  },
635
- isProtected: isRootPath ? rule.isProtected : true,
658
+ isProtected: occupied ? rootRule.isProtected : true,
636
659
  };
637
660
 
638
661
  rules.push(childRule);
639
662
  });
640
663
 
641
- // get root rule
642
- if (!occupied && blocklet.meta.group !== BlockletGroup.gateway) {
643
- rules.push(rule);
644
- }
645
-
646
664
  return rules;
647
665
  }
648
666
  }
@@ -7,16 +7,18 @@ const shelljs = require('shelljs');
7
7
  const os = require('os');
8
8
  const tar = require('tar');
9
9
  const get = require('lodash/get');
10
+ const isEmpty = require('lodash/isEmpty');
10
11
  const streamToPromise = require('stream-to-promise');
11
12
  const { Throttle } = require('stream-throttle');
12
13
  const ssri = require('ssri');
13
14
  const diff = require('deep-diff');
14
15
  const createArchive = require('archiver');
16
+ const isUrl = require('is-url');
15
17
  const axios = require('@abtnode/util/lib/axios');
16
18
  const { stableStringify } = require('@arcblock/vc');
17
19
 
18
20
  const { fromSecretKey, WalletType } = require('@ocap/wallet');
19
- const { toHex, toBase58 } = require('@ocap/util');
21
+ const { toHex, toBase58, isHex } = require('@ocap/util');
20
22
  const { types } = require('@ocap/mcrypto');
21
23
  const { isValid: isValidDid } = require('@arcblock/did');
22
24
  const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet');
@@ -67,7 +69,7 @@ const {
67
69
  isEnvShareable,
68
70
  } = require('@blocklet/meta/lib/util');
69
71
  const toBlockletDid = require('@blocklet/meta/lib/did');
70
- const { titleSchema, descriptionSchema } = require('@blocklet/meta/lib/schema');
72
+ const { titleSchema, descriptionSchema, logoSchema } = require('@blocklet/meta/lib/schema');
71
73
  const {
72
74
  getSourceUrlsFromConfig,
73
75
  getBlockletMetaFromUrls,
@@ -1017,8 +1019,8 @@ const pruneBlockletBundle = async ({ blocklets, installDir, blockletSettings })
1017
1019
  const bundleName = arr[length - 2];
1018
1020
  const scopeName = length > 2 ? arr[length - 3] : '';
1019
1021
  const bundleDir = path.join(installDir, scopeName, bundleName);
1020
- const isEmpty = (await fs.promises.readdir(bundleDir)).length === 0;
1021
- if (isEmpty) {
1022
+ const isDirEmpty = (await fs.promises.readdir(bundleDir)).length === 0;
1023
+ if (isDirEmpty) {
1022
1024
  logger.info('Remove bundle folder', { bundleDir });
1023
1025
  await fs.remove(bundleDir);
1024
1026
  }
@@ -1513,6 +1515,129 @@ const createDataArchive = (dataDir, fileName) => {
1513
1515
  });
1514
1516
  };
1515
1517
 
1518
+ /**
1519
+ * this function has side effect on config.value
1520
+ * @param {{ key: string, value?: string }} config
1521
+ */
1522
+ const validateAppConfig = async (config, blockletDid, states) => {
1523
+ const x = config;
1524
+
1525
+ if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK) {
1526
+ if (x.value) {
1527
+ try {
1528
+ fromSecretKey(x.value);
1529
+ } catch {
1530
+ try {
1531
+ fromSecretKey(x.value, 'eth');
1532
+ } catch {
1533
+ throw new Error('Invalid custom blocklet secret key');
1534
+ }
1535
+ }
1536
+
1537
+ // Ensure sk is not used by other blocklets, otherwise we may encounter appDid collision
1538
+ const blocklets = await states.blocklet.getBlocklets({});
1539
+ const others = blocklets.filter((b) => b.meta.did !== blockletDid);
1540
+ if (
1541
+ others.some(
1542
+ (b) => b.environments.find((e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK).value === x.value
1543
+ )
1544
+ ) {
1545
+ throw new Error('Invalid custom blocklet secret key: already used by another blocklet');
1546
+ }
1547
+ } else {
1548
+ delete x.value;
1549
+ }
1550
+ }
1551
+
1552
+ if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_NAME) {
1553
+ x.value = await titleSchema.validateAsync(x.value);
1554
+ }
1555
+
1556
+ if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_DESCRIPTION) {
1557
+ x.value = await descriptionSchema.validateAsync(x.value);
1558
+ }
1559
+
1560
+ if (
1561
+ [
1562
+ BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO,
1563
+ BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_RECT,
1564
+ BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_SQUARE,
1565
+ BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_FAVICON,
1566
+ ].includes(x.key)
1567
+ ) {
1568
+ x.value = await logoSchema.validateAsync(x.value);
1569
+ }
1570
+
1571
+ if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_WALLET_TYPE) {
1572
+ if (['default', 'eth'].includes(x.value) === false) {
1573
+ throw new Error('Invalid blocklet wallet type, only "default" and "eth" are supported');
1574
+ }
1575
+ }
1576
+
1577
+ if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_DELETABLE) {
1578
+ if (['yes', 'no'].includes(x.value) === false) {
1579
+ throw new Error('BLOCKLET_DELETABLE must be either "yes" or "no"');
1580
+ }
1581
+ }
1582
+
1583
+ if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_PASSPORT_COLOR) {
1584
+ if (x.value && x.value !== 'auto') {
1585
+ if (x.value.length !== 7 || !isHex(x.value.slice(-6))) {
1586
+ throw new Error('BLOCKLET_PASSPORT_COLOR must be a hex encoded color, eg. #ffeeaa');
1587
+ }
1588
+ }
1589
+ }
1590
+
1591
+ if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT) {
1592
+ if (isEmpty(x.value)) {
1593
+ throw new Error(`${x.key} can not be empty`);
1594
+ }
1595
+
1596
+ if (!isUrl(x.value)) {
1597
+ throw new Error(`${x.key}(${x.value}) is not a valid http address`);
1598
+ }
1599
+ }
1600
+ };
1601
+
1602
+ const checkDuplicateAppSk = async ({ did, states }) => {
1603
+ const nodeInfo = await states.node.read();
1604
+ const blocklets = await states.blocklet.getBlocklets({});
1605
+ const blocklet = await states.blocklet.getBlocklet(did);
1606
+ const configs = await states.blockletExtras.getConfigs([did]);
1607
+
1608
+ const { wallet } = getBlockletInfo(
1609
+ {
1610
+ meta: blocklet.meta,
1611
+ environments: (configs || []).filter((x) => x.value),
1612
+ },
1613
+ nodeInfo.sk
1614
+ );
1615
+
1616
+ const others = blocklets.filter((b) => b.meta.did !== did);
1617
+
1618
+ const exist = others.find((b) => {
1619
+ const item = (b.environments || []).find((e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK);
1620
+ return item?.value === toHex(wallet.secretKey);
1621
+ });
1622
+
1623
+ if (exist) {
1624
+ throw new Error(`blocklet secret key already used by ${exist.meta.title || exist.meta.name}`);
1625
+ }
1626
+ };
1627
+
1628
+ const checkDuplicateMountPoint = (blocklet, mountPoint) => {
1629
+ const err = new Error(`cannot add duplicate mount point, ${mountPoint || '/'} already exist`);
1630
+ if (normalizePathPrefix(blocklet.mountPoint) === normalizePathPrefix(mountPoint)) {
1631
+ throw err;
1632
+ }
1633
+
1634
+ for (const component of blocklet.children || []) {
1635
+ if (normalizePathPrefix(component.mountPoint) === normalizePathPrefix(mountPoint)) {
1636
+ throw err;
1637
+ }
1638
+ }
1639
+ };
1640
+
1516
1641
  module.exports = {
1517
1642
  consumeServerlessNFT,
1518
1643
  forEachBlocklet,
@@ -1556,4 +1681,7 @@ module.exports = {
1556
1681
  ensureEnvDefault,
1557
1682
  getConfigFromPreferences,
1558
1683
  createDataArchive,
1684
+ validateAppConfig,
1685
+ checkDuplicateAppSk,
1686
+ checkDuplicateMountPoint,
1559
1687
  };
@@ -1,11 +1,9 @@
1
- const slugify = require('slugify');
2
1
  const { DEFAULT_IP_DOMAIN_SUFFIX } = require('@abtnode/constant');
3
2
  const { encode: encodeBase32 } = require('@abtnode/util/lib/base32');
3
+ const formatName = require('@abtnode/util/lib/format-name');
4
4
 
5
5
  const SLOT_FOR_IP_DNS_SITE = '888-888-888-888';
6
6
 
7
- const formatName = (name) => slugify(name.replace(/^[@./-]/, '').replace(/[@./_]/g, '-'));
8
-
9
7
  const hiddenInterfaceNames = ['public', 'publicUrl'];
10
8
 
11
9
  const getIpDnsDomainForBlocklet = (blocklet, blockletInterface) => {
@@ -47,7 +47,7 @@ const ruleSchema = {
47
47
  port: Joi.number().label('port').port().when('type', { is: ROUTING_RULE_TYPES.BLOCKLET, then: Joi.required() }),
48
48
  url: Joi.string().label('url').when('type', { is: ROUTING_RULE_TYPES.REDIRECT, then: Joi.required() }),
49
49
  redirectCode: Joi.alternatives()
50
- .try(301, 302)
50
+ .try(301, 302, 307, 308)
51
51
  .label('redirect code')
52
52
  .when('type', { is: ROUTING_RULE_TYPES.REDIRECT, then: Joi.required() }),
53
53
  interfaceName: Joi.string() // root interface
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.8.60",
6
+ "version": "1.8.62",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,32 +19,32 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@abtnode/auth": "1.8.60",
23
- "@abtnode/certificate-manager": "1.8.60",
24
- "@abtnode/constant": "1.8.60",
25
- "@abtnode/cron": "1.8.60",
26
- "@abtnode/db": "1.8.60",
27
- "@abtnode/logger": "1.8.60",
28
- "@abtnode/queue": "1.8.60",
29
- "@abtnode/rbac": "1.8.60",
30
- "@abtnode/router-provider": "1.8.60",
31
- "@abtnode/static-server": "1.8.60",
32
- "@abtnode/timemachine": "1.8.60",
33
- "@abtnode/util": "1.8.60",
34
- "@arcblock/did": "1.18.34",
22
+ "@abtnode/auth": "1.8.62",
23
+ "@abtnode/certificate-manager": "1.8.62",
24
+ "@abtnode/constant": "1.8.62",
25
+ "@abtnode/cron": "1.8.62",
26
+ "@abtnode/db": "1.8.62",
27
+ "@abtnode/logger": "1.8.62",
28
+ "@abtnode/queue": "1.8.62",
29
+ "@abtnode/rbac": "1.8.62",
30
+ "@abtnode/router-provider": "1.8.62",
31
+ "@abtnode/static-server": "1.8.62",
32
+ "@abtnode/timemachine": "1.8.62",
33
+ "@abtnode/util": "1.8.62",
34
+ "@arcblock/did": "1.18.36",
35
35
  "@arcblock/did-motif": "^1.1.10",
36
- "@arcblock/did-util": "1.18.34",
37
- "@arcblock/event-hub": "1.18.34",
38
- "@arcblock/jwt": "^1.18.34",
36
+ "@arcblock/did-util": "1.18.36",
37
+ "@arcblock/event-hub": "1.18.36",
38
+ "@arcblock/jwt": "^1.18.36",
39
39
  "@arcblock/pm2-events": "^0.0.5",
40
- "@arcblock/vc": "1.18.34",
41
- "@blocklet/constant": "1.8.60",
42
- "@blocklet/meta": "1.8.60",
43
- "@blocklet/sdk": "1.8.60",
40
+ "@arcblock/vc": "1.18.36",
41
+ "@blocklet/constant": "1.8.62",
42
+ "@blocklet/meta": "1.8.62",
43
+ "@blocklet/sdk": "1.8.62",
44
44
  "@fidm/x509": "^1.2.1",
45
- "@ocap/mcrypto": "1.18.34",
46
- "@ocap/util": "1.18.34",
47
- "@ocap/wallet": "1.18.34",
45
+ "@ocap/mcrypto": "1.18.36",
46
+ "@ocap/util": "1.18.36",
47
+ "@ocap/wallet": "1.18.36",
48
48
  "@slack/webhook": "^5.0.4",
49
49
  "archiver": "^5.3.1",
50
50
  "axios": "^0.27.2",
@@ -85,5 +85,5 @@
85
85
  "express": "^4.18.2",
86
86
  "jest": "^27.5.1"
87
87
  },
88
- "gitHead": "0a56dc596f58d83f22d1ea0d0145d84e7151f5be"
88
+ "gitHead": "ea6ab48149eb9e1157ef37ab1505fa36a9ce598f"
89
89
  }