@abtnode/core 1.8.36 → 1.8.38
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/node.js +0 -73
- package/lib/api/team.js +61 -5
- package/lib/blocklet/manager/disk.js +251 -115
- package/lib/event.js +15 -11
- package/lib/index.js +4 -7
- package/lib/states/audit-log.js +1 -1
- package/lib/states/blocklet.js +15 -0
- package/lib/states/node.js +0 -4
- package/lib/states/notification.js +14 -28
- package/lib/team/manager.js +30 -0
- package/lib/util/blocklet.js +90 -5
- package/lib/util/default-node-config.js +3 -13
- package/lib/util/index.js +44 -0
- package/lib/util/store.js +6 -6
- package/lib/validators/blocklet.js +0 -2
- package/lib/validators/role.js +4 -0
- package/package.json +25 -25
|
@@ -13,6 +13,7 @@ const capitalize = require('lodash/capitalize');
|
|
|
13
13
|
const { Throttle } = require('stream-throttle');
|
|
14
14
|
const LRU = require('lru-cache');
|
|
15
15
|
const joi = require('joi');
|
|
16
|
+
const { isNFTExpired } = require('@abtnode/util/lib/nft');
|
|
16
17
|
const { sign } = require('@arcblock/jwt');
|
|
17
18
|
const { isValid: isValidDid } = require('@arcblock/did');
|
|
18
19
|
const { verifyPresentation } = require('@arcblock/vc');
|
|
@@ -25,7 +26,12 @@ const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager');
|
|
|
25
26
|
const downloadFile = require('@abtnode/util/lib/download-file');
|
|
26
27
|
const Lock = require('@abtnode/util/lib/lock');
|
|
27
28
|
const { getVcFromPresentation } = require('@abtnode/util/lib/vc');
|
|
28
|
-
const {
|
|
29
|
+
const {
|
|
30
|
+
VC_TYPE_BLOCKLET_PURCHASE,
|
|
31
|
+
WHO_CAN_ACCESS,
|
|
32
|
+
SERVER_ROLES,
|
|
33
|
+
WHO_CAN_ACCESS_PREFIX_ROLES,
|
|
34
|
+
} = require('@abtnode/constant');
|
|
29
35
|
|
|
30
36
|
const getBlockletEngine = require('@blocklet/meta/lib/engine');
|
|
31
37
|
const {
|
|
@@ -40,13 +46,21 @@ const {
|
|
|
40
46
|
forEachChild,
|
|
41
47
|
getComponentId,
|
|
42
48
|
getComponentBundleId,
|
|
49
|
+
isPreferenceKey,
|
|
50
|
+
getRolesFromAuthConfig,
|
|
43
51
|
} = require('@blocklet/meta/lib/util');
|
|
44
52
|
const getComponentProcessId = require('@blocklet/meta/lib/get-component-process-id');
|
|
45
53
|
const validateBlockletEntry = require('@blocklet/meta/lib/entry');
|
|
46
54
|
const toBlockletDid = require('@blocklet/meta/lib/did');
|
|
47
55
|
const { validateMeta } = require('@blocklet/meta/lib/validate');
|
|
48
56
|
const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
|
|
49
|
-
const {
|
|
57
|
+
const {
|
|
58
|
+
titleSchema,
|
|
59
|
+
descriptionSchema,
|
|
60
|
+
mountPointSchema,
|
|
61
|
+
logoSchema,
|
|
62
|
+
environmentNameSchema,
|
|
63
|
+
} = require('@blocklet/meta/lib/schema');
|
|
50
64
|
const hasReservedKey = require('@blocklet/meta/lib/has-reserved-key');
|
|
51
65
|
|
|
52
66
|
const { toExternalBlocklet } = toBlockletDid;
|
|
@@ -109,6 +123,8 @@ const {
|
|
|
109
123
|
ensureMeta,
|
|
110
124
|
getBlocklet,
|
|
111
125
|
ensureEnvDefault,
|
|
126
|
+
getConfigFromPreferences,
|
|
127
|
+
consumeServerlessNFT,
|
|
112
128
|
} = require('../../util/blocklet');
|
|
113
129
|
const StoreUtil = require('../../util/store');
|
|
114
130
|
const states = require('../../states');
|
|
@@ -120,6 +136,7 @@ const runMigrationScripts = require('../migration');
|
|
|
120
136
|
const hooks = require('../hooks');
|
|
121
137
|
const { formatName } = require('../../util/get-domain-for-blocklet');
|
|
122
138
|
const handleInstanceInStore = require('../../util/public-to-store');
|
|
139
|
+
const { getNFTState } = require('../../util');
|
|
123
140
|
|
|
124
141
|
const {
|
|
125
142
|
isInProgress,
|
|
@@ -173,7 +190,7 @@ const getSkippedProcessIds = ({ newBlocklet, oldBlocklet, context = {} }) => {
|
|
|
173
190
|
};
|
|
174
191
|
|
|
175
192
|
const getBlockletIndex = (meta, controller) =>
|
|
176
|
-
controller ? toExternalBlocklet(meta.name, controller.
|
|
193
|
+
controller ? toExternalBlocklet(meta.name, controller.nftId) : { did: meta.did, name: meta.name };
|
|
177
194
|
|
|
178
195
|
class BlockletManager extends BaseBlockletManager {
|
|
179
196
|
/**
|
|
@@ -269,8 +286,8 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
269
286
|
}
|
|
270
287
|
|
|
271
288
|
if (source === BlockletSource.registry) {
|
|
272
|
-
const { did, controller, sync, delay } = params;
|
|
273
|
-
return this._installFromStore({ did, controller, sync, delay }, context);
|
|
289
|
+
const { did, controller, sync, delay, storeUrl } = params;
|
|
290
|
+
return this._installFromStore({ did, controller, sync, delay, storeUrl }, context);
|
|
274
291
|
}
|
|
275
292
|
|
|
276
293
|
if (source === BlockletSource.custom) {
|
|
@@ -377,7 +394,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
377
394
|
return { meta, isFree, inStore, registryUrl };
|
|
378
395
|
}
|
|
379
396
|
|
|
380
|
-
async getBlockletByBundle({ did, name }, context) {
|
|
397
|
+
async getBlockletByBundle({ did, name, serverlessNftId }, context) {
|
|
381
398
|
if (toBlockletDid(name) !== did) {
|
|
382
399
|
throw new Error('did and name does not match');
|
|
383
400
|
}
|
|
@@ -386,11 +403,15 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
386
403
|
throw new Error('user does not exist');
|
|
387
404
|
}
|
|
388
405
|
|
|
406
|
+
if (context.user.role === SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER && !serverlessNftId) {
|
|
407
|
+
throw new Error('serverless nft id is required');
|
|
408
|
+
}
|
|
409
|
+
|
|
389
410
|
let blockletDid = did;
|
|
390
411
|
let isExternal = false;
|
|
391
412
|
|
|
392
|
-
if (
|
|
393
|
-
blockletDid = toExternalBlocklet(name,
|
|
413
|
+
if (serverlessNftId) {
|
|
414
|
+
blockletDid = toExternalBlocklet(name, serverlessNftId, { didOnly: true });
|
|
394
415
|
isExternal = true;
|
|
395
416
|
}
|
|
396
417
|
|
|
@@ -512,7 +533,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
512
533
|
const error = Array.isArray(err) ? err[0] : err;
|
|
513
534
|
logger.error('Failed to start blocklet', { error, did, name: blocklet.meta.name });
|
|
514
535
|
const description = `Start blocklet ${blocklet.meta.name} failed with error: ${error.message}`;
|
|
515
|
-
|
|
536
|
+
this._createNotification(did, {
|
|
516
537
|
title: 'Start Blocklet Failed',
|
|
517
538
|
description,
|
|
518
539
|
entityType: 'blocklet',
|
|
@@ -564,7 +585,12 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
564
585
|
|
|
565
586
|
if (updateStatus) {
|
|
566
587
|
const res = await this.status(did, { forceSync: true });
|
|
588
|
+
// send notification to websocket channel
|
|
567
589
|
this.emit(BlockletEvents.statusChange, res);
|
|
590
|
+
|
|
591
|
+
// send notification to wallet
|
|
592
|
+
this.emit(BlockletEvents.stopped, res);
|
|
593
|
+
|
|
568
594
|
return res;
|
|
569
595
|
}
|
|
570
596
|
|
|
@@ -585,7 +611,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
585
611
|
const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped);
|
|
586
612
|
this.emit(BlockletEvents.statusChange, state);
|
|
587
613
|
|
|
588
|
-
|
|
614
|
+
this._createNotification(did, {
|
|
589
615
|
title: 'Blocklet Restart Failed',
|
|
590
616
|
description: `Blocklet ${did} restart failed with error: ${err.message || 'queue exception'}`,
|
|
591
617
|
entityType: 'blocklet',
|
|
@@ -636,7 +662,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
636
662
|
});
|
|
637
663
|
|
|
638
664
|
const doc = await this._deleteBlocklet({ did, keepData, keepLogsDir, keepConfigs }, context);
|
|
639
|
-
|
|
665
|
+
this._createNotification(doc.meta.did, {
|
|
640
666
|
title: 'Blocklet Deleted',
|
|
641
667
|
description: `Blocklet ${doc.meta.name}@${doc.meta.version} is deleted.`,
|
|
642
668
|
entityType: 'blocklet',
|
|
@@ -650,7 +676,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
650
676
|
logger.info('blocklet is corrupted, will delete again', { did });
|
|
651
677
|
const doc = await this._deleteBlocklet({ did, keepData, keepLogsDir, keepConfigs }, context);
|
|
652
678
|
|
|
653
|
-
|
|
679
|
+
this._createNotification(doc.meta.did, {
|
|
654
680
|
title: 'Blocklet Deleted',
|
|
655
681
|
description: `Blocklet ${doc.meta.name}@${doc.meta.version} is deleted.`,
|
|
656
682
|
entityType: 'blocklet',
|
|
@@ -775,7 +801,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
775
801
|
const newBlocklet = await this.ensureBlocklet(rootDid);
|
|
776
802
|
this.emit(BlockletEvents.upgraded, { blocklet: newBlocklet, context: { ...context, createAuditLog: false } }); // trigger router refresh
|
|
777
803
|
|
|
778
|
-
|
|
804
|
+
this._createNotification(newBlocklet.meta.did, {
|
|
779
805
|
title: 'Component Deleted',
|
|
780
806
|
description: `Component ${child.meta.name} of ${newBlocklet.meta.name} is successfully deleted.`,
|
|
781
807
|
entityType: 'blocklet',
|
|
@@ -836,6 +862,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
836
862
|
return result;
|
|
837
863
|
}
|
|
838
864
|
|
|
865
|
+
// Get blocklet by blockletDid or appDid
|
|
839
866
|
async detail({ did, attachConfig = true, attachRuntimeInfo = true }, context) {
|
|
840
867
|
if (!did) {
|
|
841
868
|
throw new Error('did should not be empty');
|
|
@@ -931,69 +958,88 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
931
958
|
// run hook
|
|
932
959
|
const nodeEnvironments = await states.node.getEnvironments();
|
|
933
960
|
for (const x of newConfigs) {
|
|
934
|
-
if (
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
}
|
|
961
|
+
if (x.custom === true) {
|
|
962
|
+
// custom key
|
|
963
|
+
await environmentNameSchema.validateAsync(x.key);
|
|
964
|
+
} else if (BLOCKLET_CONFIGURABLE_KEY[x.key] && x.key.startsWith('BLOCKLET_')) {
|
|
965
|
+
// app key
|
|
966
|
+
if (childDids.length) {
|
|
967
|
+
logger.error(`Cannot set ${x.key} to child blocklet`, [dids]);
|
|
968
|
+
throw new Error(`Cannot set ${x.key} to child blocklet`);
|
|
969
|
+
}
|
|
938
970
|
|
|
939
|
-
|
|
940
|
-
try {
|
|
941
|
-
fromSecretKey(x.value);
|
|
942
|
-
} catch {
|
|
971
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK) {
|
|
943
972
|
try {
|
|
944
|
-
fromSecretKey(x.value
|
|
973
|
+
fromSecretKey(x.value);
|
|
945
974
|
} catch {
|
|
946
|
-
|
|
975
|
+
try {
|
|
976
|
+
fromSecretKey(x.value, 'eth');
|
|
977
|
+
} catch {
|
|
978
|
+
throw new Error('Invalid custom blocklet secret key');
|
|
979
|
+
}
|
|
947
980
|
}
|
|
948
|
-
}
|
|
949
981
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
982
|
+
// Ensure sk is not used by other blocklets, otherwise we may encounter appDid collision
|
|
983
|
+
const blocklets = await states.blocklet.getBlocklets({});
|
|
984
|
+
const others = blocklets.filter((b) => b.meta.did !== did);
|
|
985
|
+
if (
|
|
986
|
+
others.some(
|
|
987
|
+
(b) => b.environments.find((e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK).value === x.value
|
|
988
|
+
)
|
|
989
|
+
) {
|
|
990
|
+
throw new Error('Invalid custom blocklet secret key: already used by another blocklet');
|
|
991
|
+
}
|
|
955
992
|
}
|
|
956
|
-
}
|
|
957
993
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
if (x.key === 'BLOCKLET_APP_DESCRIPTION') {
|
|
963
|
-
x.value = await descriptionSchema.validateAsync(x.value);
|
|
964
|
-
}
|
|
994
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_NAME) {
|
|
995
|
+
x.value = await titleSchema.validateAsync(x.value);
|
|
996
|
+
}
|
|
965
997
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
998
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_DESCRIPTION) {
|
|
999
|
+
x.value = await descriptionSchema.validateAsync(x.value);
|
|
1000
|
+
}
|
|
969
1001
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1002
|
+
if (
|
|
1003
|
+
[BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO, BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_SQUARE].includes(
|
|
1004
|
+
x.key
|
|
1005
|
+
)
|
|
1006
|
+
) {
|
|
1007
|
+
x.value = await logoSchema.validateAsync(x.value);
|
|
973
1008
|
}
|
|
974
|
-
}
|
|
975
1009
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1010
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_WALLET_TYPE) {
|
|
1011
|
+
if (['default', 'eth'].includes(x.value) === false) {
|
|
1012
|
+
throw new Error('Invalid blocklet wallet type, only "default" and "eth" are supported');
|
|
1013
|
+
}
|
|
979
1014
|
}
|
|
980
|
-
}
|
|
981
1015
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
throw new Error('BLOCKLET_PASSPORT_COLOR must be a hex encoded color, eg. #ffeeaa');
|
|
1016
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_DELETABLE) {
|
|
1017
|
+
if (['yes', 'no'].includes(x.value) === false) {
|
|
1018
|
+
throw new Error('BLOCKLET_DELETABLE must be either "yes" or "no"');
|
|
986
1019
|
}
|
|
987
1020
|
}
|
|
988
|
-
}
|
|
989
1021
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1022
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_PASSPORT_COLOR) {
|
|
1023
|
+
if (x.value && x.value !== 'auto') {
|
|
1024
|
+
if (x.value.length !== 7 || !isHex(x.value.slice(-6))) {
|
|
1025
|
+
throw new Error('BLOCKLET_PASSPORT_COLOR must be a hex encoded color, eg. #ffeeaa');
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
993
1028
|
}
|
|
994
1029
|
|
|
995
|
-
if (
|
|
996
|
-
|
|
1030
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT) {
|
|
1031
|
+
if (isEmpty(x.value)) {
|
|
1032
|
+
throw new Error(`${x.key} can not be empty`);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (!urlHttp(x.value)) {
|
|
1036
|
+
throw new Error(`${x.key}(${x.value}) is not a valid http address`);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
} else if (!BLOCKLET_CONFIGURABLE_KEY[x.key] && !isPreferenceKey(x)) {
|
|
1040
|
+
if (!(blocklet.meta.environments || []).some((y) => y.name === x.key)) {
|
|
1041
|
+
// forbid unknown format key
|
|
1042
|
+
throw new Error(`unknown format key: ${x.key}`);
|
|
997
1043
|
}
|
|
998
1044
|
}
|
|
999
1045
|
|
|
@@ -1035,18 +1081,53 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1035
1081
|
}
|
|
1036
1082
|
|
|
1037
1083
|
async updateWhoCanAccess({ did, whoCanAccess }) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1084
|
+
const dids = Array.isArray(did) ? did : [did];
|
|
1085
|
+
|
|
1086
|
+
const [rootDid] = dids;
|
|
1087
|
+
|
|
1088
|
+
const isApp = dids.length === 1;
|
|
1089
|
+
|
|
1090
|
+
try {
|
|
1091
|
+
// check exist
|
|
1092
|
+
if (!(await this.hasBlocklet({ did: rootDid }))) {
|
|
1093
|
+
throw new Error('The blocklet does not exist');
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// validate input
|
|
1097
|
+
if (
|
|
1098
|
+
!whoCanAccess.startsWith(WHO_CAN_ACCESS_PREFIX_ROLES) &&
|
|
1099
|
+
!Object.values(WHO_CAN_ACCESS).includes(whoCanAccess)
|
|
1100
|
+
) {
|
|
1101
|
+
throw new Error(`The value of whoCanAccess is invalid: ${whoCanAccess}`);
|
|
1102
|
+
} else if (whoCanAccess.startsWith(WHO_CAN_ACCESS_PREFIX_ROLES)) {
|
|
1103
|
+
if (!whoCanAccess.substring(WHO_CAN_ACCESS_PREFIX_ROLES.length).trim()) {
|
|
1104
|
+
throw new Error('Roles in whoCanAccess cannot be empty');
|
|
1105
|
+
}
|
|
1041
1106
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1107
|
+
if (whoCanAccess.length > 200) {
|
|
1108
|
+
throw new Error('The length of whoCanAccess should not exceed 200');
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const roleNames = (await this.teamManager.getRoles(rootDid)).map((x) => x.name);
|
|
1112
|
+
const accessRoleNames = getRolesFromAuthConfig({ whoCanAccess });
|
|
1113
|
+
const noExistNames = accessRoleNames.filter((x) => !roleNames.includes(x));
|
|
1114
|
+
if (noExistNames.length) {
|
|
1115
|
+
throw new Error(`Found no exist role names: ${noExistNames.join(',')}`);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
} catch (error) {
|
|
1119
|
+
logger.error(error.message);
|
|
1120
|
+
throw error;
|
|
1045
1121
|
}
|
|
1046
1122
|
|
|
1047
|
-
|
|
1123
|
+
if (isApp) {
|
|
1124
|
+
await states.blockletExtras.setSettings(rootDid, { whoCanAccess });
|
|
1125
|
+
} else {
|
|
1126
|
+
const configs = [{ key: BLOCKLET_CONFIGURABLE_KEY.COMPONENT_ACCESS_WHO, value: whoCanAccess }];
|
|
1127
|
+
await states.blockletExtras.setConfigs(dids, configs);
|
|
1128
|
+
}
|
|
1048
1129
|
|
|
1049
|
-
const blocklet = await this.ensureBlocklet(
|
|
1130
|
+
const blocklet = await this.ensureBlocklet(rootDid);
|
|
1050
1131
|
|
|
1051
1132
|
this.emit(BlockletEvents.updated, { meta: { did: blocklet.meta.did } });
|
|
1052
1133
|
|
|
@@ -1130,23 +1211,23 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1130
1211
|
/**
|
|
1131
1212
|
* upgrade blocklet from registry
|
|
1132
1213
|
*/
|
|
1133
|
-
async upgrade({ did,
|
|
1214
|
+
async upgrade({ did, storeUrl, sync }, context) {
|
|
1134
1215
|
const blocklet = await states.blocklet.getBlocklet(did);
|
|
1135
1216
|
|
|
1136
|
-
if (!
|
|
1217
|
+
if (!storeUrl && blocklet.source === BlockletSource.url) {
|
|
1137
1218
|
return this._installFromUrl({ url: blocklet.deployedFrom }, context);
|
|
1138
1219
|
}
|
|
1139
1220
|
|
|
1140
|
-
// TODO: 查看了下目前页面中的升级按钮,都是会传
|
|
1141
|
-
if (!
|
|
1142
|
-
throw new Error('Wrong upgrade source, empty
|
|
1221
|
+
// TODO: 查看了下目前页面中的升级按钮,都是会传 storeUrl 过来的,这个函数里的逻辑感觉需要在以后做一个简化
|
|
1222
|
+
if (!storeUrl && blocklet.source !== BlockletSource.registry) {
|
|
1223
|
+
throw new Error('Wrong upgrade source, empty storeUrl or not installed from blocklet registry');
|
|
1143
1224
|
}
|
|
1144
1225
|
|
|
1145
|
-
const
|
|
1226
|
+
const upgradeFromStore = storeUrl || blocklet.deployedFrom;
|
|
1146
1227
|
|
|
1147
1228
|
let newVersionMeta = await StoreUtil.getBlockletMeta({
|
|
1148
1229
|
did: blocklet.meta.bundleDid,
|
|
1149
|
-
|
|
1230
|
+
storeUrl: upgradeFromStore,
|
|
1150
1231
|
});
|
|
1151
1232
|
newVersionMeta = ensureMeta(newVersionMeta, { name: blocklet.meta.name, did: blocklet.meta.did });
|
|
1152
1233
|
|
|
@@ -1197,7 +1278,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1197
1278
|
return this._upgrade({
|
|
1198
1279
|
meta: newVersionMeta,
|
|
1199
1280
|
source: BlockletSource.registry,
|
|
1200
|
-
deployedFrom:
|
|
1281
|
+
deployedFrom: upgradeFromStore,
|
|
1201
1282
|
context,
|
|
1202
1283
|
sync,
|
|
1203
1284
|
});
|
|
@@ -1358,7 +1439,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1358
1439
|
logger.error('queue failed', { entity: 'blocklet', action, did, version, name, error: err });
|
|
1359
1440
|
await this._rollback(action, did, oldBlocklet);
|
|
1360
1441
|
this.emit(`blocklet.${action}.failed`, { did, version, err });
|
|
1361
|
-
|
|
1442
|
+
this._createNotification(did, {
|
|
1362
1443
|
title: `Blocklet ${capitalize(action)} Failed`,
|
|
1363
1444
|
description: `Blocklet ${name}@${version} ${action} failed with error: ${err.message || 'queue exception'}`,
|
|
1364
1445
|
entityType: 'blocklet',
|
|
@@ -1382,7 +1463,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1382
1463
|
|
|
1383
1464
|
const meta = getBlockletMeta(folder);
|
|
1384
1465
|
if (meta.group !== 'static' && (!meta.scripts || !meta.scripts.dev)) {
|
|
1385
|
-
throw new Error('Incorrect blocklet
|
|
1466
|
+
throw new Error('Incorrect blocklet.yml: missing `scripts.dev` field');
|
|
1386
1467
|
}
|
|
1387
1468
|
|
|
1388
1469
|
if (rootDid) {
|
|
@@ -1757,7 +1838,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1757
1838
|
message: err.message,
|
|
1758
1839
|
},
|
|
1759
1840
|
});
|
|
1760
|
-
|
|
1841
|
+
this._createNotification(did, {
|
|
1761
1842
|
title: 'Blocklet Download Failed',
|
|
1762
1843
|
description: `Blocklet ${name}@${version} download failed with error: ${err.message}`,
|
|
1763
1844
|
entityType: 'blocklet',
|
|
@@ -1895,7 +1976,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1895
1976
|
await this.deleteProcess({ did }, context);
|
|
1896
1977
|
await states.blocklet.setBlockletStatus(did, BlockletStatus.error);
|
|
1897
1978
|
|
|
1898
|
-
|
|
1979
|
+
this._createNotification(did, {
|
|
1899
1980
|
title: 'Blocklet Start Failed',
|
|
1900
1981
|
description: `Blocklet ${name} start failed: ${error.message}`,
|
|
1901
1982
|
entityType: 'blocklet',
|
|
@@ -1975,16 +2056,19 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1975
2056
|
* @memberof BlockletManager
|
|
1976
2057
|
*/
|
|
1977
2058
|
async _installFromStore(params, context) {
|
|
1978
|
-
const { did,
|
|
2059
|
+
const { did, storeUrl, sync, delay, controller } = params;
|
|
1979
2060
|
|
|
1980
2061
|
logger.debug('start install blocklet', { did });
|
|
1981
2062
|
if (!isValidDid(did)) {
|
|
1982
2063
|
throw new Error('Blocklet did is invalid');
|
|
1983
2064
|
}
|
|
1984
2065
|
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2066
|
+
if (!storeUrl) {
|
|
2067
|
+
throw new Error('registry url should not be empty');
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
const info = await StoreUtil.getRegistryMeta(storeUrl);
|
|
2071
|
+
const meta = await StoreUtil.getBlockletMeta({ did, storeUrl });
|
|
1988
2072
|
if (!meta) {
|
|
1989
2073
|
throw new Error('Can not install blocklet that not found in registry');
|
|
1990
2074
|
}
|
|
@@ -2013,7 +2097,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2013
2097
|
return this._install({
|
|
2014
2098
|
meta: ensureMeta(meta, { did: blockletDid, name: blockletName }),
|
|
2015
2099
|
source: BlockletSource.registry,
|
|
2016
|
-
deployedFrom: info.cdnUrl ||
|
|
2100
|
+
deployedFrom: info.cdnUrl || storeUrl,
|
|
2017
2101
|
sync,
|
|
2018
2102
|
delay,
|
|
2019
2103
|
controller,
|
|
@@ -2056,10 +2140,10 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2056
2140
|
if (inStore) {
|
|
2057
2141
|
const exist = await states.blocklet.getBlocklet(blockletDid);
|
|
2058
2142
|
if (exist) {
|
|
2059
|
-
return this.upgrade({ did: blockletDid, registryUrl, sync, delay }, context);
|
|
2143
|
+
return this.upgrade({ did: blockletDid, storeUrl: registryUrl, sync, delay }, context);
|
|
2060
2144
|
}
|
|
2061
2145
|
|
|
2062
|
-
return this._installFromStore({ did: bundleDid,
|
|
2146
|
+
return this._installFromStore({ did: bundleDid, storeUrl: registryUrl, controller, sync, delay }, context);
|
|
2063
2147
|
}
|
|
2064
2148
|
|
|
2065
2149
|
const meta = ensureMeta(bundleMeta, { name: blockletName, did: blockletDid });
|
|
@@ -2514,6 +2598,11 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2514
2598
|
await refreshAccessibleExternalNodeIp(nodeInfo);
|
|
2515
2599
|
},
|
|
2516
2600
|
},
|
|
2601
|
+
{
|
|
2602
|
+
name: 'delete-expired-external-blocklet',
|
|
2603
|
+
time: '0 */30 * * * *', // 30min
|
|
2604
|
+
fn: () => this._deleteExpiredExternalBlocklet(),
|
|
2605
|
+
},
|
|
2517
2606
|
];
|
|
2518
2607
|
}
|
|
2519
2608
|
|
|
@@ -2633,7 +2722,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2633
2722
|
logger.error('failed to remove blocklet on install error', { did: meta.did, error: e });
|
|
2634
2723
|
}
|
|
2635
2724
|
|
|
2636
|
-
|
|
2725
|
+
this._createNotification(did, {
|
|
2637
2726
|
title: 'Blocklet Install Failed',
|
|
2638
2727
|
description: `Blocklet ${name}@${version} install failed with error: ${err.message || 'queue exception'}`,
|
|
2639
2728
|
entityType: 'blocklet',
|
|
@@ -2646,7 +2735,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2646
2735
|
return blocklet1;
|
|
2647
2736
|
} catch (err) {
|
|
2648
2737
|
logger.error('failed to install blocklet', { name, did, version, error: err });
|
|
2649
|
-
|
|
2738
|
+
this._createNotification(did, {
|
|
2650
2739
|
title: 'Blocklet Install Failed',
|
|
2651
2740
|
description: `Blocklet ${name}@${version} install failed with error: ${err.message || 'queue exception'}`,
|
|
2652
2741
|
entityType: 'blocklet',
|
|
@@ -2744,7 +2833,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2744
2833
|
downgrade: BlockletEvents.downgradeFailed,
|
|
2745
2834
|
};
|
|
2746
2835
|
this.emit(eventNames[action], { blocklet: oldBlocklet, context });
|
|
2747
|
-
|
|
2836
|
+
this._createNotification(did, {
|
|
2748
2837
|
title: `Blocklet ${capitalize(action)} Failed`,
|
|
2749
2838
|
description: `Blocklet ${name}@${version} ${action} failed with error: ${err.message || 'queue exception'}`,
|
|
2750
2839
|
entityType: 'blocklet',
|
|
@@ -2935,7 +3024,12 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2935
3024
|
});
|
|
2936
3025
|
}
|
|
2937
3026
|
|
|
2938
|
-
|
|
3027
|
+
if (blocklet.controller && process.env.NODE_ENV !== 'test') {
|
|
3028
|
+
const nodeInfo = await states.node.read();
|
|
3029
|
+
await consumeServerlessNFT({ nftId: blocklet.controller.nftId, nodeInfo, blocklet });
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
this._createNotification(did, {
|
|
2939
3033
|
title: 'Blocklet Installed',
|
|
2940
3034
|
description: `Blocklet ${meta.name}@${meta.version} is installed successfully. (Source: ${
|
|
2941
3035
|
deployedFrom || fromBlockletSource(source)
|
|
@@ -2958,7 +3052,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2958
3052
|
message: err.message,
|
|
2959
3053
|
},
|
|
2960
3054
|
});
|
|
2961
|
-
|
|
3055
|
+
this._createNotification(did, {
|
|
2962
3056
|
title: 'Blocklet Install Failed',
|
|
2963
3057
|
description: `Blocklet ${meta.name}@${meta.version} install failed with error: ${err.message}`,
|
|
2964
3058
|
entityType: 'blocklet',
|
|
@@ -3053,7 +3147,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3053
3147
|
downgrade: BlockletEvents.downgraded,
|
|
3054
3148
|
};
|
|
3055
3149
|
this.emit(eventNames[action], { blocklet, context });
|
|
3056
|
-
|
|
3150
|
+
this._createNotification(did, {
|
|
3057
3151
|
title: `Blocklet ${capitalize(action)} Success`,
|
|
3058
3152
|
description: `Blocklet ${name}@${version} ${action} successfully. (Source: ${
|
|
3059
3153
|
deployedFrom || fromBlockletSource(source)
|
|
@@ -3081,9 +3175,9 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3081
3175
|
upgrade: BlockletEvents.upgradeFailed,
|
|
3082
3176
|
downgrade: BlockletEvents.downgradeFailed,
|
|
3083
3177
|
};
|
|
3084
|
-
this.emit(eventNames[action], { blocklet: oldBlocklet, context });
|
|
3178
|
+
this.emit(eventNames[action], { blocklet: { ...oldBlocklet, error: { message: err.message } }, context });
|
|
3085
3179
|
|
|
3086
|
-
|
|
3180
|
+
this._createNotification(did, {
|
|
3087
3181
|
title: `Blocklet ${capitalize(action)} Failed`,
|
|
3088
3182
|
description: `Blocklet ${name}@${version} ${action} failed with error: ${err.message}`,
|
|
3089
3183
|
entityType: 'blocklet',
|
|
@@ -3490,11 +3584,11 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3490
3584
|
}
|
|
3491
3585
|
|
|
3492
3586
|
async _setConfigsFromMeta(did, childDid) {
|
|
3493
|
-
const blocklet = await states.
|
|
3587
|
+
const blocklet = await getBlocklet({ states, dataDirs: this.dataDirs, did, validateEnv: false, ensureDirs: false });
|
|
3494
3588
|
|
|
3495
3589
|
if (!childDid) {
|
|
3496
3590
|
await forEachBlocklet(blocklet, async (b, { ancestors }) => {
|
|
3497
|
-
const environments = get(b.meta, 'environments', []);
|
|
3591
|
+
const environments = [...get(b.meta, 'environments', []), ...getConfigFromPreferences(b)];
|
|
3498
3592
|
|
|
3499
3593
|
// remove default if ancestors has a value
|
|
3500
3594
|
ensureEnvDefault(environments, ancestors);
|
|
@@ -3507,7 +3601,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3507
3601
|
await forEachBlocklet(child, async (b, { ancestors }) => {
|
|
3508
3602
|
await states.blockletExtras.setConfigs(
|
|
3509
3603
|
[blocklet.meta.did, ...ancestors.map((x) => x.meta.did), b.meta.did],
|
|
3510
|
-
get(b.meta, 'environments', [])
|
|
3604
|
+
[...get(b.meta, 'environments', []), ...getConfigFromPreferences(child)]
|
|
3511
3605
|
);
|
|
3512
3606
|
});
|
|
3513
3607
|
}
|
|
@@ -3556,13 +3650,13 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3556
3650
|
}
|
|
3557
3651
|
|
|
3558
3652
|
async _getLatestBlockletVersionFromStore({ blocklet, version }) {
|
|
3559
|
-
const { deployedFrom:
|
|
3653
|
+
const { deployedFrom: storeUrl } = blocklet;
|
|
3560
3654
|
const { did, bundleDid } = blocklet.meta;
|
|
3561
3655
|
|
|
3562
3656
|
let versions = this.cachedBlockletVersions.get(did);
|
|
3563
3657
|
|
|
3564
3658
|
if (!versions) {
|
|
3565
|
-
const item = await StoreUtil.getBlockletMeta({ did: bundleDid,
|
|
3659
|
+
const item = await StoreUtil.getBlockletMeta({ did: bundleDid, storeUrl });
|
|
3566
3660
|
|
|
3567
3661
|
if (!item) {
|
|
3568
3662
|
return null;
|
|
@@ -3579,23 +3673,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3579
3673
|
return null;
|
|
3580
3674
|
}
|
|
3581
3675
|
|
|
3582
|
-
|
|
3583
|
-
if (blocklet.source === BlockletSource.registry && blocklet.deployedFrom) {
|
|
3584
|
-
const latestFromSameRegistry = versions.find((x) => x.registryUrl === blocklet.deployedFrom);
|
|
3585
|
-
if (latestFromSameRegistry) {
|
|
3586
|
-
return latestFromSameRegistry;
|
|
3587
|
-
}
|
|
3588
|
-
}
|
|
3589
|
-
|
|
3590
|
-
// Otherwise try upgrading from other store
|
|
3591
|
-
let latestBlockletVersion = versions[0];
|
|
3592
|
-
versions.forEach((item) => {
|
|
3593
|
-
if (semver.lt(latestBlockletVersion.version, item.version)) {
|
|
3594
|
-
latestBlockletVersion = item;
|
|
3595
|
-
}
|
|
3596
|
-
});
|
|
3597
|
-
|
|
3598
|
-
return latestBlockletVersion;
|
|
3676
|
+
return versions[0];
|
|
3599
3677
|
}
|
|
3600
3678
|
|
|
3601
3679
|
async _getLatestBlockletVersionFromUrl({ blocklet, version }) {
|
|
@@ -3654,6 +3732,64 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3654
3732
|
|
|
3655
3733
|
await forEachBlocklet(blocklet, postInstall, { parallel: true });
|
|
3656
3734
|
}
|
|
3735
|
+
|
|
3736
|
+
async _createNotification(did, notification) {
|
|
3737
|
+
try {
|
|
3738
|
+
const isExternal = await states.blocklet.isExternalBlocklet(did);
|
|
3739
|
+
|
|
3740
|
+
if (isExternal) {
|
|
3741
|
+
return;
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
await states.notification.create(notification);
|
|
3745
|
+
} catch (error) {
|
|
3746
|
+
logger.error('create notification failed', { error });
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3750
|
+
async _deleteExpiredExternalBlocklet() {
|
|
3751
|
+
try {
|
|
3752
|
+
logger.info('start check expired external blocklet');
|
|
3753
|
+
const blocklets = await states.blocklet.getBlocklets({
|
|
3754
|
+
controller: {
|
|
3755
|
+
$exists: true,
|
|
3756
|
+
},
|
|
3757
|
+
});
|
|
3758
|
+
|
|
3759
|
+
const nodeInfo = await states.node.read();
|
|
3760
|
+
|
|
3761
|
+
const tasks = blocklets.map(async (blocklet) => {
|
|
3762
|
+
try {
|
|
3763
|
+
const assetState = await getNFTState(nodeInfo.launcher.chainHost, blocklet.controller.nftId);
|
|
3764
|
+
const isExpired = isNFTExpired(assetState);
|
|
3765
|
+
if (isExpired) {
|
|
3766
|
+
logger.info('the blocklet already expired', {
|
|
3767
|
+
blockletId: blocklet._id,
|
|
3768
|
+
nftId: blocklet.controller.nftId,
|
|
3769
|
+
});
|
|
3770
|
+
|
|
3771
|
+
// FIXME: 后面需要考虑将数据保留一段时间
|
|
3772
|
+
await this.delete({ did: blocklet.meta.did, keepData: false, keepConfigs: false, keepLogsDir: false });
|
|
3773
|
+
logger.info('the expired blocklet already deleted', {
|
|
3774
|
+
blockletId: blocklet._id,
|
|
3775
|
+
nftId: blocklet.controller.nftId,
|
|
3776
|
+
});
|
|
3777
|
+
}
|
|
3778
|
+
} catch (error) {
|
|
3779
|
+
logger.error('get asset state failed when check expired external blocklet', {
|
|
3780
|
+
blockletId: blocklet._id,
|
|
3781
|
+
nftId: blocklet.controller.nftId,
|
|
3782
|
+
});
|
|
3783
|
+
}
|
|
3784
|
+
});
|
|
3785
|
+
|
|
3786
|
+
await Promise.all(tasks);
|
|
3787
|
+
|
|
3788
|
+
logger.info('check expired external blocklet end');
|
|
3789
|
+
} catch (error) {
|
|
3790
|
+
logger.info('check expired external blocklet failed', { error });
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3657
3793
|
}
|
|
3658
3794
|
|
|
3659
3795
|
module.exports = BlockletManager;
|