@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.
- package/lib/api/team.js +2 -2
- package/lib/blocklet/downloader/resolve-download.js +31 -0
- package/lib/blocklet/manager/base.js +8 -16
- package/lib/blocklet/manager/disk.js +367 -1659
- package/lib/blocklet/manager/helper/{install-from-backup.js → install-application-from-backup.js} +26 -19
- package/lib/blocklet/manager/helper/install-application-from-dev.js +94 -0
- package/lib/blocklet/manager/helper/install-application-from-general.js +188 -0
- package/lib/blocklet/manager/helper/install-component-from-dev.js +84 -0
- package/lib/blocklet/manager/helper/install-component-from-upload.js +181 -0
- package/lib/blocklet/manager/helper/install-component-from-url.js +173 -0
- package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +446 -0
- package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
- package/lib/blocklet/storage/backup/spaces.js +14 -14
- package/lib/blocklet/storage/restore/blocklets.js +7 -9
- package/lib/blocklet/storage/restore/spaces.js +6 -6
- package/lib/index.js +7 -7
- package/lib/router/helper.js +5 -7
- package/lib/states/blocklet-extras.js +44 -0
- package/lib/states/blocklet.js +56 -4
- package/lib/states/node.js +1 -0
- package/lib/states/site.js +15 -6
- package/lib/team/manager.js +5 -0
- package/lib/util/blocklet.js +182 -132
- package/lib/util/get-domain-for-blocklet.js +5 -14
- package/lib/util/get-meta-from-url.js +33 -0
- package/lib/util/index.js +0 -5
- package/lib/util/store.js +44 -6
- package/lib/webhook/sender/wallet/index.js +3 -0
- package/package.json +28 -26
package/lib/util/blocklet.js
CHANGED
|
@@ -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) =>
|
|
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
|
|
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
|
|
763
|
-
const {
|
|
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 =
|
|
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
|
|
779
|
+
const staticComponents = [];
|
|
775
780
|
|
|
781
|
+
// FIXME @linchen 改成并行获取
|
|
776
782
|
for (const config of configs) {
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
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
|
|
797
|
+
let rawMeta;
|
|
785
798
|
try {
|
|
786
|
-
|
|
787
|
-
} catch {
|
|
788
|
-
throw new Error(
|
|
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
|
-
|
|
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
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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(
|
|
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
|
|
842
|
+
bundleSource: config.source,
|
|
843
|
+
dependencies: [],
|
|
833
844
|
};
|
|
834
845
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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
|
-
|
|
858
|
+
await parseComponents(child, {
|
|
859
|
+
ancestors: [...ancestors, { meta }],
|
|
860
|
+
dynamicComponents,
|
|
861
|
+
});
|
|
842
862
|
}
|
|
843
863
|
|
|
844
|
-
|
|
864
|
+
component.children = staticComponents;
|
|
845
865
|
|
|
846
|
-
return
|
|
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
|
-
|
|
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
|
|
1331
|
-
|
|
1332
|
-
|
|
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
|
|
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 &&
|
|
1347
|
-
throw new Error(
|
|
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 ||
|
|
1355
|
-
throw new Error(
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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 = ({
|
|
19
|
-
return `${encodeBase32(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
137
|
-
|
|
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
|
}
|