@abtnode/core 1.8.69-beta-b0bb2d67 → 1.8.69-beta-650a290b

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.
Files changed (29) hide show
  1. package/lib/api/team.js +2 -2
  2. package/lib/blocklet/downloader/resolve-download.js +31 -0
  3. package/lib/blocklet/manager/base.js +8 -16
  4. package/lib/blocklet/manager/disk.js +367 -1659
  5. package/lib/blocklet/manager/helper/{install-from-backup.js → install-application-from-backup.js} +26 -19
  6. package/lib/blocklet/manager/helper/install-application-from-dev.js +94 -0
  7. package/lib/blocklet/manager/helper/install-application-from-general.js +188 -0
  8. package/lib/blocklet/manager/helper/install-component-from-dev.js +84 -0
  9. package/lib/blocklet/manager/helper/install-component-from-upload.js +181 -0
  10. package/lib/blocklet/manager/helper/install-component-from-url.js +173 -0
  11. package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +446 -0
  12. package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
  13. package/lib/blocklet/storage/backup/spaces.js +14 -14
  14. package/lib/blocklet/storage/restore/blocklets.js +7 -9
  15. package/lib/blocklet/storage/restore/spaces.js +6 -6
  16. package/lib/index.js +7 -7
  17. package/lib/router/helper.js +5 -7
  18. package/lib/states/blocklet-extras.js +44 -0
  19. package/lib/states/blocklet.js +56 -4
  20. package/lib/states/node.js +1 -0
  21. package/lib/states/site.js +15 -6
  22. package/lib/team/manager.js +5 -0
  23. package/lib/util/blocklet.js +182 -132
  24. package/lib/util/get-domain-for-blocklet.js +5 -14
  25. package/lib/util/get-meta-from-url.js +33 -0
  26. package/lib/util/index.js +0 -5
  27. package/lib/util/store.js +44 -6
  28. package/lib/webhook/sender/wallet/index.js +3 -0
  29. package/package.json +28 -26
