@abtnode/core 1.8.69-beta-54faead3 → 1.8.69-beta-76f8a46f

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.
@@ -14,11 +14,12 @@ const ssri = require('ssri');
14
14
  const diff = require('deep-diff');
15
15
  const createArchive = require('archiver');
16
16
  const isUrl = require('is-url');
17
+ const semver = require('semver');
17
18
  const axios = require('@abtnode/util/lib/axios');
18
19
  const { stableStringify } = require('@arcblock/vc');
19
20
 
20
21
  const { fromSecretKey, WalletType } = require('@ocap/wallet');
21
- const { toHex, toBase58, isHex } = require('@ocap/util');
22
+ const { toHex, toBase58, isHex, toDid } = require('@ocap/util');
22
23
  const { types } = require('@ocap/mcrypto');
23
24
  const { isValid: isValidDid } = require('@arcblock/did');
24
25
  const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet');
@@ -30,12 +31,12 @@ const CustomError = require('@abtnode/util/lib/custom-error');
30
31
  const getFolderSize = require('@abtnode/util/lib/get-folder-size');
31
32
  const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
32
33
  const hashFiles = require('@abtnode/util/lib/hash-files');
33
- const isPathPrefixEqual = require('@abtnode/util/lib/is-path-prefix-equal');
34
34
  const {
35
35
  BLOCKLET_MAX_MEM_LIMIT_IN_MB,
36
36
  BLOCKLET_STORE,
37
37
  BLOCKLET_INSTALL_TYPE,
38
38
  BLOCKLET_STORE_DEV,
39
+ APP_STRUCT_VERSION,
39
40
  } = require('@abtnode/constant');
40
41
  const formatBackSlash = require('@abtnode/util/lib/format-back-slash');
41
42
 
@@ -52,12 +53,10 @@ const {
52
53
  BLOCKLET_DEFAULT_PORT_NAME,
53
54
  BLOCKLET_INTERFACE_TYPE_WEB,
54
55
  BLOCKLET_CONFIGURABLE_KEY,
55
- BLOCKLET_DYNAMIC_PATH_PREFIX,
56
56
  fromBlockletStatus,
57
57
  BLOCKLET_PREFERENCE_FILE,
58
58
  BLOCKLET_PREFERENCE_PREFIX,
59
59
  } = require('@blocklet/constant');
60
- const verifyMultiSig = require('@blocklet/meta/lib/verify-multi-sig');
61
60
  const validateBlockletEntry = require('@blocklet/meta/lib/entry');
62
61
  const getBlockletEngine = require('@blocklet/meta/lib/engine');
63
62
  const getBlockletInfo = require('@blocklet/meta/lib/info');
@@ -91,13 +90,16 @@ const {
91
90
  isBeforeInstalled,
92
91
  expandBundle,
93
92
  findInterfacePortByName,
94
- validateBlockletMeta,
95
93
  prettyURL,
96
94
  getNFTState,
97
95
  templateReplace,
98
96
  } = require('./index');
99
97
 
100
- const getComponentConfig = (meta) => meta.components || meta.children;
98
+ const getComponentConfig = (meta) => {
99
+ const components = meta.components || meta.children || [];
100
+ const staticComponents = (meta.staticComponents || []).map((x) => ({ ...x, static: true }));
101
+ return [...components, ...staticComponents];
102
+ };
101
103
 
102
104
  /**
103
105
  * get blocklet engine info, default is node
@@ -284,6 +286,10 @@ const getAppSystemEnvironments = (blocklet, nodeInfo) => {
284
286
  const appName = title || name || result.name;
285
287
  const appDescription = description || result.description;
286
288
 
289
+ const isMigrated = Array.isArray(blocklet.migratedFrom) && blocklet.migratedFrom.length > 0;
290
+ const appPid = blocklet.appPid || appId;
291
+ const appPsk = isMigrated ? blocklet.migratedFrom[0].appSk : appSk;
292
+
287
293
  /* 获取 did domain 方式:
288
294
  * 1. 先从 site 里读
289
295
  * 2. 如果没有,再拼接
@@ -299,16 +305,9 @@ const getAppSystemEnvironments = (blocklet, nodeInfo) => {
299
305
  if (didDomainAlias) {
300
306
  appUrl = prettyURL(didDomainAlias.value, true);
301
307
  } else {
302
- appUrl = `https://${getDidDomainForBlocklet({
303
- blockletAppDid: appId,
304
- didDomain: nodeInfo.didDomain,
305
- })}`;
308
+ appUrl = `https://${getDidDomainForBlocklet({ appPid, didDomain: nodeInfo.didDomain })}`;
306
309
  }
307
310
 
308
- const isMigrated = Array.isArray(blocklet.migratedFrom) && blocklet.migratedFrom.length > 0;
309
- const appPid = blocklet.appPid || appId;
310
- const appPsk = isMigrated ? blocklet.migratedFrom[0].appSk : appSk;
311
-
312
311
  return {
313
312
  BLOCKLET_DID: did,
314
313
  BLOCKLET_APP_SK: appSk,
@@ -743,75 +742,78 @@ const reloadProcess = (processId) =>
743
742
  });
744
743
  });
745
744
 
746
- const parseChildren = (children, parentMeta = {}, { dynamic } = {}) => {
747
- const configs = Array.isArray(parentMeta) ? parentMeta : getComponentConfig(parentMeta) || [];
748
-
749
- children.forEach((x) => {
750
- if (dynamic !== undefined) {
751
- x.dynamic = !!dynamic;
752
- }
753
- });
754
-
755
- mergeMeta(configs, children);
756
- return children;
757
- };
758
-
759
745
  /**
760
- * this function has side effect on children
746
+ * this function has side effect to component (will set component.children: Array<staticComponent> )
747
+ * this function has side effect to dynamicComponents (will push dynamicComponent in dynamicComponents)
748
+ *
749
+ * @param {Component} component
750
+ * @param {{
751
+ * ancestors: Array<{meta}>
752
+ * dynamicComponents: Array<{Component}>
753
+ * }} context
754
+ * @returns {{
755
+ * dynamicComponents: Array<Component>,
756
+ * staticComponents: Array<Component>,
757
+ * }}
761
758
  */