@@ -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,
@@ -624,6 +623,11 @@ const deleteBlockletProcess = async (blocklet, { preDelete = noop, skippedProces
624
623
  await forEachBlocklet(
625
624
  blocklet,
626
625
  async (b, { ancestors }) => {
626
+ // NOTICE: 如果不判断 group, 在 github action 中测试 disk.spec.js 时会报错, 但是在 mac 中跑测试不会报错
627
+ if (b.meta?.group === BlockletGroup.gateway) {
628
+ return;
629
+ }
630
+
627
631
  if (skippedProcessIds.includes(b.env.processId)) {
628
632
  logger.info(`skip delete process ${b.env.processId}`);
629
633
  return;
@@ -743,75 +747,78 @@ const reloadProcess = (processId) =>
743
747
  });
744
748
  });
745
749
 
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
750
  /**
760
- * this function has side effect on children
751
+ * this function has side effect to component (will set component.children: Array<staticComponent> )
752
+ * this function has side effect to dynamicComponents (will push dynamicComponent in dynamicComponents)
753
+ *
754
+ * @param {Component} component
755
+ * @param {{
756
+ * ancestors: Array<{meta}>
757
+ * dynamicComponents: Array<{Component}>
758
+ * }} context
759
+ * @returns {{
760
+ * dynamicComponents: Array<Component>,
761
+ * staticComponents: Array<Component>,
762
+ * }}
761
763
  */
762
- const parseChildrenFromMeta = async (src, context = {}) => {
763
- const { dynamic, ancestors = [] } = context;
764
+ const parseComponents = async (component, context = {}) => {
765
+ const { ancestors = [], dynamicComponents = [] } = context;
764
766
  if (ancestors.length > 40) {
765
767
  throw new Error('The depth of component should not exceed 40');
766
768
  }
767
769
 
768
- const configs = Array.isArray(src) ? src : getComponentConfig(src) || [];
770
+ const configs = getComponentConfig(component.meta) || [];
769
771
 
770
772
  if (!configs || !configs.length) {
771
- return [];
773
+ return {
774
+ staticComponents: [],
775
+ dynamicComponents,
776
+ };
772
777
  }
773
778
 
774
- const children = [];
779
+ const staticComponents = [];
775
780
 
781
+ // FIXME @linchen 改成并行获取
776
782
  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}`);
783
+ const isStatic = !!config.static;
784
+
785
+ if (isStatic) {
786
+ if (!config.name) {
787
+ throw new Error(`Name does not found in child ${config.name}`);
788
+ }
789
+
790
+ if (!config.mountPoint) {
791
+ throw new Error(`MountPoint does not found in child ${config.name}`);
792
+ }
780
793
  }
781
794
 
782
795
  const urls = getSourceUrlsFromConfig(config);
783
796
 
784
- let m;
797
+ let rawMeta;
785
798
  try {
786
- m = await getBlockletMetaFromUrls(urls, { logger });
787
- } catch {
788
- throw new Error(`Failed get component meta: ${config.title || config.name}`);
799
+ rawMeta = await getBlockletMetaFromUrls(urls, { logger });
800
+ } catch (error) {
801
+ throw new Error(
802
+ `Failed get component meta. Component: ${rawMeta.title || rawMeta.did}, reason: ${error.message}`
803
+ );
789
804
  }
790
805
 
791
- validateBlockletMeta(m, { ensureDist: true });
792
-
793
- if (!isComponentBlocklet(m)) {
794
- throw new Error(`The blocklet cannot be a component: ${m.title}`);
806
+ if (rawMeta.group === BlockletGroup.gateway) {
807
+ throw new Error(`Cannot add gateway component ${rawMeta.title || rawMeta.did}`);
795
808
  }
796
809
 
797
- const webInterface = findWebInterface(m);
798
- if (!webInterface) {
799
- throw new Error(`Web interface does not found in child ${config.name}`);
810
+ validateBlockletMeta(rawMeta, { ensureDist: true });
811
+
812
+ if (!isComponentBlocklet(rawMeta)) {
813
+ throw new Error(`The blocklet cannot be a component: ${rawMeta.title}`);
800
814
  }
801
815
 
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
- }
816
+ const webInterface = findWebInterface(rawMeta);
817
+ if (!webInterface) {
818
+ throw new Error(`Web interface does not found in component ${rawMeta.title || rawMeta.name}`);
812
819
  }
813
820
 
814
- const meta = ensureMeta(m, { name: config.name });
821
+ const meta = ensureMeta(rawMeta, { name: isStatic ? config.name : null });
815
822
 
816
823
  // check circular dependencies
817
824
  if (ancestors.map((x) => x.meta?.bundleDid).indexOf(meta.bundleDid) > -1) {
@@ -822,28 +829,44 @@ const parseChildrenFromMeta = async (src, context = {}) => {
822
829
  meta.title = config.title;
823
830
  meta.title = await titleSchema.validateAsync(config.title);
824
831
  }
832
+
825
833
  if (config.description) {
826
834
  meta.description = await descriptionSchema.validateAsync(config.description);
827
835
  }
828
836
 
837
+ const mountPoint = isStatic ? config.mountPoint : config.mountPoint || `/${meta.did}`;
838
+
829
839
  const child = {
830
840
  mountPoint,
831
841
  meta,
832
- bundleSource: config.source || { url: config.resolved },
842
+ bundleSource: config.source,
843
+ dependencies: [],
833
844
  };
834
845
 
835
- child.children = await parseChildrenFromMeta(meta, {
836
- ...context,
837
- ancestors: [...ancestors, { meta, mountPoint }],
838
- dynamic: false,
839
- });
846
+ if (isStatic) {
847
+ staticComponents.push(child);
848
+ } else {
849
+ dynamicComponents.push(child);
850
+ component.dependencies = component.dependencies || [];
851
+ component.dependencies.push({
852
+ did: child.meta.bundleDid,
853
+ required: !!config.required,
854
+ version: config.source.version || 'latest',
855
+ });
856
+ }
840
857
 
841
- children.push(child);
858
+ await parseComponents(child, {
859
+ ancestors: [...ancestors, { meta }],
860
+ dynamicComponents,
861
+ });
842
862
  }
843
863
 
844
- parseChildren(children, configs, { dynamic });
864
+ component.children = staticComponents;
845
865
 
846
- return children;
866
+ return {
867
+ staticComponents,
868
+ dynamicComponents,
869
+ };
847
870
  };
848
871
 
849
872
  const validateBlocklet = (blocklet) =>
@@ -1166,43 +1189,6 @@ const mergeMeta = (source, childrenMeta = []) => {
1166
1189
  });
1167
1190
  };
1168
1191
 
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
1192
  const getUpdateMetaList = (oldBlocklet = {}, newBlocklet = {}) => {
1207
1193
  const oldMap = {};
1208
1194
  forEachChildSync(oldBlocklet, (b, { id }) => {
@@ -1238,7 +1224,7 @@ const getTypeFromInstallParams = (params) => {
1238
1224
  }
1239
1225
 
1240
1226
  if (params.file) {
1241
- return BLOCKLET_INSTALL_TYPE.UPLOAD;
1227
+ throw new Error('install from upload is not supported');
1242
1228
  }
1243
1229
 
1244
1230
  if (params.did) {
@@ -1263,13 +1249,6 @@ const checkDuplicateComponents = (components = []) => {
1263
1249
  .join(', ')}`
1264
1250
  );
1265
1251
  }
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
1252
  };