762
- const parseChildrenFromMeta = async (src, context = {}) => {
763
- const { dynamic, ancestors = [] } = context;
759
+ const parseComponents = async (component, context = {}) => {
760
+ const { ancestors = [], dynamicComponents = [] } = context;
764
761
  if (ancestors.length > 40) {
765
762
  throw new Error('The depth of component should not exceed 40');
766
763
  }
767
764
 
768
- const configs = Array.isArray(src) ? src : getComponentConfig(src) || [];
765
+ const configs = getComponentConfig(component.meta) || [];
769
766
 
770
767
  if (!configs || !configs.length) {
771
- return [];
768
+ return {
769
+ staticComponents: [],
770
+ dynamicComponents,
771
+ };
772
772
  }
773
773
 
774
- const children = [];
774
+ const staticComponents = [];
775
775
 
776
+ // FIXME @linchen 改成并行获取
776
777
  for (const config of configs) {
777
- const mountPoint = config.mountPoint || config.mountPoints[0].root.prefix;
778
- if (!mountPoint) {
779
- throw new Error(`MountPoint does not found in child ${config.name}`);
778
+ const isStatic = !!config.static;
779
+
780
+ if (isStatic) {
781
+ if (!config.name) {
782
+ throw new Error(`Name does not found in child ${config.name}`);
783
+ }
784
+
785
+ if (!config.mountPoint) {
786
+ throw new Error(`MountPoint does not found in child ${config.name}`);
787
+ }
780
788
  }
781
789
 
782
790
  const urls = getSourceUrlsFromConfig(config);
783
791
 
784
- let m;
792
+ let rawMeta;
785
793
  try {
786
- m = await getBlockletMetaFromUrls(urls, { logger });
787
- } catch {
788
- throw new Error(`Failed get component meta: ${config.title || config.name}`);
794
+ rawMeta = await getBlockletMetaFromUrls(urls, { logger });
795
+ } catch (error) {
796
+ throw new Error(
797
+ `Failed get component meta. Component: ${rawMeta.title || rawMeta.did}, reason: ${error.message}`
798
+ );
789
799
  }
790
800
 
791
- validateBlockletMeta(m, { ensureDist: true });
792
-
793
- if (!isComponentBlocklet(m)) {
794
- throw new Error(`The blocklet cannot be a component: ${m.title}`);
801
+ if (rawMeta.group === BlockletGroup.gateway) {
802
+ throw new Error(`Cannot add gateway component ${rawMeta.title || rawMeta.did}`);
795
803
  }
796
804
 
797
- const webInterface = findWebInterface(m);
798
- if (!webInterface) {
799
- throw new Error(`Web interface does not found in child ${config.name}`);
805
+ validateBlockletMeta(rawMeta, { ensureDist: true });
806
+
807
+ if (!isComponentBlocklet(rawMeta)) {
808
+ throw new Error(`The blocklet cannot be a component: ${rawMeta.title}`);
800
809
  }
801
810
 
802
- // validate mountPoint
803
- const rule = webInterface.prefix;
804
- if (rule !== BLOCKLET_DYNAMIC_PATH_PREFIX) {
805
- const fullMountPoint = path.join(
806
- ancestors.map((x) => x.mountPoint || ''),
807
- mountPoint
808
- );
809
- if (normalizePathPrefix(rule) !== normalizePathPrefix(fullMountPoint)) {
810
- throw new Error(`Prefix does not match in child ${config.name}. expected: ${rule}, resolved: ${mountPoint}`);
811
- }
811
+ const webInterface = findWebInterface(rawMeta);
812
+ if (!webInterface) {
813
+ throw new Error(`Web interface does not found in component ${rawMeta.title || rawMeta.name}`);
812
814
  }
813
815
 
814
- const meta = ensureMeta(m, { name: config.name });
816
+ const meta = ensureMeta(rawMeta, { name: isStatic ? config.name : null });
815
817
 
816
818
  // check circular dependencies
817
819
  if (ancestors.map((x) => x.meta?.bundleDid).indexOf(meta.bundleDid) > -1) {
@@ -822,28 +824,44 @@ const parseChildrenFromMeta = async (src, context = {}) => {
822
824
  meta.title = config.title;
823
825
  meta.title = await titleSchema.validateAsync(config.title);
824
826
  }
827
+
825
828
  if (config.description) {
826
829
  meta.description = await descriptionSchema.validateAsync(config.description);
827
830
  }
828
831
 
832
+ const mountPoint = isStatic ? config.mountPoint : config.mountPoint || `/${meta.did}`;
833
+
829
834
  const child = {
830
835
  mountPoint,
831
836
  meta,
832
- bundleSource: config.source || { url: config.resolved },
837
+ bundleSource: config.source,
838
+ dependencies: [],
833
839
  };
834
840
 
835
- child.children = await parseChildrenFromMeta(meta, {
836
- ...context,
837
- ancestors: [...ancestors, { meta, mountPoint }],
838
- dynamic: false,
839
- });
841
+ if (isStatic) {
842
+ staticComponents.push(child);
843
+ } else {
844
+ dynamicComponents.push(child);
845
+ component.dependencies = component.dependencies || [];
846
+ component.dependencies.push({
847
+ did: child.meta.bundleDid,
848
+ required: !!config.required,
849
+ version: config.source.version || 'latest',
850
+ });
851
+ }
840
852
 
841
- children.push(child);
853
+ await parseComponents(child, {
854
+ ancestors: [...ancestors, { meta }],
855
+ dynamicComponents,
856
+ });
842
857
  }
843
858
 
844
- parseChildren(children, configs, { dynamic });
859
+ component.children = staticComponents;
845
860
 
846
- return children;
861
+ return {
862
+ staticComponents,
863
+ dynamicComponents,
864
+ };
847
865
  };
848
866
 
849
867
  const validateBlocklet = (blocklet) =>
@@ -1166,43 +1184,6 @@ const mergeMeta = (source, childrenMeta = []) => {
1166
1184
  });
1167
1185
  };
1168
1186
 
1169
- const fixAndVerifyMetaFromStore = (meta) => {
1170
- // strip unknown properties
1171
- // rename property alias
1172
-
1173
- const sanitized = validateMeta(meta, { ensureDist: false, schemaOptions: { noDefaults: true, stripUnknown: true } });
1174
-
1175
- // rollback meta renamed by validateMeta
1176
-
1177
- if (meta.children) {
1178
- sanitized.children = meta.children;
1179
- delete sanitized.components;
1180
- }
1181
-
1182
- if (meta.navigation) {
1183
- meta.navigation.forEach((nav, index) => {
1184
- if (nav.child) {
1185
- sanitized.navigation[index].child = nav.child;
1186
- delete sanitized.navigation[index].component;
1187
- }
1188
- });
1189
- }
1190
-
1191
- // verify
1192
-
1193
- try {
1194
- const result = verifyMultiSig(sanitized);
1195
- if (!result) {
1196
- throw new Error('invalid signature from developer or store');
1197
- }
1198
- } catch (err) {
1199
- throw new Error(`Invalid blocklet meta: ${err.message}`);
1200
- }
1201
-
1202
- // this step comes last because it has side effects: the meta is changed
1203
- return fixAndValidateService(meta);
1204
- };
1205
-
1206
1187
  const getUpdateMetaList = (oldBlocklet = {}, newBlocklet = {}) => {
1207
1188
  const oldMap = {};
1208
1189
  forEachChildSync(oldBlocklet, (b, { id }) => {
@@ -1238,7 +1219,7 @@ const getTypeFromInstallParams = (params) => {
1238
1219
  }
1239
1220
 
1240
1221
  if (params.file) {
1241
- return BLOCKLET_INSTALL_TYPE.UPLOAD;
1222
+ throw new Error('install from upload is not supported');
1242
1223
  }
1243
1224
 
1244
1225
  if (params.did) {
@@ -1263,13 +1244,6 @@ const checkDuplicateComponents = (components = []) => {
1263
1244
  .join(', ')}`
1264
1245
  );
1265
1246
  }
1266
-
1267
- const duplicateMountPoints = components.filter(
1268
- (item, index) => components.findIndex((x) => isPathPrefixEqual(x.mountPoint, item.mountPoint)) !== index
1269
- );
1270
- if (duplicateMountPoints.length) {
1271
- throw new Error(`mount point must be unique: ${duplicateMountPoints.map((x) => x.mountPoint).join(', ')}`);
1272
- }
1273
1247
  };
1274
1248
 
1275
1249
  const getDiffFiles = async (inputFiles, sourceDir) => {
@@ -1327,32 +1301,30 @@ const needBlockletDownload = (blocklet, oldBlocklet) => {
1327
1301
  return get(oldBlocklet, 'meta.dist.integrity') !== get(blocklet, 'meta.dist.integrity');
1328
1302
  };
1329
1303
 
1330
- const findAvailableDid = (meta, siblings) => {
1331
- const reg = /-(\d+)$/;
1332
- const match = reg.exec(meta.name);
1333
-
1334
- const newName = match ? meta.name.replace(reg, `-${Number(match[1]) + 1}`) : `${meta.name}-1`;
1335
- const newDid = toBlockletDid(newName);
1336
- const newMeta = { name: newName, did: newDid };
1337
-
1338
- if (!(siblings || []).some((x) => x.did === newMeta.did)) {
1339
- return newMeta;
1304
+ const isDidMatchName = (did, name) => {
1305
+ if (isValidDid(did) && name === did) {
1306
+ return true;
1340
1307
  }
1341
1308
 
1342
- return findAvailableDid(newMeta, siblings);
1309
+ return toBlockletDid(name) === did;
1343
1310
  };
1344
1311
 
1312
+ /**
1313
+ * set bundleDid and bundleMeta in meta
1314
+ * update name and did to server index
1315
+ * in app structure 2.0, application's bundleDid, bundleName, meta, did will be same
1316
+ */
1345
1317
  const ensureMeta = (meta, { name, did } = {}) => {
1346
- if (name && did && toBlockletDid(name) !== did) {
1347
- throw new Error('name does not match with did');
1318
+ if (name && did && !isDidMatchName(did, name)) {
1319
+ throw new Error(`name does not match with did: ${name}, ${did}`);
1348
1320
  }
1349
1321
 
1350
1322
  const newMeta = {
1351
1323
  ...meta,
1352
1324
  };
1353
1325
 
1354
- if (!newMeta.did || !newMeta.name || toBlockletDid(newMeta.name) !== newMeta.did) {
1355
- throw new Error('name does not match with did in meta');
1326
+ if (!newMeta.did || !newMeta.name || !isDidMatchName(newMeta.did, newMeta.name)) {
1327
+ throw new Error(`name does not match with did in meta: ${newMeta.name}, ${newMeta.did}`);
1356
1328
  }
1357
1329
 
1358
1330
  if (!meta.bundleDid) {
@@ -1740,7 +1712,10 @@ const checkDuplicateAppSk = async ({ sk, did, states }) => {
1740
1712
 
1741
1713
  const checkDuplicateMountPoint = (blocklet, mountPoint) => {
1742
1714
  const err = new Error(`cannot add duplicate mount point, ${mountPoint || '/'} already exist`);
1743
- if (normalizePathPrefix(blocklet.mountPoint) === normalizePathPrefix(mountPoint)) {
1715
+ if (
1716
+ blocklet.meta?.group !== BlockletGroup.gateway &&
1717
+ normalizePathPrefix(blocklet.mountPoint) === normalizePathPrefix(mountPoint)
1718
+ ) {
1744
1719
  throw err;
1745
1720
  }
1746
1721
 
@@ -1777,12 +1752,79 @@ const validateInServerless = ({ blockletMeta }) => {
1777
1752
  }
1778
1753
  };
1779
1754
 
1755
+ const checkStructVersion = (blocklet) => {
1756
+ if (blocklet.structVersion !== APP_STRUCT_VERSION) {
1757
+ throw new Error('You should migrate the application first');
1758
+ }
1759
+ };
1760
+
1761
+ const isVersionCompatible = (actualVersion, expectedRange) =>
1762
+ !expectedRange || expectedRange === 'latest' || semver.satisfies(actualVersion, expectedRange);
1763
+
1764
+ const checkVersionCompatibility = (components) => {
1765
+ for (const component of components) {
1766
+ // eslint-disable-next-line no-loop-func
1767
+ forEachBlockletSync(component, (x) => {
1768
+ const dependencies = x.dependencies || [];
1769
+ dependencies.forEach((dep) => {
1770
+ const { did, version: expectedRange } = dep;
1771
+ const exist = components.find((y) => y.meta.did === did);
1772
+ if (exist && !isVersionCompatible(exist.meta.version, expectedRange)) {
1773
+ throw new Error(
1774
+ `Check version compatible failed: ${component.meta.title || component.meta.did} expects ${
1775
+ exist.meta.title || exist.meta.did
1776
+ }'s version to be ${expectedRange}, but actual is ${exist.meta.version}`
1777
+ );
1778
+ }
1779
+ });
1780
+ });
1781
+ }
1782
+ };
1783
+
1784
+ const filterDuplicateComponents = (components = [], currents = []) => {
1785
+ const arr = [];
1786
+
1787
+ components.forEach((component) => {
1788
+ if (currents.some((x) => x.meta.did === component.meta.did)) {
1789
+ return;
1790
+ }
1791
+
1792
+ const index = arr.findIndex((x) => x.meta.did === component.meta.did);
1793
+ if (index > -1) {
1794
+ const exist = arr[index];
1795
+
1796
+ // 选择最小版本:
1797
+ // 如果 com1 和 com2 都依赖某个 component, com1 声明依赖版本 1.0.0, 实际获取版本 1.0.0; com2 声明依赖版本 latest(不限), 实际获取版本 2.0.0 -> 则应该取 1.0.0
1798
+ if (semver.lt(component.meta.version, exist.meta.version)) {
1799
+ arr.splice(index, 1, component);
1800
+ }
1801
+ } else {
1802
+ arr.push(component);
1803
+ }
1804
+ });
1805
+
1806
+ return arr;
1807
+ };
1808
+
1809
+ const validateBlockletMeta = (meta, opts = {}) => {
1810
+ fixAndValidateService(meta);
1811
+ return validateMeta(meta, { ensureName: true, skipValidateDidName: true, schemaOptions: { ...opts } });
1812
+ };
1813
+
1814
+ const getBlockletKnownAs = (blocklet) => {
1815
+ const alsoKnownAs = [blocklet.appDid];
1816
+ if (Array.isArray(blocklet.migratedFrom)) {
1817
+ blocklet.migratedFrom.filter((x) => x.appDid !== blocklet.appPid).forEach((x) => alsoKnownAs.push(x.appDid));
1818
+ }
1819
+
1820
+ return alsoKnownAs.filter(Boolean).map(toDid);
1821
+ };
1822
+
1780
1823
  module.exports = {
1781
1824
  consumeServerlessNFT,
1782
1825
  forEachBlocklet,
1783
1826
  getBlockletMetaFromUrl: (url) => getBlockletMetaFromUrl(url, { logger }),
1784
- parseChildrenFromMeta,
1785
- parseChildren,
1827
+ parseComponents,
1786
1828
  getComponentDirs,
1787
1829
  getAppSystemEnvironments,
1788
1830
  getAppOverwrittenEnvironments,
@@ -1807,15 +1849,14 @@ module.exports = {
1807
1849
  getDiskInfo,
1808
1850
  getRuntimeInfo,
1809
1851
  mergeMeta,
1810
- fixAndVerifyMetaFromStore,
1811
1852
  getUpdateMetaList,
1812
1853
  getTypeFromInstallParams,
1813
1854
  findWebInterface,
1814
1855
  checkDuplicateComponents,
1856
+ filterDuplicateComponents,
1815
1857
  getDiffFiles,
1816
1858
  getBundleDir,
1817
1859
  needBlockletDownload,
1818
- findAvailableDid,
1819
1860
  ensureMeta,
1820
1861
  getBlocklet,
1821
1862
  ensureEnvDefault,
@@ -1829,4 +1870,8 @@ module.exports = {
1829
1870
  checkDuplicateMountPoint,
1830
1871
  validateStore,
1831
1872
  validateInServerless,
1873
+ checkStructVersion,
1874
+ checkVersionCompatibility,
1875
+ validateBlockletMeta,
1876
+ getBlockletKnownAs,
1832
1877
  };
@@ -1,22 +1,13 @@
1
- const { DEFAULT_IP_DOMAIN_SUFFIX } = require('@abtnode/constant');
1
+ const { DEFAULT_IP_DOMAIN_SUFFIX, SLOT_FOR_IP_DNS_SITE } = require('@abtnode/constant');
2
2
  const { encode: encodeBase32 } = require('@abtnode/util/lib/base32');
3
3
  const formatName = require('@abtnode/util/lib/format-name');
4
4
 
5
- const SLOT_FOR_IP_DNS_SITE = '888-888-888-888';
6
-
7
- const hiddenInterfaceNames = ['public', 'publicUrl'];
8
-
9
- const getIpDnsDomainForBlocklet = (blocklet, blockletInterface) => {
10
- const nameSuffix = blocklet.meta.did.slice(-3).toLowerCase();
11
- const iName = hiddenInterfaceNames.includes(blockletInterface.name) ? '' : blockletInterface.name.toLowerCase();
12
-
13
- return `${formatName(blocklet.meta.name)}-${nameSuffix}${
14
- iName ? '-' : ''
15
- }${iName}-${SLOT_FOR_IP_DNS_SITE}.${DEFAULT_IP_DOMAIN_SUFFIX}`;
5
+ const getIpDnsDomainForBlocklet = (blocklet) => {
6
+ return `${encodeBase32(blocklet.meta.did)}-${SLOT_FOR_IP_DNS_SITE}.${DEFAULT_IP_DOMAIN_SUFFIX}`;
16
7
  };
17
8
 
18
- const getDidDomainForBlocklet = ({ blockletAppDid, didDomain }) => {
19
- return `${encodeBase32(blockletAppDid)}.${didDomain}`;
9
+ const getDidDomainForBlocklet = ({ appPid, didDomain }) => {
10
+ return `${encodeBase32(appPid)}.${didDomain}`;
20
11
  };
21
12
 
22
13
  module.exports = { getIpDnsDomainForBlocklet, getDidDomainForBlocklet, formatName };
@@ -0,0 +1,33 @@
1
+ const { isFreeBlocklet } = require('@blocklet/meta/lib/util');
2
+
3
+ const logger = require('@abtnode/logger')('getMetaFromUrl');
4
+
5
+ const { getBlockletMetaFromUrl } = require('./blocklet');
6
+ const { getStoreMeta, parseSourceUrl } = require('./store');
7
+ const { getFactoryState } = require('./chain');
8
+
9
+ const getMetaFromUrl = async ({ url, checkPrice = false }) => {
10
+ const meta = await getBlockletMetaFromUrl(url);
11
+ let isFree = isFreeBlocklet(meta);
12
+
13
+ if (checkPrice && !isFree && meta.nftFactory) {
14
+ try {
15
+ const registryMeta = await getStoreMeta(new URL(url).origin);
16
+
17
+ if (registryMeta.chainHost) {
18
+ const state = await getFactoryState(registryMeta.chainHost, meta.nftFactory);
19
+ if (state) {
20
+ isFree = false;
21
+ }
22
+ }
23
+ } catch (error) {
24
+ logger.warn('failed when checking if the blocklet is free', { did: meta.did, error });
25
+ }
26
+ }
27
+
28
+ const { inStore, registryUrl } = await parseSourceUrl(url);
29
+
30
+ return { meta, isFree, inStore, registryUrl };
31
+ };
32
+
33
+ module.exports = getMetaFromUrl;
package/lib/util/index.js CHANGED
@@ -16,7 +16,6 @@ const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
16
16
  const axios = require('@abtnode/util/lib/axios');
17
17
  const { encode: encodeBase32 } = require('@abtnode/util/lib/base32');
18
18
  const parseBlockletMeta = require('@blocklet/meta/lib/parse');
19
- const { validateMeta, fixAndValidateService } = require('@blocklet/meta/lib/validate');
20
19
  const { BlockletStatus } = require('@blocklet/constant');
21
20
  const { replaceSlotToIp } = require('@blocklet/meta/lib/util');
22
21
  const {
@@ -493,10 +492,6 @@ const lib = {
493
492
  getDataDirs,
494
493
  getBaseUrls,
495
494
  getBlockletMeta: parseBlockletMeta,
496
- validateBlockletMeta: (meta, opts = {}) => {
497
- fixAndValidateService(meta);
498
- return validateMeta(meta, opts);
499
- },
500
495
  expandBundle,
501
496
  findInterfaceByName,
502
497
  findInterfacePortByName,
package/lib/util/store.js CHANGED
@@ -3,14 +3,47 @@ const pick = require('lodash/pick');
3
3
  const isBase64 = require('is-base64');
4
4
 
5
5
  const { BLOCKLET_STORE_API_PREFIX, BLOCKLET_STORE_META_PATH } = require('@abtnode/constant');
6
+ const { validateMeta, fixAndValidateService } = require('@blocklet/meta/lib/validate');
7
+ const verifyMultiSig = require('@blocklet/meta/lib/verify-multi-sig');
6
8
 
7
9
  const { name } = require('../../package.json');
8
10
  const logger = require('@abtnode/logger')(`${name}:util:registry`); // eslint-disable-line
9
11
 
10
12
  const request = require('./request');
11
- const { fixAndVerifyMetaFromStore } = require('./blocklet');
12
13
 
13
- const validateRegistryURL = async (registry) => {
14
+ const fixAndVerifyMetaFromStore = (meta) => {
15
+ const sanitized = validateMeta(meta, { ensureDist: false, schemaOptions: { noDefaults: true, stripUnknown: true } });
16
+
17
+ if (meta.children) {
18
+ sanitized.children = meta.children;
19
+ delete sanitized.components;
20
+ }
21
+
22
+ if (meta.navigation) {
23
+ meta.navigation.forEach((nav, index) => {
24
+ if (nav.child) {
25
+ sanitized.navigation[index].child = nav.child;
26
+ delete sanitized.navigation[index].component;
27
+ }
28
+ });
29
+ }
30
+
31
+ // verify
32
+
33
+ try {
34
+ const result = verifyMultiSig(sanitized);
35
+ if (!result) {
36
+ throw new Error('invalid signature from developer or store');
37
+ }
38
+ } catch (err) {
39
+ throw new Error(`Invalid blocklet meta: ${err.message}`);
40
+ }
41
+
42
+ // this step comes last because it has side effects: the meta is changed
43
+ return fixAndValidateService(meta);
44
+ };
45
+
46
+ const validateStoreUrl = async (registry) => {
14
47
  const url = joinURL(registry, BLOCKLET_STORE_API_PREFIX, `/blocklets.json?__t__=${Date.now()}`);
15
48
  try {
16
49
  const res = await request.get(url);
@@ -26,7 +59,7 @@ const validateRegistryURL = async (registry) => {
26
59
  }
27
60
  };
28
61
 
29
- const getRegistryMeta = async (registry) => {
62
+ const getStoreMeta = async (registry) => {
30
63
  try {
31
64
  const url = joinURL(registry, BLOCKLET_STORE_META_PATH, `?__t__=${Date.now()}`);
32
65
  const { data } = await request.get(url);
@@ -66,7 +99,7 @@ const parseSourceUrl = async (url) => {
66
99
  const match = pathname.match(/^\/api\/blocklets\/(\w*)\/blocklet\.json/);
67
100
  if (match) {
68
101
  try {
69
- const m = await getRegistryMeta(origin);
102
+ const m = await getStoreMeta(origin);
70
103
  if (m && m.id) {
71
104
  return {
72
105
  inStore: true,
@@ -108,6 +141,9 @@ const resolveTarballURL = async ({ did, tarball = '', storeUrl = '' }) => {
108
141
  return joinURL(storeUrl, 'api', 'blocklets', did, tarball);
109
142
  };
110
143
 
144
+ const getBlockletMetaUrl = ({ did, storeUrl }) =>
145
+ joinURL(storeUrl, BLOCKLET_STORE_API_PREFIX, `/blocklets/${did}/blocklet.json`);
146
+
111
147
  const getBlockletMeta = async ({ did, storeUrl }) => {
112
148
  const url = joinURL(storeUrl, BLOCKLET_STORE_API_PREFIX, `/blocklets/${did}/blocklet.json?__t__=${Date.now()}`);
113
149
 
@@ -133,9 +169,11 @@ const getBlockletMeta = async ({ did, storeUrl }) => {
133
169
  };
134
170
 
135
171
  module.exports = {
136
- validateRegistryURL,
137
- getRegistryMeta,
172
+ validateStoreUrl,
173
+ getStoreMeta,
138
174
  parseSourceUrl,
139
175
  getBlockletMeta,
140
176
  resolveTarballURL,
177
+ getBlockletMetaUrl,
178
+ fixAndVerifyMetaFromStore,
141
179
  };
@@ -35,6 +35,9 @@ class WalletSender extends BaseSender {
35
35
 
36
36
  await sendToUser(adminUsers, message, sender, process.env.ABT_NODE_SERVICE_PORT);
37
37
  } catch (error) {
38
+ delete error.request;
39
+ delete error.response;
40
+ delete error.config;
38
41
  logger.error('failed to push notification to wallet', { error });
39
42
  }
40
43
  }