1274
1253
 
1275
1254
  const getDiffFiles = async (inputFiles, sourceDir) => {
@@ -1327,32 +1306,30 @@ const needBlockletDownload = (blocklet, oldBlocklet) => {
1327
1306
  return get(oldBlocklet, 'meta.dist.integrity') !== get(blocklet, 'meta.dist.integrity');
1328
1307
  };
1329
1308
 
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;
1309
+ const isDidMatchName = (did, name) => {
1310
+ if (isValidDid(did) && name === did) {
1311
+ return true;
1340
1312
  }
1341
1313
 
1342
- return findAvailableDid(newMeta, siblings);
1314
+ return toBlockletDid(name) === did;
1343
1315
  };
1344
1316
 
1317
+ /**
1318
+ * set bundleDid and bundleMeta in meta
1319
+ * update name and did to server index
1320
+ * in app structure 2.0, application's bundleDid, bundleName, meta, did will be same
1321
+ */
1345
1322
  const ensureMeta = (meta, { name, did } = {}) => {
1346
- if (name && did && toBlockletDid(name) !== did) {
1347
- throw new Error('name does not match with did');
1323
+ if (name && did && !isDidMatchName(did, name)) {
1324
+ throw new Error(`name does not match with did: ${name}, ${did}`);
1348
1325
  }
1349
1326
 
1350
1327
  const newMeta = {
1351
1328
  ...meta,
1352
1329
  };
1353
1330
 
1354
- if (!newMeta.did || !newMeta.name || toBlockletDid(newMeta.name) !== newMeta.did) {
1355
- throw new Error('name does not match with did in meta');
1331
+ if (!newMeta.did || !newMeta.name || !isDidMatchName(newMeta.did, newMeta.name)) {
1332
+ throw new Error(`name does not match with did in meta: ${newMeta.name}, ${newMeta.did}`);
1356
1333
  }
1357
1334
 
1358
1335
  if (!meta.bundleDid) {
@@ -1740,7 +1717,10 @@ const checkDuplicateAppSk = async ({ sk, did, states }) => {
1740
1717
 
1741
1718
  const checkDuplicateMountPoint = (blocklet, mountPoint) => {
1742
1719
  const err = new Error(`cannot add duplicate mount point, ${mountPoint || '/'} already exist`);
1743
- if (normalizePathPrefix(blocklet.mountPoint) === normalizePathPrefix(mountPoint)) {
1720
+ if (
1721
+ blocklet.meta?.group !== BlockletGroup.gateway &&
1722
+ normalizePathPrefix(blocklet.mountPoint) === normalizePathPrefix(mountPoint)
1723
+ ) {
1744
1724
  throw err;
1745
1725
  }
1746
1726
 
@@ -1777,12 +1757,79 @@ const validateInServerless = ({ blockletMeta }) => {
1777
1757
  }
1778
1758
  };
1779
1759
 
1760
+ const checkStructVersion = (blocklet) => {
1761
+ if (blocklet.structVersion !== APP_STRUCT_VERSION) {
1762
+ throw new Error('You should migrate the application first');
1763
+ }
1764
+ };
1765
+
1766
+ const isVersionCompatible = (actualVersion, expectedRange) =>
1767
+ !expectedRange || expectedRange === 'latest' || semver.satisfies(actualVersion, expectedRange);
1768
+
1769
+ const checkVersionCompatibility = (components) => {
1770
+ for (const component of components) {
1771
+ // eslint-disable-next-line no-loop-func
1772
+ forEachBlockletSync(component, (x) => {
1773
+ const dependencies = x.dependencies || [];
1774
+ dependencies.forEach((dep) => {
1775
+ const { did, version: expectedRange } = dep;
1776
+ const exist = components.find((y) => y.meta.did === did);
1777
+ if (exist && !isVersionCompatible(exist.meta.version, expectedRange)) {
1778
+ throw new Error(
1779
+ `Check version compatible failed: ${component.meta.title || component.meta.did} expects ${
1780
+ exist.meta.title || exist.meta.did
1781
+ }'s version to be ${expectedRange}, but actual is ${exist.meta.version}`
1782
+ );
1783
+ }
1784
+ });
1785
+ });
1786
+ }
1787
+ };
1788
+
1789
+ const filterDuplicateComponents = (components = [], currents = []) => {
1790
+ const arr = [];
1791
+
1792
+ components.forEach((component) => {
1793
+ if (currents.some((x) => x.meta.did === component.meta.did)) {
1794
+ return;
1795
+ }
1796
+
1797
+ const index = arr.findIndex((x) => x.meta.did === component.meta.did);
1798
+ if (index > -1) {
1799
+ const exist = arr[index];
1800
+
1801
+ // 选择最小版本:
1802
+ // 如果 com1 和 com2 都依赖某个 component, com1 声明依赖版本 1.0.0, 实际获取版本 1.0.0; com2 声明依赖版本 latest(不限), 实际获取版本 2.0.0 -> 则应该取 1.0.0
1803
+ if (semver.lt(component.meta.version, exist.meta.version)) {
1804
+ arr.splice(index, 1, component);
1805
+ }
1806
+ } else {
1807
+ arr.push(component);
1808
+ }
1809
+ });
1810
+
1811
+ return arr;
1812
+ };
1813
+
1814
+ const validateBlockletMeta = (meta, opts = {}) => {
1815
+ fixAndValidateService(meta);
1816
+ return validateMeta(meta, { ensureName: true, skipValidateDidName: true, schemaOptions: { ...opts } });
1817
+ };
1818
+
1819
+ const getBlockletKnownAs = (blocklet) => {
1820
+ const alsoKnownAs = [blocklet.appDid];
1821
+ if (Array.isArray(blocklet.migratedFrom)) {
1822
+ blocklet.migratedFrom.filter((x) => x.appDid !== blocklet.appPid).forEach((x) => alsoKnownAs.push(x.appDid));
1823
+ }
1824
+
1825
+ return alsoKnownAs.filter(Boolean).map(toDid);
1826
+ };
1827
+
1780
1828
  module.exports = {
1781
1829
  consumeServerlessNFT,
1782
1830
  forEachBlocklet,
1783
1831
  getBlockletMetaFromUrl: (url) => getBlockletMetaFromUrl(url, { logger }),
1784
- parseChildrenFromMeta,
1785
- parseChildren,
1832
+ parseComponents,
1786
1833
  getComponentDirs,
1787
1834
  getAppSystemEnvironments,
1788
1835
  getAppOverwrittenEnvironments,
@@ -1807,15 +1854,14 @@ module.exports = {
1807
1854
  getDiskInfo,
1808
1855
  getRuntimeInfo,
1809
1856
  mergeMeta,
1810
- fixAndVerifyMetaFromStore,
1811
1857
  getUpdateMetaList,
1812
1858
  getTypeFromInstallParams,
1813
1859
  findWebInterface,
1814
1860
  checkDuplicateComponents,
1861
+ filterDuplicateComponents,
1815
1862
  getDiffFiles,
1816
1863
  getBundleDir,
1817
1864
  needBlockletDownload,
1818
- findAvailableDid,
1819
1865
  ensureMeta,
1820
1866
  getBlocklet,
1821
1867
  ensureEnvDefault,
@@ -1829,4 +1875,8 @@ module.exports = {
1829
1875
  checkDuplicateMountPoint,
1830
1876
  validateStore,
1831
1877
  validateInServerless,
1878
+ checkStructVersion,
1879
+ checkVersionCompatibility,
1880
+ validateBlockletMeta,
1881
+ getBlockletKnownAs,
1832
1882
  };
@@ -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
  }