@abtnode/core 1.15.17 → 1.16.0-beta-b16cb035
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 +67 -69
- package/lib/api/team.js +386 -55
- package/lib/blocklet/downloader/blocklet-downloader.js +226 -0
- package/lib/blocklet/downloader/bundle-downloader.js +272 -0
- package/lib/blocklet/downloader/constants.js +3 -0
- package/lib/blocklet/downloader/resolve-download.js +199 -0
- package/lib/blocklet/extras.js +83 -26
- package/lib/blocklet/hooks.js +18 -65
- package/lib/blocklet/manager/base.js +10 -16
- package/lib/blocklet/manager/disk.js +1679 -1566
- package/lib/blocklet/manager/helper/install-application-from-backup.js +177 -0
- 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 +450 -0
- package/lib/blocklet/manager/helper/rollback-cache.js +41 -0
- package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
- package/lib/blocklet/migration.js +30 -52
- package/lib/blocklet/storage/backup/audit-log.js +27 -0
- package/lib/blocklet/storage/backup/base.js +62 -0
- package/lib/blocklet/storage/backup/blocklet-extras.js +92 -0
- package/lib/blocklet/storage/backup/blocklet.js +70 -0
- package/lib/blocklet/storage/backup/blocklets.js +74 -0
- package/lib/blocklet/storage/backup/data.js +19 -0
- package/lib/blocklet/storage/backup/logs.js +24 -0
- package/lib/blocklet/storage/backup/routing-rule.js +19 -0
- package/lib/blocklet/storage/backup/spaces.js +240 -0
- package/lib/blocklet/storage/restore/base.js +67 -0
- package/lib/blocklet/storage/restore/blocklet-extras.js +86 -0
- package/lib/blocklet/storage/restore/blocklet.js +56 -0
- package/lib/blocklet/storage/restore/blocklets.js +43 -0
- package/lib/blocklet/storage/restore/logs.js +21 -0
- package/lib/blocklet/storage/restore/spaces.js +156 -0
- package/lib/blocklet/storage/utils/hash.js +51 -0
- package/lib/blocklet/storage/utils/zip.js +43 -0
- package/lib/cert.js +206 -0
- package/lib/event.js +237 -64
- package/lib/index.js +191 -83
- package/lib/migrations/1.0.21-update-config.js +1 -1
- package/lib/migrations/1.0.22-max-memory.js +1 -1
- package/lib/migrations/1.0.25.js +1 -1
- package/lib/migrations/1.0.32-update-config.js +1 -1
- package/lib/migrations/1.0.33-blocklets.js +1 -1
- package/lib/migrations/1.5.20-registry.js +15 -0
- package/lib/migrations/1.6.17-blocklet-children.js +48 -0
- package/lib/migrations/1.6.21-rename-ip-echo-domain.js +35 -0
- package/lib/migrations/1.6.4-security.js +59 -0
- package/lib/migrations/1.6.5-security.js +60 -0
- package/lib/migrations/1.6.9-update-node-info-and-certificate.js +38 -0
- package/lib/migrations/1.7.1-blocklet-setup.js +18 -0
- package/lib/migrations/1.7.12-blocklet-meta.js +51 -0
- package/lib/migrations/1.7.15-blocklet-bundle-source.js +42 -0
- package/lib/migrations/1.7.20-blocklet-component.js +41 -0
- package/lib/migrations/1.8.33-blocklet-mem-limit.js +20 -0
- package/lib/migrations/README.md +1 -1
- package/lib/migrations/index.js +6 -2
- package/lib/monitor/blocklet-runtime-monitor.js +200 -0
- package/lib/monitor/get-history-list.js +37 -0
- package/lib/monitor/node-runtime-monitor.js +228 -0
- package/lib/router/helper.js +572 -497
- package/lib/router/index.js +85 -21
- package/lib/router/manager.js +146 -187
- package/lib/states/README.md +36 -1
- package/lib/states/access-key.js +39 -17
- package/lib/states/audit-log.js +462 -0
- package/lib/states/base.js +4 -213
- package/lib/states/blocklet-extras.js +194 -138
- package/lib/states/blocklet.js +361 -104
- package/lib/states/cache.js +8 -6
- package/lib/states/challenge.js +5 -5
- package/lib/states/index.js +19 -36
- package/lib/states/migration.js +4 -4
- package/lib/states/node.js +135 -46
- package/lib/states/notification.js +22 -35
- package/lib/states/session.js +17 -9
- package/lib/states/site.js +50 -25
- package/lib/states/user.js +74 -20
- package/lib/states/webhook.js +10 -6
- package/lib/team/manager.js +124 -7
- package/lib/util/blocklet.js +1223 -246
- package/lib/util/chain.js +1 -1
- package/lib/util/default-node-config.js +5 -23
- package/lib/util/disk-monitor.js +13 -10
- package/lib/util/domain-status.js +84 -15
- package/lib/util/get-accessible-external-node-ip.js +2 -2
- package/lib/util/get-domain-for-blocklet.js +13 -0
- package/lib/util/get-meta-from-url.js +33 -0
- package/lib/util/index.js +207 -272
- package/lib/util/ip.js +6 -0
- package/lib/util/maintain.js +233 -0
- package/lib/util/public-to-store.js +85 -0
- package/lib/util/ready.js +1 -1
- package/lib/util/requirement.js +28 -9
- package/lib/util/reset-node.js +22 -7
- package/lib/util/router.js +13 -0
- package/lib/util/rpc.js +16 -0
- package/lib/util/store.js +179 -0
- package/lib/util/sysinfo.js +44 -0
- package/lib/util/ua.js +54 -0
- package/lib/validators/blocklet-extra.js +24 -0
- package/lib/validators/node.js +25 -12
- package/lib/validators/permission.js +16 -1
- package/lib/validators/role.js +17 -3
- package/lib/validators/router.js +40 -20
- package/lib/validators/trusted-passport.js +1 -0
- package/lib/validators/util.js +22 -5
- package/lib/webhook/index.js +45 -35
- package/lib/webhook/sender/index.js +5 -0
- package/lib/webhook/sender/slack/index.js +1 -1
- package/lib/webhook/sender/wallet/index.js +48 -0
- package/package.json +54 -36
- package/lib/blocklet/registry.js +0 -205
- package/lib/states/https-cert.js +0 -67
- package/lib/util/get-ip-dns-domain-for-blocklet.js +0 -19
- package/lib/util/service.js +0 -66
- package/lib/util/upgrade.js +0 -178
- /package/lib/{queue.js → util/queue.js} +0 -0
|
@@ -2,55 +2,73 @@
|
|
|
2
2
|
/* eslint-disable no-await-in-loop */
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const flat = require('flat');
|
|
5
6
|
const get = require('lodash/get');
|
|
7
|
+
const merge = require('lodash/merge');
|
|
8
|
+
const pick = require('lodash/pick');
|
|
6
9
|
const cloneDeep = require('lodash/cloneDeep');
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const { verifyPresentation } = require('@arcblock/vc');
|
|
15
|
-
const { toBase58 } = require('@ocap/util');
|
|
16
|
-
const { fromSecretKey } = require('@ocap/wallet');
|
|
10
|
+
const { isNFTExpired, getNftExpirationDate } = require('@abtnode/util/lib/nft');
|
|
11
|
+
const didDocument = require('@abtnode/util/lib/did-document');
|
|
12
|
+
const { sign } = require('@arcblock/jwt');
|
|
13
|
+
const { toSvg: createDidLogo } =
|
|
14
|
+
process.env.NODE_ENV !== 'test' ? require('@arcblock/did-motif') : require('@arcblock/did-motif/dist/did-motif.cjs');
|
|
15
|
+
const getBlockletInfo = require('@blocklet/meta/lib/info');
|
|
16
|
+
const sleep = require('@abtnode/util/lib/sleep');
|
|
17
17
|
|
|
18
18
|
const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager');
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
const {
|
|
20
|
+
WHO_CAN_ACCESS,
|
|
21
|
+
WHO_CAN_ACCESS_PREFIX_ROLES,
|
|
22
|
+
BLOCKLET_INSTALL_TYPE,
|
|
23
|
+
NODE_MODES,
|
|
24
|
+
APP_STRUCT_VERSION,
|
|
25
|
+
} = require('@abtnode/constant');
|
|
24
26
|
|
|
25
27
|
const getBlockletEngine = require('@blocklet/meta/lib/engine');
|
|
26
|
-
const {
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
const {
|
|
29
|
+
isDeletableBlocklet,
|
|
30
|
+
getAppMissingConfigs,
|
|
31
|
+
hasRunnableComponent,
|
|
32
|
+
forEachBlockletSync,
|
|
33
|
+
forEachChildSync,
|
|
34
|
+
forEachBlocklet,
|
|
35
|
+
getComponentId,
|
|
36
|
+
isPreferenceKey,
|
|
37
|
+
getRolesFromAuthConfig,
|
|
38
|
+
} = require('@blocklet/meta/lib/util');
|
|
39
|
+
const getComponentProcessId = require('@blocklet/meta/lib/get-component-process-id');
|
|
40
|
+
const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
|
|
41
|
+
const { titleSchema, updateMountPointSchema, environmentNameSchema } = require('@blocklet/meta/lib/schema');
|
|
42
|
+
const Lock = require('@abtnode/util/lib/lock');
|
|
29
43
|
|
|
30
44
|
const {
|
|
31
45
|
BlockletStatus,
|
|
32
46
|
BlockletSource,
|
|
33
47
|
BlockletEvents,
|
|
34
|
-
BLOCKLET_BUNDLE_FOLDER,
|
|
35
48
|
BLOCKLET_MODES,
|
|
36
49
|
BlockletGroup,
|
|
37
50
|
fromBlockletStatus,
|
|
38
51
|
fromBlockletSource,
|
|
39
|
-
|
|
52
|
+
BLOCKLET_DEFAULT_PORT_NAME,
|
|
53
|
+
BLOCKLET_INTERFACE_TYPE_WEB,
|
|
54
|
+
BLOCKLET_INTERFACE_PUBLIC,
|
|
55
|
+
BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
56
|
+
BLOCKLET_INTERFACE_PROTOCOL_HTTP,
|
|
57
|
+
BLOCKLET_DEFAULT_PATH_REWRITE,
|
|
58
|
+
BLOCKLET_DEFAULT_VERSION,
|
|
59
|
+
BLOCKLET_LATEST_SPEC_VERSION,
|
|
60
|
+
BLOCKLET_META_FILE,
|
|
61
|
+
BLOCKLET_CONFIGURABLE_KEY,
|
|
62
|
+
} = require('@blocklet/constant');
|
|
40
63
|
const util = require('../../util');
|
|
41
64
|
const {
|
|
42
65
|
refresh: refreshAccessibleExternalNodeIp,
|
|
43
66
|
getFromCache: getAccessibleExternalNodeIp,
|
|
44
67
|
} = require('../../util/get-accessible-external-node-ip');
|
|
45
68
|
const {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
fillBlockletConfigs,
|
|
50
|
-
ensureBlockletExpanded,
|
|
51
|
-
getRootSystemEnvironments,
|
|
52
|
-
getSystemEnvironments,
|
|
53
|
-
getOverwrittenEnvironments,
|
|
69
|
+
getAppSystemEnvironments,
|
|
70
|
+
getComponentSystemEnvironments,
|
|
71
|
+
getAppOverwrittenEnvironments,
|
|
54
72
|
getHealthyCheckTimeout,
|
|
55
73
|
startBlockletProcess,
|
|
56
74
|
stopBlockletProcess,
|
|
@@ -59,39 +77,59 @@ const {
|
|
|
59
77
|
getBlockletStatusFromProcess,
|
|
60
78
|
checkBlockletProcessHealthy,
|
|
61
79
|
validateBlocklet,
|
|
62
|
-
getChildrenMeta,
|
|
63
80
|
statusMap,
|
|
64
|
-
expandTarball,
|
|
65
|
-
verifyIntegrity,
|
|
66
81
|
pruneBlockletBundle,
|
|
67
82
|
getDiskInfo,
|
|
68
|
-
getRuntimeInfo,
|
|
69
|
-
mergeMeta,
|
|
70
|
-
getUpdateMetaList,
|
|
71
83
|
getRuntimeEnvironments,
|
|
84
|
+
getTypeFromInstallParams,
|
|
85
|
+
parseComponents,
|
|
86
|
+
filterDuplicateComponents,
|
|
87
|
+
getBundleDir,
|
|
88
|
+
getBlocklet,
|
|
89
|
+
ensureEnvDefault,
|
|
90
|
+
getConfigFromPreferences,
|
|
91
|
+
consumeServerlessNFT,
|
|
92
|
+
validateAppConfig,
|
|
93
|
+
checkDuplicateMountPoint,
|
|
94
|
+
validateStore,
|
|
95
|
+
isRotatingAppSk,
|
|
96
|
+
isRotatingAppDid,
|
|
97
|
+
checkVersionCompatibility,
|
|
98
|
+
getBlockletKnownAs,
|
|
72
99
|
} = require('../../util/blocklet');
|
|
73
100
|
const states = require('../../states');
|
|
74
|
-
const BlockletRegistry = require('../registry');
|
|
75
101
|
const BaseBlockletManager = require('./base');
|
|
76
102
|
const { get: getEngine } = require('./engine');
|
|
77
103
|
const blockletPm2Events = require('./pm2-events');
|
|
78
|
-
const { getFactoryState } = require('../../util/chain');
|
|
79
104
|
const runMigrationScripts = require('../migration');
|
|
80
105
|
const hooks = require('../hooks');
|
|
106
|
+
const { getDidDomainForBlocklet } = require('../../util/get-domain-for-blocklet');
|
|
107
|
+
const handleInstanceInStore = require('../../util/public-to-store');
|
|
108
|
+
const { BlockletRuntimeMonitor } = require('../../monitor/blocklet-runtime-monitor');
|
|
109
|
+
const getHistoryList = require('../../monitor/get-history-list');
|
|
110
|
+
const { SpacesBackup } = require('../storage/backup/spaces');
|
|
111
|
+
const { SpacesRestore } = require('../storage/restore/spaces');
|
|
112
|
+
const { installApplicationFromGeneral } = require('./helper/install-application-from-general');
|
|
113
|
+
const { installApplicationFromDev } = require('./helper/install-application-from-dev');
|
|
114
|
+
const { installApplicationFromBackup } = require('./helper/install-application-from-backup');
|
|
115
|
+
const { installComponentFromDev } = require('./helper/install-component-from-dev');
|
|
116
|
+
const { installComponentFromUrl } = require('./helper/install-component-from-url');
|
|
117
|
+
const { installComponentFromUpload, diff } = require('./helper/install-component-from-upload');
|
|
118
|
+
const UpgradeComponents = require('./helper/upgrade-components');
|
|
119
|
+
const BlockletDownloader = require('../downloader/blocklet-downloader');
|
|
120
|
+
const RollbackCache = require('./helper/rollback-cache');
|
|
121
|
+
const { migrateApplicationToStructV2 } = require('./helper/migrate-application-to-struct-v2');
|
|
81
122
|
|
|
82
123
|
const {
|
|
83
124
|
isInProgress,
|
|
84
125
|
isBeforeInstalled,
|
|
85
|
-
getBlockletInterfaces,
|
|
86
126
|
formatEnvironments,
|
|
87
127
|
shouldUpdateBlockletStatus,
|
|
88
128
|
getBlockletMeta,
|
|
89
|
-
|
|
129
|
+
validateOwner,
|
|
90
130
|
} = util;
|
|
91
131
|
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
const asyncFs = fs.promises;
|
|
132
|
+
const statusLock = new Lock('blocklet-status-lock');
|
|
95
133
|
|
|
96
134
|
const pm2StatusMap = {
|
|
97
135
|
online: BlockletStatus.running,
|
|
@@ -105,42 +143,64 @@ const pm2StatusMap = {
|
|
|
105
143
|
*/
|
|
106
144
|
const getBlockletEngineNameByPlatform = (blockletMeta) => getBlockletEngine(blockletMeta).interpreter;
|
|
107
145
|
|
|
146
|
+
const getSkippedProcessIds = ({ newBlocklet, oldBlocklet, context = {} }) => {
|
|
147
|
+
const { forceStartProcessIds = [] } = context;
|
|
148
|
+
const idMap = {};
|
|
149
|
+
const res = [];
|
|
150
|
+
|
|
151
|
+
forEachBlockletSync(oldBlocklet, (b, { ancestors }) => {
|
|
152
|
+
if (b.meta.dist?.integrity) {
|
|
153
|
+
idMap[getComponentProcessId(b, ancestors)] = b.meta.dist?.integrity;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
forEachBlockletSync(newBlocklet, (b, { ancestors }) => {
|
|
158
|
+
const id = getComponentProcessId(b, ancestors);
|
|
159
|
+
if (forceStartProcessIds.includes(id)) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!b.meta.dist?.integrity || b.meta.dist.integrity === idMap[id]) {
|
|
164
|
+
res.push(id);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return res;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// 10s 上报统计一次
|
|
172
|
+
const MONITOR_RECORD_INTERVAL_SEC = 10;
|
|
173
|
+
|
|
174
|
+
// 保存当天数据, 每天上报 8640 次
|
|
175
|
+
const MONITOR_HISTORY_LENGTH = 86400 / MONITOR_RECORD_INTERVAL_SEC;
|
|
176
|
+
|
|
108
177
|
class BlockletManager extends BaseBlockletManager {
|
|
109
178
|
/**
|
|
110
179
|
* @param {*} dataDirs generate by ../../util:getDataDirs
|
|
111
180
|
*/
|
|
112
|
-
constructor({ dataDirs,
|
|
181
|
+
constructor({ dataDirs, startQueue, installQueue, backupQueue, daemon = false, teamManager }) {
|
|
113
182
|
super();
|
|
114
183
|
|
|
115
184
|
this.dataDirs = dataDirs;
|
|
116
185
|
this.installDir = dataDirs.blocklets;
|
|
117
|
-
this.state = states.blocklet;
|
|
118
|
-
this.node = states.node;
|
|
119
186
|
this.startQueue = startQueue;
|
|
120
187
|
this.installQueue = installQueue;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
*/
|
|
124
|
-
this.downloadCtrls = {};
|
|
125
|
-
/**
|
|
126
|
-
* { [download-did-version]: Lock }
|
|
127
|
-
*/
|
|
128
|
-
this.downloadLocks = {};
|
|
129
|
-
this.registry = registry;
|
|
130
|
-
this.notification = states.notification;
|
|
131
|
-
this.session = states.session;
|
|
132
|
-
this.extras = states.blockletExtras;
|
|
133
|
-
this.cache = states.cache;
|
|
188
|
+
this.backupQueue = backupQueue;
|
|
189
|
+
this.teamManager = teamManager;
|
|
134
190
|
|
|
135
191
|
// cached installed blocklets for performance
|
|
136
192
|
this.cachedBlocklets = null;
|
|
137
193
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
194
|
+
this.runtimeMonitor = new BlockletRuntimeMonitor({ historyLength: MONITOR_HISTORY_LENGTH, states });
|
|
195
|
+
|
|
196
|
+
this.blockletDownloader = new BlockletDownloader({
|
|
197
|
+
installDir: this.installDir,
|
|
198
|
+
downloadDir: this.dataDirs.tmp,
|
|
199
|
+
cache: states.cache,
|
|
142
200
|
});
|
|
143
201
|
|
|
202
|
+
this._rollbackCache = new RollbackCache({ dir: this.dataDirs.tmp });
|
|
203
|
+
|
|
144
204
|
if (daemon) {
|
|
145
205
|
blockletPm2Events.on('online', (data) => this._syncPm2Status('online', data.blockletDid));
|
|
146
206
|
blockletPm2Events.on('stop', (data) => this._syncPm2Status('stop', data.blockletDid));
|
|
@@ -148,126 +208,278 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
148
208
|
}
|
|
149
209
|
|
|
150
210
|
// ============================================================================================
|
|
151
|
-
// Public API
|
|
211
|
+
// Public API for Installing/Upgrading Application or Components
|
|
152
212
|
// ============================================================================================
|
|
153
213
|
|
|
154
214
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* @param {
|
|
215
|
+
*
|
|
216
|
+
*
|
|
217
|
+
* @param {{
|
|
218
|
+
* url: string;
|
|
219
|
+
* did: string;
|
|
220
|
+
* title: string;
|
|
221
|
+
* description: string;
|
|
222
|
+
* storeUrl: string;
|
|
223
|
+
* appSk: string;
|
|
224
|
+
* sync: boolean = false; // download synchronously, not use queue
|
|
225
|
+
* delay: number; // push download task to queue after a delay
|
|
226
|
+
* downloadTokenList: Array<{did: string, token: string}>;
|
|
227
|
+
* startImmediately: boolean;
|
|
228
|
+
* controller: Controller
|
|
229
|
+
* type: BLOCKLET_INSTALL_TYPE
|
|
230
|
+
* }} params
|
|
231
|
+
* @param {{
|
|
232
|
+
* [key: string]: any
|
|
233
|
+
* }} context
|
|
234
|
+
* @return {*}
|
|
235
|
+
* @memberof BlockletManager
|
|
158
236
|
*/
|
|
159
|
-
async install(params, context) {
|
|
237
|
+
async install(params, context = {}) {
|
|
160
238
|
logger.debug('install blocklet', { params, context });
|
|
161
|
-
|
|
162
|
-
|
|
239
|
+
|
|
240
|
+
const type = getTypeFromInstallParams(params);
|
|
241
|
+
|
|
242
|
+
const { appSk } = params;
|
|
243
|
+
if (!appSk) {
|
|
244
|
+
throw new Error('appSk is required');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!params.controller && context?.user?.controller) {
|
|
248
|
+
params.controller = context.user.controller;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const info = await states.node.read();
|
|
252
|
+
|
|
253
|
+
// Note: if you added new header here, please change core/state/lib/blocklet/downloader/bundle-downloader.js to use that header
|
|
254
|
+
context.headers = Object.assign(context?.headers || {}, {
|
|
255
|
+
'x-server-did': info.did,
|
|
256
|
+
'x-server-public-key': info.pk,
|
|
257
|
+
'x-server-signature': sign(info.did, info.sk, {
|
|
258
|
+
exp: (Date.now() + 5 * 60 * 1000) / 1000,
|
|
259
|
+
}),
|
|
260
|
+
});
|
|
261
|
+
context.downloadTokenList = params.downloadTokenList || [];
|
|
262
|
+
|
|
263
|
+
if (typeof context.startImmediately === 'undefined') {
|
|
264
|
+
context.startImmediately = !!params.startImmediately;
|
|
163
265
|
}
|
|
164
266
|
|
|
165
|
-
if (
|
|
166
|
-
const {
|
|
167
|
-
return
|
|
267
|
+
if (type === BLOCKLET_INSTALL_TYPE.RESTORE) {
|
|
268
|
+
const { url } = params;
|
|
269
|
+
return installApplicationFromBackup({ url, appSk, context, manager: this, states });
|
|
168
270
|
}
|
|
169
271
|
|
|
170
|
-
if (
|
|
171
|
-
return
|
|
272
|
+
if ([BLOCKLET_INSTALL_TYPE.URL, BLOCKLET_INSTALL_TYPE.STORE, BLOCKLET_INSTALL_TYPE.CREATE].includes(type)) {
|
|
273
|
+
return installApplicationFromGeneral({ ...params, type, context, manager: this, states });
|
|
172
274
|
}
|
|
173
275
|
|
|
174
|
-
|
|
276
|
+
// should not be here
|
|
277
|
+
throw new Error(`install from ${type} is not supported`);
|
|
175
278
|
}
|
|
176
279
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
280
|
+
/**
|
|
281
|
+
* @param {String} rootDid
|
|
282
|
+
* @param {String} mountPoint
|
|
283
|
+
*
|
|
284
|
+
* installFromUrl
|
|
285
|
+
* @param {String} url
|
|
286
|
+
*
|
|
287
|
+
* InstallFromUpload
|
|
288
|
+
* @param {Object} file
|
|
289
|
+
* @param {String} did for diff upload or custom component did
|
|
290
|
+
* @param {String} diffVersion for diff upload
|
|
291
|
+
* @param {Array} deleteSet for diff upload
|
|
292
|
+
*
|
|
293
|
+
* Custom info
|
|
294
|
+
* @param {String} title custom component title
|
|
295
|
+
* @param {String} name custom component name
|
|
296
|
+
*
|
|
297
|
+
* @param {ConfigEntry} configs pre configs
|
|
298
|
+
*/
|
|
299
|
+
async installComponent(
|
|
300
|
+
{
|
|
301
|
+
rootDid,
|
|
302
|
+
mountPoint: tmpMountPoint,
|
|
303
|
+
url,
|
|
304
|
+
file,
|
|
305
|
+
did,
|
|
306
|
+
diffVersion,
|
|
307
|
+
deleteSet,
|
|
308
|
+
title,
|
|
309
|
+
name,
|
|
310
|
+
configs,
|
|
311
|
+
sync,
|
|
312
|
+
downloadTokenList,
|
|
313
|
+
},
|
|
314
|
+
context = {}
|
|
315
|
+
) {
|
|
316
|
+
const mountPoint = await updateMountPointSchema.validateAsync(tmpMountPoint);
|
|
317
|
+
logger.debug('start install component', { rootDid, mountPoint, url });
|
|
181
318
|
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
319
|
+
if (file) {
|
|
320
|
+
// TODO: 如何触发这种场景?
|
|
321
|
+
const info = await states.node.read();
|
|
322
|
+
if (info.mode === NODE_MODES.SERVERLESS) {
|
|
323
|
+
throw new Error("Can't install component in serverless-mode server via upload");
|
|
324
|
+
}
|
|
185
325
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
326
|
+
return installComponentFromUpload({
|
|
327
|
+
rootDid,
|
|
328
|
+
mountPoint,
|
|
329
|
+
file,
|
|
330
|
+
did,
|
|
331
|
+
diffVersion,
|
|
332
|
+
deleteSet,
|
|
333
|
+
context,
|
|
334
|
+
states,
|
|
335
|
+
manager: this,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (url) {
|
|
340
|
+
const info = await states.node.read();
|
|
341
|
+
if (info.mode === NODE_MODES.SERVERLESS) {
|
|
342
|
+
validateStore(info, url);
|
|
194
343
|
}
|
|
344
|
+
|
|
345
|
+
return installComponentFromUrl({
|
|
346
|
+
rootDid,
|
|
347
|
+
mountPoint,
|
|
348
|
+
url,
|
|
349
|
+
context,
|
|
350
|
+
title,
|
|
351
|
+
did,
|
|
352
|
+
name,
|
|
353
|
+
configs,
|
|
354
|
+
sync,
|
|
355
|
+
downloadTokenList,
|
|
356
|
+
states,
|
|
357
|
+
manager: this,
|
|
358
|
+
});
|
|
195
359
|
}
|
|
196
360
|
|
|
197
|
-
|
|
361
|
+
// should not be here
|
|
362
|
+
throw new Error('Unknown source');
|
|
363
|
+
}
|
|
198
364
|
|
|
199
|
-
|
|
365
|
+
async diff({ did, hashFiles, rootDid }) {
|
|
366
|
+
return diff({ did, hashFiles, rootDid, states, manager: this });
|
|
200
367
|
}
|
|
201
368
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
369
|
+
/**
|
|
370
|
+
* After the dev function finished, the caller should send a BlockletEvents.installed event to the daemon
|
|
371
|
+
* @returns {Object} blocklet
|
|
372
|
+
*/
|
|
373
|
+
async dev(folder, { rootDid, mountPoint, defaultStoreUrl } = {}) {
|
|
374
|
+
logger.info('dev component', { folder, rootDid, mountPoint });
|
|
205
375
|
|
|
206
|
-
|
|
207
|
-
|
|
376
|
+
const meta = getBlockletMeta(folder, { defaultStoreUrl });
|
|
377
|
+
if (meta.group !== 'static' && (!meta.scripts || !meta.scripts.dev)) {
|
|
378
|
+
throw new Error('Incorrect blocklet.yml: missing `scripts.dev` field');
|
|
379
|
+
}
|
|
208
380
|
|
|
209
|
-
if (!
|
|
210
|
-
|
|
381
|
+
if (!rootDid) {
|
|
382
|
+
return installApplicationFromDev({ folder, meta, manager: this, states });
|
|
211
383
|
}
|
|
212
384
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
385
|
+
return installComponentFromDev({ folder, meta, rootDid, mountPoint, manager: this, states });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async checkComponentsForUpdates({ did }) {
|
|
389
|
+
return UpgradeComponents.check({ did, states });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async upgradeComponents({ updateId, selectedComponents: selectedComponentDids }, context = {}) {
|
|
393
|
+
return UpgradeComponents.upgrade({ updateId, selectedComponentDids, context, states, manager: this });
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async migrateApplicationToStructV2({ did, appSk, context = {} }) {
|
|
397
|
+
return migrateApplicationToStructV2({ did, appSk, context, manager: this, states });
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ============================================================================================
|
|
401
|
+
// Public API for GQL or internal
|
|
402
|
+
// ============================================================================================
|
|
217
403
|
|
|
218
|
-
|
|
404
|
+
async getBlockletForLauncher({ did }) {
|
|
405
|
+
const blocklet = await states.blocklet.getBlocklet(did);
|
|
406
|
+
const isRunning = blocklet ? blocklet.status === BlockletStatus.running : false;
|
|
407
|
+
return { did, isInstalled: !!blocklet, isRunning };
|
|
219
408
|
}
|
|
220
409
|
|
|
221
|
-
async start({ did, checkHealthImmediately = false,
|
|
410
|
+
async start({ did, throwOnError, checkHealthImmediately = false, e2eMode = false }, context) {
|
|
222
411
|
logger.info('start blocklet', { did });
|
|
223
|
-
|
|
412
|
+
// should check blocklet integrity
|
|
413
|
+
const blocklet = await this.ensureBlocklet(did, { e2eMode });
|
|
224
414
|
|
|
225
415
|
try {
|
|
416
|
+
// blocklet may be manually stopped durning starting
|
|
417
|
+
// so error message would not be sent if blocklet is stopped
|
|
418
|
+
// so we need update status first
|
|
419
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.starting);
|
|
420
|
+
blocklet.status = BlockletStatus.starting;
|
|
421
|
+
|
|
422
|
+
// validate requirement and engine
|
|
423
|
+
await validateBlocklet(blocklet);
|
|
424
|
+
|
|
425
|
+
if (!hasRunnableComponent(blocklet)) {
|
|
426
|
+
throw new Error('No runnable component found');
|
|
427
|
+
}
|
|
428
|
+
|
|
226
429
|
// check required config
|
|
227
|
-
const missingProps =
|
|
430
|
+
const missingProps = getAppMissingConfigs(blocklet);
|
|
228
431
|
if (missingProps.length) {
|
|
229
432
|
throw new Error(
|
|
230
433
|
`Missing required configuration to start the blocklet: ${missingProps.map((x) => x.key).join(',')}`
|
|
231
434
|
);
|
|
232
435
|
}
|
|
233
436
|
|
|
234
|
-
// update status
|
|
235
|
-
await this.state.setBlockletStatus(did, BlockletStatus.starting);
|
|
236
|
-
blocklet.status = BlockletStatus.starting;
|
|
237
437
|
this.emit(BlockletEvents.statusChange, blocklet);
|
|
238
438
|
|
|
239
439
|
if (blocklet.mode === BLOCKLET_MODES.DEVELOPMENT) {
|
|
240
440
|
const { logsDir } = blocklet.env;
|
|
241
|
-
|
|
242
|
-
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
fs.removeSync(logsDir);
|
|
444
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
445
|
+
} catch {
|
|
446
|
+
// Windows && Node.js 18.x 下会发生删除错误(ENOTEMPTY)
|
|
447
|
+
// 但是这个错误并不影响后续逻辑,所以这里对这个错误做了 catch
|
|
448
|
+
}
|
|
243
449
|
}
|
|
244
450
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
hooks.preStart(b, {
|
|
451
|
+
const getHookFn =
|
|
452
|
+
(hookName) =>
|
|
453
|
+
(b, { env }) =>
|
|
454
|
+
hooks[hookName](b, {
|
|
250
455
|
appDir: b.env.appDir,
|
|
251
456
|
hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
|
|
252
|
-
env
|
|
457
|
+
env,
|
|
253
458
|
did, // root blocklet did,
|
|
254
|
-
|
|
255
|
-
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// start process
|
|
462
|
+
const nodeEnvironments = await states.node.getEnvironments();
|
|
463
|
+
await startBlockletProcess(blocklet, {
|
|
464
|
+
...context,
|
|
465
|
+
preStart: getHookFn('preStart'),
|
|
466
|
+
postStart: getHookFn('postStart'),
|
|
256
467
|
nodeEnvironments,
|
|
257
|
-
nodeInfo: await
|
|
468
|
+
nodeInfo: await states.node.read(),
|
|
469
|
+
e2eMode,
|
|
258
470
|
});
|
|
259
471
|
|
|
260
472
|
// check blocklet healthy
|
|
261
473
|
const { startTimeout, minConsecutiveTime } = getHealthyCheckTimeout(blocklet, { checkHealthImmediately });
|
|
262
474
|
const params = {
|
|
263
|
-
|
|
475
|
+
did,
|
|
264
476
|
context,
|
|
265
477
|
minConsecutiveTime,
|
|
266
478
|
timeout: startTimeout,
|
|
267
479
|
};
|
|
268
480
|
|
|
269
481
|
if (checkHealthImmediately) {
|
|
270
|
-
await this.
|
|
482
|
+
await this._onCheckIfStarted(params, { throwOnError });
|
|
271
483
|
} else {
|
|
272
484
|
this.startQueue.push({
|
|
273
485
|
entity: 'blocklet',
|
|
@@ -279,10 +491,16 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
279
491
|
|
|
280
492
|
return blocklet;
|
|
281
493
|
} catch (err) {
|
|
494
|
+
const status = await states.blocklet.getBlockletStatus(did);
|
|
495
|
+
if ([BlockletStatus.stopping, BlockletStatus.stopped].includes(status)) {
|
|
496
|
+
logger.info('Failed to start blocklet maybe due to manually stopped');
|
|
497
|
+
return states.blocklet.getBlocklet(did);
|
|
498
|
+
}
|
|
499
|
+
|
|
282
500
|
const error = Array.isArray(err) ? err[0] : err;
|
|
283
501
|
logger.error('Failed to start blocklet', { error, did, name: blocklet.meta.name });
|
|
284
502
|
const description = `Start blocklet ${blocklet.meta.name} failed with error: ${error.message}`;
|
|
285
|
-
this.
|
|
503
|
+
this._createNotification(did, {
|
|
286
504
|
title: 'Start Blocklet Failed',
|
|
287
505
|
description,
|
|
288
506
|
entityType: 'blocklet',
|
|
@@ -291,8 +509,8 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
291
509
|
});
|
|
292
510
|
|
|
293
511
|
await this.deleteProcess({ did });
|
|
294
|
-
const res = await
|
|
295
|
-
this.emit(BlockletEvents.startFailed, res);
|
|
512
|
+
const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.error);
|
|
513
|
+
this.emit(BlockletEvents.startFailed, { ...res, error: { message: error.message } });
|
|
296
514
|
|
|
297
515
|
if (throwOnError) {
|
|
298
516
|
throw new Error(description);
|
|
@@ -302,60 +520,119 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
302
520
|
}
|
|
303
521
|
}
|
|
304
522
|
|
|
305
|
-
async stop({ did, updateStatus = true }, context) {
|
|
523
|
+
async stop({ did, updateStatus = true, silent = false }, context) {
|
|
306
524
|
logger.info('stop blocklet', { did });
|
|
307
525
|
|
|
308
|
-
const blocklet = await this.
|
|
309
|
-
const {
|
|
526
|
+
const blocklet = await this.getBlocklet(did);
|
|
527
|
+
const { processId } = blocklet.env;
|
|
310
528
|
|
|
311
529
|
if (updateStatus) {
|
|
312
|
-
await
|
|
530
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.stopping);
|
|
313
531
|
blocklet.status = BlockletStatus.stopping;
|
|
314
532
|
this.emit(BlockletEvents.statusChange, blocklet);
|
|
315
533
|
}
|
|
316
534
|
|
|
317
|
-
const nodeEnvironments = await
|
|
535
|
+
const nodeEnvironments = await states.node.getEnvironments();
|
|
318
536
|
|
|
319
537
|
await stopBlockletProcess(blocklet, {
|
|
320
|
-
preStop: (b) =>
|
|
321
|
-
hooks.preStop({
|
|
538
|
+
preStop: (b, { ancestors }) =>
|
|
539
|
+
hooks.preStop(b.env.processId, {
|
|
322
540
|
appDir: b.env.appDir,
|
|
323
541
|
hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
|
|
324
|
-
env: getRuntimeEnvironments(b, nodeEnvironments),
|
|
542
|
+
env: getRuntimeEnvironments(b, nodeEnvironments, ancestors),
|
|
325
543
|
did, // root blocklet did
|
|
326
|
-
notification:
|
|
544
|
+
notification: states.notification,
|
|
327
545
|
context,
|
|
328
546
|
exitOnError: false,
|
|
329
|
-
|
|
547
|
+
silent,
|
|
330
548
|
}),
|
|
331
549
|
});
|
|
332
550
|
|
|
333
|
-
logger.info('blocklet stopped successfully', {
|
|
551
|
+
logger.info('blocklet stopped successfully', { processId, did });
|
|
334
552
|
|
|
335
553
|
if (updateStatus) {
|
|
336
554
|
const res = await this.status(did, { forceSync: true });
|
|
555
|
+
// send notification to websocket channel
|
|
337
556
|
this.emit(BlockletEvents.statusChange, res);
|
|
557
|
+
|
|
558
|
+
// send notification to wallet
|
|
559
|
+
this.emit(BlockletEvents.stopped, res);
|
|
560
|
+
|
|
338
561
|
return res;
|
|
339
562
|
}
|
|
340
563
|
|
|
341
564
|
return blocklet;
|
|
342
565
|
}
|
|
343
566
|
|
|
567
|
+
/**
|
|
568
|
+
* FIXME: @wangshijun create audit log for this
|
|
569
|
+
* @param {import('@abtnode/client').RequestBackupToSpacesInput} input
|
|
570
|
+
* @memberof BlockletManager
|
|
571
|
+
*/
|
|
572
|
+
// eslint-disable-next-line no-unused-vars
|
|
573
|
+
async backupToSpaces({ appDid }, context) {
|
|
574
|
+
const blocklet = await states.blocklet.getBlocklet(appDid);
|
|
575
|
+
if (blocklet.structVersion !== APP_STRUCT_VERSION) {
|
|
576
|
+
throw new Error('Only new version app can be backup to spaces, please migrate this app first');
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const userDid = context.user.did;
|
|
580
|
+
const { referrer } = context;
|
|
581
|
+
|
|
582
|
+
const spacesBackup = new SpacesBackup({ appDid, event: this, userDid, referrer });
|
|
583
|
+
this.emit(BlockletEvents.backupProgress, { appDid, message: 'Start backup...', progress: 10, completed: false });
|
|
584
|
+
await spacesBackup.backup();
|
|
585
|
+
this.emit(BlockletEvents.backupProgress, { appDid, completed: true, progress: 100 });
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* FIXME: @linchen support cancel
|
|
590
|
+
* FIXME: @wangshijun create audit log for this
|
|
591
|
+
* @param {import('@abtnode/client').RequestRestoreFromSpacesInput} input
|
|
592
|
+
* @memberof BlockletManager
|
|
593
|
+
*/
|
|
594
|
+
// eslint-disable-next-line no-unused-vars
|
|
595
|
+
async restoreFromSpaces(input, context) {
|
|
596
|
+
this.emit(BlockletEvents.restoreProgress, { appDid: input.appDid, message: 'Start restore...', completed: false });
|
|
597
|
+
|
|
598
|
+
const userDid = context.user.did;
|
|
599
|
+
|
|
600
|
+
const spacesRestore = new SpacesRestore({ ...input, event: this, userDid, referrer: context.referrer });
|
|
601
|
+
const params = await spacesRestore.restore();
|
|
602
|
+
|
|
603
|
+
this.emit(BlockletEvents.restoreProgress, { appDid: input.appDid, message: 'Installing blocklet...' });
|
|
604
|
+
await installApplicationFromBackup({
|
|
605
|
+
url: `file://${spacesRestore.restoreDir}`,
|
|
606
|
+
moveDir: true,
|
|
607
|
+
...merge(...params),
|
|
608
|
+
manager: this,
|
|
609
|
+
states,
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
this.emit(BlockletEvents.restoreProgress, { appDid: input.appDid, completed: true });
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
*
|
|
617
|
+
* @param {import('@abtnode/client').RequestBlockletInput} param0
|
|
618
|
+
* @param {Record<string, any>} context
|
|
619
|
+
* @returns {import('@abtnode/client').BlockletState}
|
|
620
|
+
*/
|
|
344
621
|
async restart({ did }, context) {
|
|
345
622
|
logger.info('restart blocklet', { did });
|
|
346
623
|
|
|
347
|
-
await
|
|
348
|
-
const result = await
|
|
624
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.stopping);
|
|
625
|
+
const result = await states.blocklet.getBlocklet(did);
|
|
349
626
|
this.emit(BlockletEvents.statusChange, result);
|
|
350
627
|
|
|
351
628
|
const ticket = this.startQueue.push({ entity: 'blocklet', action: 'restart', id: did, did, context });
|
|
352
629
|
ticket.on('failed', async (err) => {
|
|
353
630
|
logger.error('failed to restart blocklet', { did, error: err });
|
|
354
631
|
|
|
355
|
-
const state = await
|
|
632
|
+
const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped);
|
|
356
633
|
this.emit(BlockletEvents.statusChange, state);
|
|
357
634
|
|
|
358
|
-
this.
|
|
635
|
+
this._createNotification(did, {
|
|
359
636
|
title: 'Blocklet Restart Failed',
|
|
360
637
|
description: `Blocklet ${did} restart failed with error: ${err.message || 'queue exception'}`,
|
|
361
638
|
entityType: 'blocklet',
|
|
@@ -369,109 +646,293 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
369
646
|
|
|
370
647
|
// eslint-disable-next-line no-unused-vars
|
|
371
648
|
async reload({ did }, context) {
|
|
372
|
-
const blocklet = await this.
|
|
649
|
+
const blocklet = await this.getBlocklet(did);
|
|
373
650
|
|
|
374
|
-
await
|
|
651
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.stopping);
|
|
375
652
|
await reloadBlockletProcess(blocklet);
|
|
376
|
-
await
|
|
653
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.running);
|
|
377
654
|
logger.info('blocklet reload successfully', { did });
|
|
378
655
|
|
|
379
656
|
const res = await this.status(did);
|
|
380
|
-
this.emit(BlockletEvents.
|
|
657
|
+
this.emit(BlockletEvents.statusChange, res);
|
|
381
658
|
return res;
|
|
382
659
|
}
|
|
383
660
|
|
|
384
661
|
async delete({ did, keepData, keepLogsDir, keepConfigs }, context) {
|
|
385
662
|
logger.info('delete blocklet', { did, keepData });
|
|
386
663
|
|
|
387
|
-
|
|
388
|
-
const blocklet = await this.ensureBlocklet(did);
|
|
664
|
+
const blocklet = await this.getBlocklet(did);
|
|
389
665
|
|
|
390
|
-
|
|
666
|
+
try {
|
|
667
|
+
if (isDeletableBlocklet(blocklet) === false) {
|
|
668
|
+
throw new Error('Blocklet is protected from accidental deletion');
|
|
669
|
+
}
|
|
391
670
|
|
|
671
|
+
const nodeEnvironments = await states.node.getEnvironments();
|
|
392
672
|
await deleteBlockletProcess(blocklet, {
|
|
393
|
-
preDelete: (b) =>
|
|
394
|
-
hooks.preUninstall({
|
|
673
|
+
preDelete: (b, { ancestors }) =>
|
|
674
|
+
hooks.preUninstall(b.env.processId, {
|
|
395
675
|
appDir: b.env.appDir,
|
|
396
676
|
hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
|
|
397
|
-
env: getRuntimeEnvironments(b, nodeEnvironments),
|
|
677
|
+
env: getRuntimeEnvironments(b, nodeEnvironments, ancestors),
|
|
398
678
|
did, // root blocklet did
|
|
399
|
-
notification:
|
|
679
|
+
notification: states.notification,
|
|
400
680
|
context,
|
|
401
681
|
exitOnError: false,
|
|
402
682
|
}),
|
|
403
683
|
});
|
|
404
684
|
|
|
405
|
-
|
|
406
|
-
|
|
685
|
+
const doc = await this._deleteBlocklet({ did, keepData, keepLogsDir, keepConfigs }, context);
|
|
686
|
+
this._createNotification(doc.meta.did, {
|
|
687
|
+
title: 'Blocklet Deleted',
|
|
688
|
+
description: `Blocklet ${doc.meta.name}@${doc.meta.version} is deleted.`,
|
|
689
|
+
entityType: 'blocklet',
|
|
690
|
+
entityId: doc.meta.did,
|
|
691
|
+
severity: 'success',
|
|
692
|
+
});
|
|
693
|
+
return doc;
|
|
694
|
+
} catch (error) {
|
|
407
695
|
// If we installed a corrupted blocklet accidentally, just cleanup the disk and state db
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
696
|
+
logger.error('blocklet delete failed, will delete again', { did, error });
|
|
697
|
+
const doc = await this._deleteBlocklet({ did, keepData, keepLogsDir, keepConfigs }, context);
|
|
698
|
+
|
|
699
|
+
this._createNotification(doc.meta.did, {
|
|
700
|
+
title: 'Blocklet Deleted',
|
|
701
|
+
description: `Blocklet ${doc.meta.name}@${doc.meta.version} is deleted.`,
|
|
702
|
+
entityType: 'blocklet',
|
|
703
|
+
entityId: doc.meta.did,
|
|
704
|
+
severity: 'success',
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
return doc;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
async reset({ did, childDid }, context = {}) {
|
|
712
|
+
logger.info('reset blocklet', { did, childDid });
|
|
713
|
+
|
|
714
|
+
const blocklet = await this.getBlocklet(did);
|
|
715
|
+
|
|
716
|
+
if (isInProgress(blocklet.status || blocklet.status === BlockletStatus.running)) {
|
|
717
|
+
throw new Error('Cannot reset when blocklet is in progress');
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
try {
|
|
721
|
+
await this.deleteProcess({ did }, context);
|
|
722
|
+
} catch {
|
|
723
|
+
// do nothing
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (!childDid) {
|
|
727
|
+
// Cleanup disk storage
|
|
728
|
+
const { cacheDir, logsDir, dataDir } = blocklet.env;
|
|
729
|
+
fs.removeSync(cacheDir);
|
|
730
|
+
fs.removeSync(dataDir);
|
|
731
|
+
fs.removeSync(logsDir);
|
|
732
|
+
|
|
733
|
+
// Reset config in db
|
|
734
|
+
await states.blockletExtras.remove({ did: blocklet.meta.did });
|
|
735
|
+
await this._setConfigsFromMeta(did);
|
|
736
|
+
await this._updateBlockletEnvironment(did);
|
|
737
|
+
await this.resetSiteByDid(did, context);
|
|
738
|
+
} else {
|
|
739
|
+
const child = blocklet.children.find((x) => x.meta.did === childDid);
|
|
740
|
+
|
|
741
|
+
if (!child) {
|
|
742
|
+
throw new Error('Child does not exist');
|
|
411
743
|
}
|
|
412
744
|
|
|
413
|
-
|
|
745
|
+
// Cleanup disk storage
|
|
746
|
+
const { cacheDir, logsDir, dataDir } = child.env;
|
|
747
|
+
fs.removeSync(cacheDir);
|
|
748
|
+
fs.removeSync(dataDir);
|
|
749
|
+
fs.removeSync(logsDir);
|
|
750
|
+
|
|
751
|
+
// Reset config in db
|
|
752
|
+
await states.blockletExtras.delConfigs([blocklet.meta.did, child.meta.did]);
|
|
753
|
+
await this._setConfigsFromMeta(blocklet.meta.did, child.meta.did);
|
|
754
|
+
await this._updateBlockletEnvironment(did);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
logger.info('blocklet reset', { did, childDid });
|
|
758
|
+
return blocklet;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
async deleteComponent({ did, rootDid, keepData, keepState }, context) {
|
|
762
|
+
logger.info('delete blocklet component', { did, rootDid, keepData });
|
|
763
|
+
|
|
764
|
+
const blocklet = await this.getBlocklet(rootDid);
|
|
765
|
+
|
|
766
|
+
const child = blocklet.children.find((x) => x.meta.did === did);
|
|
767
|
+
if (!child) {
|
|
768
|
+
throw new Error('Component does not exist');
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// delete state
|
|
772
|
+
const doc = await states.blocklet.getBlocklet(rootDid);
|
|
773
|
+
doc.children = doc.children.filter((x) => x.meta.did !== did);
|
|
774
|
+
const deletedChildren = await states.blockletExtras.getSettings(did, 'children', []);
|
|
775
|
+
if (keepData !== false && keepState !== false) {
|
|
776
|
+
deletedChildren.push({
|
|
777
|
+
meta: pick(child.meta, ['did', 'name', 'bundleDid', 'bundleName', 'version', 'title', 'description']),
|
|
778
|
+
mountPoint: child.mountPoint,
|
|
779
|
+
status: BlockletStatus.deleted,
|
|
780
|
+
deletedAt: new Date(),
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
await states.blocklet.updateBlocklet(rootDid, doc);
|
|
785
|
+
states.blockletExtras.setSettings(doc.meta.did, { children: deletedChildren });
|
|
786
|
+
|
|
787
|
+
// delete process
|
|
788
|
+
try {
|
|
789
|
+
const skippedProcessIds = [];
|
|
790
|
+
forEachBlockletSync(blocklet, (b) => {
|
|
791
|
+
if (!b.env.id.startsWith(child.env.id)) {
|
|
792
|
+
skippedProcessIds.push(b.env.processId);
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
await deleteBlockletProcess(blocklet, { skippedProcessIds });
|
|
796
|
+
logger.info('delete blocklet process for deleting component', { did, rootDid });
|
|
797
|
+
} catch (err) {
|
|
798
|
+
logger.error('delete blocklet process for deleting component', { did, rootDid, error: err });
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// delete storage
|
|
802
|
+
const childBlocklet = blocklet.children.find((x) => x.meta.did === did);
|
|
803
|
+
const { cacheDir, logsDir, dataDir } = childBlocklet.env;
|
|
804
|
+
fs.removeSync(cacheDir);
|
|
805
|
+
fs.removeSync(logsDir);
|
|
806
|
+
if (keepData === false) {
|
|
807
|
+
fs.removeSync(dataDir);
|
|
808
|
+
await states.blockletExtras.delConfigs([blocklet.meta.did, child.meta.did]);
|
|
414
809
|
}
|
|
810
|
+
|
|
811
|
+
const newBlocklet = await this.getBlocklet(rootDid);
|
|
812
|
+
|
|
813
|
+
await this._updateDependents(rootDid);
|
|
814
|
+
|
|
815
|
+
this.emit(BlockletEvents.upgraded, { blocklet: newBlocklet, context: { ...context, createAuditLog: false } }); // trigger router refresh
|
|
816
|
+
|
|
817
|
+
this._createNotification(newBlocklet.meta.did, {
|
|
818
|
+
title: 'Component Deleted',
|
|
819
|
+
description: `Component ${child.meta.name} of ${newBlocklet.meta.name} is successfully deleted.`,
|
|
820
|
+
entityType: 'blocklet',
|
|
821
|
+
entityId: newBlocklet.meta.did,
|
|
822
|
+
severity: 'success',
|
|
823
|
+
action: `/blocklets/${newBlocklet.meta.did}/components`,
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
return newBlocklet;
|
|
415
827
|
}
|
|
416
828
|
|
|
417
|
-
async cancelDownload({ did }
|
|
418
|
-
await preDownloadLock.acquire();
|
|
829
|
+
async cancelDownload({ did: inputDid }) {
|
|
419
830
|
try {
|
|
420
|
-
|
|
831
|
+
await statusLock.acquire();
|
|
832
|
+
const blocklet = await states.blocklet.getBlocklet(inputDid);
|
|
421
833
|
if (!blocklet) {
|
|
422
|
-
throw new Error(
|
|
834
|
+
throw new Error(`Can not cancel download for non-exist blocklet in database. did: ${inputDid}`);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const { name, did, version } = blocklet.meta;
|
|
838
|
+
|
|
839
|
+
if (![BlockletStatus.downloading, BlockletStatus.waiting].includes(blocklet.status)) {
|
|
840
|
+
throw new Error(`Can not cancel blocklet that status is ${fromBlockletStatus(blocklet.status)}`);
|
|
423
841
|
}
|
|
424
842
|
|
|
843
|
+
const job = await this.installQueue.get(did);
|
|
844
|
+
|
|
845
|
+
// cancel job
|
|
425
846
|
if (blocklet.status === BlockletStatus.downloading) {
|
|
426
|
-
|
|
847
|
+
try {
|
|
848
|
+
await this.blockletDownloader.cancelDownload(blocklet.meta.did);
|
|
849
|
+
} catch (error) {
|
|
850
|
+
logger.error('failed to exec blockletDownloader.download', { did: blocklet.meta.did, error });
|
|
851
|
+
}
|
|
427
852
|
} else if (blocklet.status === BlockletStatus.waiting) {
|
|
428
|
-
|
|
853
|
+
try {
|
|
854
|
+
await this.installQueue.cancel(blocklet.meta.did);
|
|
855
|
+
} catch (error) {
|
|
856
|
+
logger.error('failed to cancel waiting', { did: blocklet.meta.did, error });
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// rollback
|
|
861
|
+
if (job) {
|
|
862
|
+
const { postAction, oldBlocklet } = job;
|
|
863
|
+
await this._rollback(postAction, did, oldBlocklet);
|
|
429
864
|
} else {
|
|
430
|
-
|
|
865
|
+
const data = await this._rollbackCache.restore({ did });
|
|
866
|
+
if (data) {
|
|
867
|
+
const { action, oldBlocklet } = data;
|
|
868
|
+
await this._rollback(action, did, oldBlocklet);
|
|
869
|
+
await this._rollbackCache.remove({ did });
|
|
870
|
+
} else {
|
|
871
|
+
throw new Error(`Cannot find rollback data in queue or backup file of blocklet ${inputDid}`);
|
|
872
|
+
}
|
|
431
873
|
}
|
|
432
874
|
|
|
433
|
-
|
|
875
|
+
logger.info('cancel download blocklet', { did, name, version, status: fromBlockletStatus(blocklet.status) });
|
|
876
|
+
|
|
877
|
+
statusLock.release();
|
|
434
878
|
return blocklet;
|
|
435
879
|
} catch (error) {
|
|
436
|
-
|
|
880
|
+
try {
|
|
881
|
+
// fallback blocklet status to error
|
|
882
|
+
const blocklet = await states.blocklet.getBlocklet(inputDid);
|
|
883
|
+
if (blocklet) {
|
|
884
|
+
await states.blocklet.setBlockletStatus(blocklet.meta.did, BlockletStatus.error);
|
|
885
|
+
}
|
|
886
|
+
statusLock.release();
|
|
887
|
+
} catch (err) {
|
|
888
|
+
statusLock.release();
|
|
889
|
+
logger.error('Failed to fallback blocklet status to error on cancelDownload', { error });
|
|
890
|
+
}
|
|
891
|
+
|
|
437
892
|
throw error;
|
|
438
893
|
}
|
|
439
894
|
}
|
|
440
895
|
|
|
441
896
|
// eslint-disable-next-line no-unused-vars
|
|
442
897
|
async deleteProcess({ did }, context) {
|
|
443
|
-
const blocklet = await this.
|
|
898
|
+
const blocklet = await this.getBlocklet(did);
|
|
444
899
|
|
|
445
900
|
logger.info('delete blocklet process', { did });
|
|
446
901
|
|
|
447
|
-
await deleteBlockletProcess(blocklet);
|
|
902
|
+
await deleteBlockletProcess(blocklet, context);
|
|
448
903
|
|
|
449
|
-
const result = await
|
|
904
|
+
const result = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped);
|
|
450
905
|
logger.info('blocklet process deleted successfully', { did });
|
|
451
906
|
return result;
|
|
452
907
|
}
|
|
453
908
|
|
|
454
|
-
|
|
909
|
+
// Get blocklet by blockletDid or appDid
|
|
910
|
+
async detail({ did, attachConfig = true, attachRuntimeInfo = true }, context) {
|
|
455
911
|
if (!did) {
|
|
456
912
|
throw new Error('did should not be empty');
|
|
457
913
|
}
|
|
458
914
|
|
|
459
|
-
|
|
915
|
+
if (!attachConfig) {
|
|
916
|
+
return states.blocklet.getBlocklet(did);
|
|
917
|
+
}
|
|
460
918
|
|
|
461
919
|
if (!attachRuntimeInfo) {
|
|
462
|
-
|
|
920
|
+
try {
|
|
921
|
+
const blocklet = await this.getBlocklet(did, { throwOnNotExist: false });
|
|
922
|
+
return blocklet;
|
|
923
|
+
} catch (e) {
|
|
924
|
+
logger.error('get blocklet detail error', { error: e });
|
|
925
|
+
return states.blocklet.getBlocklet(did);
|
|
926
|
+
}
|
|
463
927
|
}
|
|
464
928
|
|
|
465
|
-
|
|
466
|
-
}
|
|
929
|
+
const nodeInfo = await states.node.read();
|
|
467
930
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (!includeRuntimeInfo) {
|
|
471
|
-
return blocklets;
|
|
472
|
-
}
|
|
931
|
+
return this._attachRuntimeInfo({ did, nodeInfo, diskInfo: true, context });
|
|
932
|
+
}
|
|
473
933
|
|
|
474
|
-
|
|
934
|
+
async attachBlockletListRuntimeInfo({ blocklets, useCache }, context) {
|
|
935
|
+
const nodeInfo = await states.node.read();
|
|
475
936
|
const updated = (
|
|
476
937
|
await Promise.all(
|
|
477
938
|
blocklets.map((x) => {
|
|
@@ -482,7 +943,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
482
943
|
const cachedBlocklet =
|
|
483
944
|
useCache && this.cachedBlocklets ? this.cachedBlocklets.find((y) => y.meta.did === x.meta.did) : null;
|
|
484
945
|
|
|
485
|
-
return this.
|
|
946
|
+
return this._attachRuntimeInfo({
|
|
486
947
|
did: x.meta.did,
|
|
487
948
|
nodeInfo,
|
|
488
949
|
diskInfo: false,
|
|
@@ -497,411 +958,296 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
497
958
|
return updated;
|
|
498
959
|
}
|
|
499
960
|
|
|
961
|
+
async list({ includeRuntimeInfo = true, useCache = true, query, filter } = {}, context) {
|
|
962
|
+
const condition = { ...flat(query || {}) };
|
|
963
|
+
if (filter === 'external-only') {
|
|
964
|
+
condition.controller = {
|
|
965
|
+
$exists: true,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if (filter === 'external-excluded') {
|
|
970
|
+
condition.controller = {
|
|
971
|
+
$exists: false,
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const blocklets = await states.blocklet.getBlocklets(condition);
|
|
976
|
+
|
|
977
|
+
if (includeRuntimeInfo) {
|
|
978
|
+
return this.attachBlockletListRuntimeInfo({ blocklets, useCache }, context);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
return blocklets;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// CAUTION: this method currently only support config by blocklet.meta.did
|
|
500
985
|
// eslint-disable-next-line no-unused-vars
|
|
501
|
-
async config({ did,
|
|
502
|
-
|
|
986
|
+
async config({ did, configs: newConfigs, skipHook, skipDidDocument }, context) {
|
|
987
|
+
// todo: skipDidDocument will be deleted
|
|
503
988
|
if (!Array.isArray(newConfigs)) {
|
|
504
989
|
throw new Error('configs list is not an array');
|
|
505
990
|
}
|
|
506
991
|
|
|
507
|
-
|
|
508
|
-
|
|
992
|
+
const dids = Array.isArray(did) ? did : [did];
|
|
993
|
+
const [rootDid, ...childDids] = dids;
|
|
994
|
+
logger.info('config blocklet', { dids });
|
|
995
|
+
|
|
996
|
+
let blocklet = await this.getBlocklet(rootDid);
|
|
997
|
+
for (const childDid of childDids) {
|
|
509
998
|
blocklet = blocklet.children.find((x) => x.meta.did === childDid);
|
|
510
999
|
if (!blocklet) {
|
|
511
|
-
throw new Error('Child blocklet does not exist', {
|
|
1000
|
+
throw new Error('Child blocklet does not exist', { dids });
|
|
512
1001
|
}
|
|
513
1002
|
}
|
|
514
1003
|
|
|
515
1004
|
// run hook
|
|
516
|
-
const nodeEnvironments = await
|
|
517
|
-
|
|
518
|
-
if (x.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
}
|
|
1005
|
+
const nodeEnvironments = await states.node.getEnvironments();
|
|
1006
|
+
for (const x of newConfigs) {
|
|
1007
|
+
if (x.custom === true) {
|
|
1008
|
+
// custom key
|
|
1009
|
+
await environmentNameSchema.validateAsync(x.key);
|
|
1010
|
+
} else if (BLOCKLET_CONFIGURABLE_KEY[x.key] && x.key.startsWith('BLOCKLET_')) {
|
|
1011
|
+
// app key
|
|
1012
|
+
if (childDids.length) {
|
|
1013
|
+
logger.error(`Cannot set ${x.key} to child blocklet`, [dids]);
|
|
1014
|
+
throw new Error(`Cannot set ${x.key} to child blocklet`);
|
|
527
1015
|
}
|
|
528
|
-
}
|
|
529
1016
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
1017
|
+
await validateAppConfig(x, states);
|
|
1018
|
+
} else if (!BLOCKLET_CONFIGURABLE_KEY[x.key] && !isPreferenceKey(x)) {
|
|
1019
|
+
if (!(blocklet.meta.environments || []).some((y) => y.name === x.key)) {
|
|
1020
|
+
// forbid unknown format key
|
|
1021
|
+
throw new Error(`unknown format key: ${x.key}`);
|
|
533
1022
|
}
|
|
534
1023
|
}
|
|
535
1024
|
|
|
536
1025
|
blocklet.configObj[x.key] = x.value;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
if (!skipHook) {
|
|
1029
|
+
// FIXME: we should also call preConfig for child blocklets
|
|
1030
|
+
await hooks.preConfig(blocklet.env.processId, {
|
|
1031
|
+
appDir: blocklet.env.appDir,
|
|
1032
|
+
hooks: Object.assign(blocklet.meta.hooks || {}, blocklet.meta.scripts || {}),
|
|
1033
|
+
exitOnError: true,
|
|
1034
|
+
env: { ...getRuntimeEnvironments(blocklet, nodeEnvironments), ...blocklet.configObj },
|
|
1035
|
+
did,
|
|
1036
|
+
context,
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
const willAppSkChange = isRotatingAppSk(newConfigs, blocklet.configs, blocklet.externalSk);
|
|
1041
|
+
const willAppDidChange = isRotatingAppDid(newConfigs, blocklet.configs, blocklet.externalSk);
|
|
548
1042
|
|
|
549
1043
|
// update db
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
1044
|
+
await states.blockletExtras.setConfigs(dids, newConfigs);
|
|
1045
|
+
|
|
1046
|
+
if (willAppSkChange) {
|
|
1047
|
+
const info = await states.node.read();
|
|
1048
|
+
const { wallet } = getBlockletInfo(blocklet, info.sk);
|
|
1049
|
+
const migratedFrom = Array.isArray(blocklet.migratedFrom) ? blocklet.migratedFrom : [];
|
|
1050
|
+
await states.blocklet.updateBlocklet(rootDid, {
|
|
1051
|
+
migratedFrom: [
|
|
1052
|
+
...migratedFrom,
|
|
1053
|
+
{ appSk: wallet.secretKey, appDid: wallet.address, at: new Date().toISOString() },
|
|
1054
|
+
],
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
553
1057
|
|
|
554
|
-
|
|
1058
|
+
// Reload nginx to make sure did-space can embed content from this app
|
|
1059
|
+
if (newConfigs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT)?.value) {
|
|
1060
|
+
this.emit(BlockletEvents.spaceConnected, blocklet);
|
|
1061
|
+
}
|
|
555
1062
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
newState.configs = configs;
|
|
559
|
-
} else {
|
|
560
|
-
const c = newState.children.find((x) => x.meta.did === childDid);
|
|
561
|
-
c.configs = configs;
|
|
1063
|
+
if (willAppDidChange && !skipDidDocument) {
|
|
1064
|
+
await this._updateDidDocument(blocklet);
|
|
562
1065
|
}
|
|
563
1066
|
|
|
1067
|
+
await this._updateBlockletEnvironment(rootDid);
|
|
1068
|
+
|
|
1069
|
+
// response
|
|
1070
|
+
const newState = await this.getBlocklet(rootDid);
|
|
564
1071
|
this.emit(BlockletEvents.updated, newState);
|
|
565
1072
|
return newState;
|
|
566
1073
|
}
|
|
567
1074
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
1075
|
+
async configPublicToStore({ did, publicToStore = false }) {
|
|
1076
|
+
const blocklet = await this.getBlocklet(did);
|
|
1077
|
+
// publicToStore 由用户传入
|
|
1078
|
+
// handleInstanceInStore 方法写在前面,保证向 store 操作成功后才会更改 blocklet 中的 publicToStore值
|
|
1079
|
+
// handleInstanceInStore 中会校验修改 publicToStore字段 的条件,不符合则会抛错,就不会执行下面更新 publicToStore 的逻辑
|
|
1080
|
+
await handleInstanceInStore(blocklet, { publicToStore });
|
|
1081
|
+
await states.blockletExtras.setSettings(did, { publicToStore });
|
|
1082
|
+
|
|
1083
|
+
const newState = await this.getBlocklet(did);
|
|
1084
|
+
return newState;
|
|
1085
|
+
}
|
|
573
1086
|
|
|
574
|
-
|
|
575
|
-
if (!
|
|
576
|
-
throw new Error('
|
|
1087
|
+
async configNavigations({ did, navigations = [] }) {
|
|
1088
|
+
if (!Array.isArray(navigations)) {
|
|
1089
|
+
throw new Error('navigations is not an array');
|
|
577
1090
|
}
|
|
1091
|
+
await states.blockletExtras.setSettings(did, { navigations });
|
|
578
1092
|
|
|
579
|
-
const
|
|
1093
|
+
const newState = await this.getBlocklet(did);
|
|
1094
|
+
this.emit(BlockletEvents.updated, newState);
|
|
1095
|
+
return newState;
|
|
1096
|
+
}
|
|
580
1097
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
did,
|
|
584
|
-
registryUrl: upgradeFromRegistry,
|
|
585
|
-
},
|
|
586
|
-
context
|
|
587
|
-
);
|
|
1098
|
+
async updateWhoCanAccess({ did, whoCanAccess }) {
|
|
1099
|
+
const dids = Array.isArray(did) ? did : [did];
|
|
588
1100
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
// if old signature is old registry version, return new signatures last one signature
|
|
598
|
-
if (oldSignatures.length > 0 && oldSignatures.length < 3) {
|
|
599
|
-
return signatures[signatures.length - 1];
|
|
1101
|
+
const [rootDid] = dids;
|
|
1102
|
+
|
|
1103
|
+
const isApp = dids.length === 1;
|
|
1104
|
+
|
|
1105
|
+
try {
|
|
1106
|
+
// check exist
|
|
1107
|
+
if (!(await this.hasBlocklet({ did: rootDid }))) {
|
|
1108
|
+
throw new Error('The blocklet does not exist');
|
|
600
1109
|
}
|
|
601
|
-
// old registry signatures: [ registry 签名, developer-sk 签名]
|
|
602
|
-
// new registry signatures [ registry 签名, user wallet 签名, access-token 签名 ]
|
|
603
|
-
// old -> old: 需要对比 developer-sk 签名
|
|
604
|
-
// old -> new: 需要对比 developer-sk 和 access-token 签名
|
|
605
|
-
// new -> new: 需要对比 user-wallet 签名
|
|
606
|
-
return signatures.length === 1 ? signatures[0] : signatures[1];
|
|
607
|
-
}
|
|
608
1110
|
|
|
609
|
-
|
|
610
|
-
|
|
1111
|
+
// validate input
|
|
1112
|
+
if (
|
|
1113
|
+
!whoCanAccess.startsWith(WHO_CAN_ACCESS_PREFIX_ROLES) &&
|
|
1114
|
+
!Object.values(WHO_CAN_ACCESS).includes(whoCanAccess)
|
|
1115
|
+
) {
|
|
1116
|
+
throw new Error(`The value of whoCanAccess is invalid: ${whoCanAccess}`);
|
|
1117
|
+
} else if (whoCanAccess.startsWith(WHO_CAN_ACCESS_PREFIX_ROLES)) {
|
|
1118
|
+
if (!whoCanAccess.substring(WHO_CAN_ACCESS_PREFIX_ROLES.length).trim()) {
|
|
1119
|
+
throw new Error('Roles in whoCanAccess cannot be empty');
|
|
1120
|
+
}
|
|
611
1121
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
1122
|
+
if (whoCanAccess.length > 200) {
|
|
1123
|
+
throw new Error('The length of whoCanAccess should not exceed 200');
|
|
1124
|
+
}
|
|
615
1125
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
1126
|
+
const roleNames = (await this.teamManager.getRoles(rootDid)).map((x) => x.name);
|
|
1127
|
+
const accessRoleNames = getRolesFromAuthConfig({ whoCanAccess });
|
|
1128
|
+
const noExistNames = accessRoleNames.filter((x) => !roleNames.includes(x));
|
|
1129
|
+
if (noExistNames.length) {
|
|
1130
|
+
throw new Error(`Found no exist role names: ${noExistNames.join(',')}`);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
} catch (error) {
|
|
1134
|
+
logger.error(error.message);
|
|
1135
|
+
throw error;
|
|
624
1136
|
}
|
|
625
1137
|
|
|
626
|
-
if (
|
|
627
|
-
|
|
1138
|
+
if (isApp) {
|
|
1139
|
+
await states.blockletExtras.setSettings(rootDid, { whoCanAccess });
|
|
1140
|
+
} else {
|
|
1141
|
+
const configs = [{ key: BLOCKLET_CONFIGURABLE_KEY.COMPONENT_ACCESS_WHO, value: whoCanAccess }];
|
|
1142
|
+
await states.blockletExtras.setConfigs(dids, configs);
|
|
628
1143
|
}
|
|
629
1144
|
|
|
630
|
-
const
|
|
631
|
-
logger.info(`${action} blocklet`, { did });
|
|
1145
|
+
const blocklet = await this.getBlocklet(rootDid);
|
|
632
1146
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
deployedFrom: upgradeFromRegistry,
|
|
637
|
-
context,
|
|
638
|
-
});
|
|
1147
|
+
this.emit(BlockletEvents.updated, { meta: { did: blocklet.meta.did } });
|
|
1148
|
+
|
|
1149
|
+
return blocklet;
|
|
639
1150
|
}
|
|
640
1151
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
if (!did) {
|
|
644
|
-
throw new Error('did is empty');
|
|
645
|
-
}
|
|
646
|
-
if (!clientFiles || !clientFiles.length) {
|
|
647
|
-
throw new Error('hashFiles is empty');
|
|
648
|
-
}
|
|
1152
|
+
async updateComponentTitle({ did, rootDid: inputRootDid, title }) {
|
|
1153
|
+
await titleSchema.validateAsync(title);
|
|
649
1154
|
|
|
650
|
-
|
|
1155
|
+
const blocklet = await states.blocklet.getBlocklet(inputRootDid);
|
|
651
1156
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
return {
|
|
655
|
-
hasBlocklet: false,
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
if (state.source === BlockletSource.local) {
|
|
659
|
-
throw new Error(`Blocklet ${state.meta.name} is already deployed from local, can not deployed from remote.`);
|
|
1157
|
+
if (!blocklet) {
|
|
1158
|
+
throw new Error('blocklet does not exist');
|
|
660
1159
|
}
|
|
661
|
-
const { name, version } = state.meta;
|
|
662
|
-
const installDir = path.join(this.installDir, name, version);
|
|
663
|
-
// eslint-disable-next-line no-param-reassign
|
|
664
|
-
clientFiles = clientFiles.reduce((obj, item) => {
|
|
665
|
-
obj[item.file] = item.hash;
|
|
666
|
-
return obj;
|
|
667
|
-
}, {});
|
|
668
1160
|
|
|
669
|
-
const
|
|
670
|
-
filter: (x) => x.indexOf('node_modules') === -1,
|
|
671
|
-
concurrentHash: 1,
|
|
672
|
-
});
|
|
673
|
-
logger.info('Get files hash', { filesNum: Object.keys(files).length });
|
|
674
|
-
|
|
675
|
-
const addSet = [];
|
|
676
|
-
const changeSet = [];
|
|
677
|
-
const deleteSet = [];
|
|
678
|
-
const diffFiles = diff(files, clientFiles);
|
|
679
|
-
if (diffFiles) {
|
|
680
|
-
diffFiles.forEach((item) => {
|
|
681
|
-
if (item.kind === 'D') {
|
|
682
|
-
deleteSet.push(item.path[0]);
|
|
683
|
-
}
|
|
684
|
-
if (item.kind === 'E') {
|
|
685
|
-
changeSet.push(item.path[0]);
|
|
686
|
-
}
|
|
687
|
-
if (item.kind === 'N') {
|
|
688
|
-
addSet.push(item.path[0]);
|
|
689
|
-
}
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
logger.info('Diff files', {
|
|
693
|
-
name: state.meta.name,
|
|
694
|
-
did: state.meta.did,
|
|
695
|
-
version: state.meta.version,
|
|
696
|
-
addNum: addSet.length,
|
|
697
|
-
changeNum: changeSet.length,
|
|
698
|
-
deleteNum: deleteSet.length,
|
|
699
|
-
});
|
|
700
|
-
return {
|
|
701
|
-
hasBlocklet: true,
|
|
702
|
-
version,
|
|
703
|
-
addSet,
|
|
704
|
-
changeSet,
|
|
705
|
-
deleteSet,
|
|
706
|
-
};
|
|
707
|
-
}
|
|
1161
|
+
const rootDid = blocklet.meta.did;
|
|
708
1162
|
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
const childrenMeta = await getChildrenMeta(blocklet.meta);
|
|
712
|
-
const updateList = getUpdateMetaList(
|
|
713
|
-
blocklet.children.map((x) => x.meta),
|
|
714
|
-
childrenMeta
|
|
715
|
-
);
|
|
1163
|
+
const { children } = blocklet;
|
|
1164
|
+
const component = children.find((x) => x.meta.did === did);
|
|
716
1165
|
|
|
717
|
-
if (!
|
|
718
|
-
|
|
1166
|
+
if (!component) {
|
|
1167
|
+
throw new Error('component does not exist');
|
|
719
1168
|
}
|
|
720
1169
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
did,
|
|
724
|
-
childrenMeta,
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
return {
|
|
728
|
-
updateId,
|
|
729
|
-
updateList,
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
async updateChildren({ updateId }, context) {
|
|
734
|
-
const { did, childrenMeta } = await this.session.end(updateId);
|
|
1170
|
+
component.meta.title = title;
|
|
1171
|
+
await states.blocklet.updateBlocklet(rootDid, { children });
|
|
735
1172
|
|
|
736
|
-
//
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
const { name, version } = meta;
|
|
1173
|
+
// trigger meta.js refresh
|
|
1174
|
+
// trigger dashboard frontend refresh
|
|
1175
|
+
this.emit(BlockletEvents.updated, blocklet);
|
|
740
1176
|
|
|
741
|
-
|
|
1177
|
+
return this.getBlocklet(rootDid);
|
|
1178
|
+
}
|
|
742
1179
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
name,
|
|
746
|
-
version,
|
|
747
|
-
children: childrenMeta.map((x) => ({ name: x.name, version: x.version })),
|
|
748
|
-
});
|
|
1180
|
+
async updateComponentMountPoint({ did, rootDid: inputRootDid, mountPoint: tmpMountPoint }, context) {
|
|
1181
|
+
const mountPoint = await updateMountPointSchema.validateAsync(tmpMountPoint);
|
|
749
1182
|
|
|
750
|
-
|
|
751
|
-
const newBlocklet = await this.state.setBlockletStatus(did, BlockletStatus.waiting);
|
|
752
|
-
mergeMeta(meta, childrenMeta);
|
|
753
|
-
newBlocklet.meta = meta;
|
|
754
|
-
newBlocklet.children = await this.state.getChildrenFromMetas(childrenMeta);
|
|
755
|
-
await validateBlocklet(newBlocklet);
|
|
1183
|
+
const blocklet = await states.blocklet.getBlocklet(inputRootDid);
|
|
756
1184
|
|
|
757
|
-
|
|
1185
|
+
if (!blocklet) {
|
|
1186
|
+
throw new Error('blocklet does not exist');
|
|
1187
|
+
}
|
|
758
1188
|
|
|
759
|
-
|
|
760
|
-
const ticket = this.installQueue.push(
|
|
761
|
-
{
|
|
762
|
-
entity: 'blocklet',
|
|
763
|
-
action: 'download',
|
|
764
|
-
id: did,
|
|
765
|
-
oldBlocklet: { ...oldBlocklet },
|
|
766
|
-
blocklet: { ...newBlocklet },
|
|
767
|
-
version,
|
|
768
|
-
context,
|
|
769
|
-
postAction: action,
|
|
770
|
-
},
|
|
771
|
-
did
|
|
772
|
-
);
|
|
1189
|
+
const rootDid = blocklet.meta.did;
|
|
773
1190
|
|
|
774
|
-
|
|
775
|
-
logger.error('queue failed', { entity: 'blocklet', action, did, version, name, error: err });
|
|
776
|
-
await this._rollback(action, did, oldBlocklet);
|
|
777
|
-
this.emit(`blocklet.${action}.failed`, { did, version, err });
|
|
778
|
-
this.notification.create({
|
|
779
|
-
title: `Blocklet ${capitalize(action)} Failed`,
|
|
780
|
-
description: `Blocklet ${name}@${version} ${action} failed with error: ${err.message || 'queue exception'}`,
|
|
781
|
-
entityType: 'blocklet',
|
|
782
|
-
entityId: did,
|
|
783
|
-
severity: 'error',
|
|
784
|
-
});
|
|
785
|
-
});
|
|
786
|
-
return newBlocklet;
|
|
787
|
-
}
|
|
1191
|
+
const isRootComponent = !did;
|
|
788
1192
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
logger.info('dev blocklet', { folder });
|
|
1193
|
+
const component = isRootComponent ? blocklet : blocklet.children.find((x) => x.meta.did === did);
|
|
1194
|
+
if (!component) {
|
|
1195
|
+
throw new Error('component does not exist');
|
|
1196
|
+
}
|
|
794
1197
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
throw new Error('Incorrect blocklet manifest: missing `scripts.dev` field');
|
|
1198
|
+
if (isRootComponent && component.group === BlockletGroup.gateway) {
|
|
1199
|
+
throw new Error('cannot update mountPoint of gateway blocklet');
|
|
798
1200
|
}
|
|
799
1201
|
|
|
800
|
-
|
|
801
|
-
meta.title = `[DEV] ${meta.title || meta.name}`;
|
|
1202
|
+
checkDuplicateMountPoint(blocklet, mountPoint);
|
|
802
1203
|
|
|
803
|
-
|
|
804
|
-
if (exist) {
|
|
805
|
-
if (exist.mode === BLOCKLET_MODES.PRODUCTION) {
|
|
806
|
-
throw new Error('The blocklet of production mode already exists, please remove it before developing');
|
|
807
|
-
}
|
|
1204
|
+
component.mountPoint = mountPoint;
|
|
808
1205
|
|
|
809
|
-
|
|
810
|
-
if (['starting', 'running'].includes(status)) {
|
|
811
|
-
throw new Error(`The blocklet is already on ${status}, please stop it before developing`);
|
|
812
|
-
}
|
|
1206
|
+
await states.blocklet.updateBlocklet(rootDid, { mountPoint: blocklet.mountPoint, children: blocklet.children });
|
|
813
1207
|
|
|
814
|
-
|
|
1208
|
+
this.emit(BlockletEvents.upgraded, { blocklet, context: { ...context, createAuditLog: false } }); // trigger router refresh
|
|
815
1209
|
|
|
816
|
-
|
|
817
|
-
|
|
1210
|
+
return this.getBlocklet(rootDid);
|
|
1211
|
+
}
|
|
818
1212
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
logger.info('delete blocklet precess for dev', { did, version });
|
|
823
|
-
} catch (err) {
|
|
824
|
-
logger.error('failed to delete blocklet process for dev', { error: err });
|
|
825
|
-
}
|
|
1213
|
+
// eslint-disable-next-line no-unused-vars
|
|
1214
|
+
async getRuntimeHistory({ did, hours }, context) {
|
|
1215
|
+
const metaDid = await states.blocklet.getBlockletMetaDid(did);
|
|
826
1216
|
|
|
827
|
-
const
|
|
828
|
-
mergeMeta(meta, childrenMeta);
|
|
829
|
-
const blocklet = await this.state.addBlocklet({
|
|
830
|
-
did,
|
|
831
|
-
meta,
|
|
832
|
-
source: BlockletSource.local,
|
|
833
|
-
deployedFrom: folder,
|
|
834
|
-
mode: BLOCKLET_MODES.DEVELOPMENT,
|
|
835
|
-
childrenMeta,
|
|
836
|
-
});
|
|
837
|
-
logger.info('add blocklet for dev', { did, version, meta });
|
|
1217
|
+
const history = this.runtimeMonitor.getHistory(metaDid);
|
|
838
1218
|
|
|
839
|
-
|
|
840
|
-
|
|
1219
|
+
return getHistoryList({
|
|
1220
|
+
history,
|
|
1221
|
+
hours,
|
|
1222
|
+
recordIntervalSec: MONITOR_RECORD_INTERVAL_SEC,
|
|
1223
|
+
props: ['date', 'cpu', 'mem'],
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
841
1226
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1227
|
+
async ensureBlocklet(did, opts = {}) {
|
|
1228
|
+
return getBlocklet({ ...opts, states, dataDirs: this.dataDirs, did, ensureIntegrity: true });
|
|
1229
|
+
}
|
|
845
1230
|
|
|
846
|
-
|
|
847
|
-
return this.
|
|
1231
|
+
async getBlocklet(did, opts = {}) {
|
|
1232
|
+
return getBlocklet({ ...opts, states, dataDirs: this.dataDirs, did, ensureIntegrity: false });
|
|
848
1233
|
}
|
|
849
1234
|
|
|
850
|
-
async
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
}
|
|
1235
|
+
async hasBlocklet({ did }) {
|
|
1236
|
+
return states.blocklet.hasBlocklet(did);
|
|
1237
|
+
}
|
|
854
1238
|
|
|
855
|
-
|
|
856
|
-
if (!
|
|
857
|
-
throw new Error(
|
|
1239
|
+
async setInitialized({ did, owner }) {
|
|
1240
|
+
if (!validateOwner(owner)) {
|
|
1241
|
+
throw new Error('Blocklet owner is invalid');
|
|
858
1242
|
}
|
|
859
1243
|
|
|
860
|
-
blocklet
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
// cacheDir is /dataDirs.cache/blocklet.meta.name
|
|
864
|
-
// logDir is /dataDirs.log/blocklet.meta.name
|
|
865
|
-
...getBlockletDirs(blocklet, {
|
|
866
|
-
dataDirs: this.dataDirs,
|
|
867
|
-
ensure: true,
|
|
868
|
-
}),
|
|
869
|
-
};
|
|
870
|
-
|
|
871
|
-
const configs = await this.extras.getConfigs(did);
|
|
872
|
-
fillBlockletConfigs(blocklet, configs);
|
|
873
|
-
|
|
874
|
-
// merge settings to blocklet
|
|
875
|
-
const settings = await this.extras.getSettings(did);
|
|
876
|
-
blocklet.trustedPassports = get(settings, 'trustedPassports') || [];
|
|
877
|
-
blocklet.enablePassportIssuance = get(settings, 'enablePassportIssuance', true);
|
|
878
|
-
|
|
879
|
-
// handle child env
|
|
880
|
-
for (const child of blocklet.children) {
|
|
881
|
-
const {
|
|
882
|
-
meta: { did: childDid, name: childName },
|
|
883
|
-
} = child;
|
|
884
|
-
const {
|
|
885
|
-
meta: { name },
|
|
886
|
-
} = blocklet;
|
|
887
|
-
|
|
888
|
-
child.env = {
|
|
889
|
-
appId: `${encodeURIComponent(name)}/${encodeURIComponent(childName)}`,
|
|
890
|
-
// dataDir is /dataDir.data/blocklet.meta.name/child.meta.name
|
|
891
|
-
// cacheDir is /dataDirs.cache/blocklet.meta.name/child.meta.name
|
|
892
|
-
// logDir is /dataDirs.log/blocklet.meta.name
|
|
893
|
-
...getBlockletDirs(child, {
|
|
894
|
-
dataDirs: this.dataDirs,
|
|
895
|
-
ensure: true,
|
|
896
|
-
rootBlocklet: blocklet,
|
|
897
|
-
}),
|
|
898
|
-
};
|
|
1244
|
+
const blocklet = await states.blocklet.getBlocklet(did);
|
|
1245
|
+
await states.blockletExtras.setSettings(blocklet.meta.did, { initialized: true, owner });
|
|
1246
|
+
logger.info('Blocklet initialized', { did, owner });
|
|
899
1247
|
|
|
900
|
-
|
|
901
|
-
fillBlockletConfigs(child, childConfigs);
|
|
902
|
-
}
|
|
1248
|
+
this.emit(BlockletEvents.updated, { meta: { did: blocklet.meta.did } });
|
|
903
1249
|
|
|
904
|
-
return
|
|
1250
|
+
return this.getBlocklet(did);
|
|
905
1251
|
}
|
|
906
1252
|
|
|
907
1253
|
async status(did, { forceSync = false } = {}) {
|
|
@@ -913,10 +1259,12 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
913
1259
|
return blocklet;
|
|
914
1260
|
}
|
|
915
1261
|
|
|
916
|
-
|
|
1262
|
+
const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped);
|
|
1263
|
+
this.emit(BlockletEvents.statusChange, res);
|
|
1264
|
+
return res;
|
|
917
1265
|
};
|
|
918
1266
|
|
|
919
|
-
const blocklet = await this.
|
|
1267
|
+
const blocklet = await this.getBlocklet(did);
|
|
920
1268
|
|
|
921
1269
|
let shouldUpdateStatus = forceSync || shouldUpdateBlockletStatus(blocklet.status);
|
|
922
1270
|
if (isInProgress(blocklet.status)) {
|
|
@@ -927,104 +1275,21 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
927
1275
|
}
|
|
928
1276
|
}
|
|
929
1277
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
if (shouldUpdateStatus) {
|
|
933
|
-
if (status === BlockletStatus.stopped) {
|
|
934
|
-
await this.state.stopBlocklet(did);
|
|
935
|
-
}
|
|
936
|
-
return this.state.setBlockletStatus(did, status);
|
|
937
|
-
}
|
|
938
|
-
} catch (err) {
|
|
939
|
-
if (shouldUpdateStatus) {
|
|
940
|
-
return fastReturnOnForceSync(blocklet);
|
|
941
|
-
}
|
|
942
|
-
throw err;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
return blocklet;
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
async getBlockletInterfaces({ blocklet, nodeInfo, context }) {
|
|
949
|
-
const routingRules = (await this.getRoutingRulesByDid(blocklet.meta.did)).sort((a, b) => {
|
|
950
|
-
// Put user-defined rules first
|
|
951
|
-
if (a.isProtected !== b.isProtected) {
|
|
952
|
-
return a.isProtected ? 1 : -1;
|
|
953
|
-
}
|
|
954
|
-
// Put shorter url first
|
|
955
|
-
return a.from.pathPrefix.length < b.from.pathPrefix ? 1 : -1;
|
|
956
|
-
});
|
|
957
|
-
const nodeIp = await getAccessibleExternalNodeIp(nodeInfo);
|
|
958
|
-
return getBlockletInterfaces({ blocklet, context, nodeInfo, routingRules, nodeIp });
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
async attachRuntimeInfo({ did, nodeInfo, diskInfo = true, context, cachedBlocklet }) {
|
|
962
|
-
if (!did) {
|
|
963
|
-
throw new Error('did should not be empty');
|
|
1278
|
+
if (!shouldUpdateStatus) {
|
|
1279
|
+
return blocklet;
|
|
964
1280
|
}
|
|
965
1281
|
|
|
966
1282
|
try {
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
blocklet = { ...cachedBlocklet, ...blocklet };
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
blocklet.interfaces = await this.getBlockletInterfaces({ blocklet, nodeInfo, context });
|
|
976
|
-
|
|
977
|
-
if (!fromCache) {
|
|
978
|
-
blocklet.engine = getEngine(getBlockletEngineNameByPlatform(blocklet.meta)).describe();
|
|
979
|
-
blocklet.diskInfo = await getDiskInfo(blocklet, { useFakeDiskInfo: !diskInfo });
|
|
980
|
-
|
|
981
|
-
try {
|
|
982
|
-
const { appId } = blocklet.meta.group === BlockletGroup.gateway ? blocklet.children[0].env : blocklet.env;
|
|
983
|
-
blocklet.runtimeInfo = await getRuntimeInfo(appId);
|
|
984
|
-
if (blocklet.runtimeInfo.status && shouldUpdateBlockletStatus(blocklet.status)) {
|
|
985
|
-
blocklet.status = statusMap[blocklet.runtimeInfo.status];
|
|
986
|
-
}
|
|
987
|
-
} catch (err) {
|
|
988
|
-
if (err.code !== 'BLOCKLET_PROCESS_404') {
|
|
989
|
-
logger.error('failed to construct blocklet runtime info', { did, error: err });
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
if (blocklet.status === BlockletStatus.running) {
|
|
993
|
-
await this.state.setBlockletStatus(did, BlockletStatus.stopped);
|
|
994
|
-
blocklet.status = BlockletStatus.stopped;
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
await Promise.all(
|
|
999
|
-
blocklet.children.map(async (child) => {
|
|
1000
|
-
child.engine = getEngine(getBlockletEngineNameByPlatform(child.meta)).describe();
|
|
1001
|
-
child.diskInfo = await getDiskInfo(child, {
|
|
1002
|
-
useFakeDiskInfo: !diskInfo,
|
|
1003
|
-
});
|
|
1004
|
-
if (child.meta.group !== BlockletGroup.gateway) {
|
|
1005
|
-
try {
|
|
1006
|
-
child.runtimeInfo = await getRuntimeInfo(child.env.appId);
|
|
1007
|
-
child.status = statusMap[blocklet.runtimeInfo.status];
|
|
1008
|
-
} catch (err) {
|
|
1009
|
-
if (err.code !== 'BLOCKLET_PROCESS_404') {
|
|
1010
|
-
logger.error('failed to construct blocklet runtime info', { did, error: err });
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
})
|
|
1015
|
-
);
|
|
1283
|
+
const status = await getBlockletStatusFromProcess(blocklet);
|
|
1284
|
+
if (blocklet.status !== status) {
|
|
1285
|
+
const res = await states.blocklet.setBlockletStatus(did, status);
|
|
1286
|
+
this.emit(BlockletEvents.statusChange, res);
|
|
1287
|
+
return res;
|
|
1016
1288
|
}
|
|
1017
1289
|
|
|
1018
1290
|
return blocklet;
|
|
1019
1291
|
} catch (err) {
|
|
1020
|
-
|
|
1021
|
-
logger.error('failed to get blocklet info', {
|
|
1022
|
-
did,
|
|
1023
|
-
name: get(simpleState, 'meta.name'),
|
|
1024
|
-
status: get(simpleState, 'status'),
|
|
1025
|
-
error: err,
|
|
1026
|
-
});
|
|
1027
|
-
return simpleState;
|
|
1292
|
+
return fastReturnOnForceSync(blocklet);
|
|
1028
1293
|
}
|
|
1029
1294
|
}
|
|
1030
1295
|
|
|
@@ -1034,63 +1299,177 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1034
1299
|
});
|
|
1035
1300
|
}
|
|
1036
1301
|
|
|
1302
|
+
async updateAllBlockletEnvironment() {
|
|
1303
|
+
const blocklets = await states.blocklet.getBlocklets();
|
|
1304
|
+
for (let i = 0; i < blocklets.length; i++) {
|
|
1305
|
+
const blocklet = blocklets[i];
|
|
1306
|
+
await this._updateBlockletEnvironment(blocklet.meta.did);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
async prune() {
|
|
1311
|
+
const blocklets = await states.blocklet.getBlocklets();
|
|
1312
|
+
const settings = await states.blockletExtras.listSettings();
|
|
1313
|
+
await pruneBlockletBundle({
|
|
1314
|
+
installDir: this.dataDirs.blocklets,
|
|
1315
|
+
blocklets,
|
|
1316
|
+
blockletSettings: settings
|
|
1317
|
+
.filter((x) => x.settings.children && x.settings.children.length)
|
|
1318
|
+
.map((x) => x.settings),
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1037
1322
|
async onJob(job) {
|
|
1038
1323
|
if (job.entity === 'blocklet') {
|
|
1039
1324
|
if (job.action === 'download') {
|
|
1040
|
-
await this.
|
|
1325
|
+
await this._downloadAndInstall(job);
|
|
1041
1326
|
}
|
|
1042
1327
|
if (job.action === 'restart') {
|
|
1043
|
-
await this.
|
|
1328
|
+
await this._onRestart(job);
|
|
1044
1329
|
}
|
|
1045
1330
|
|
|
1046
1331
|
if (job.action === 'check_if_started') {
|
|
1047
|
-
await this.
|
|
1332
|
+
await this._onCheckIfStarted(job);
|
|
1048
1333
|
}
|
|
1049
1334
|
}
|
|
1050
1335
|
}
|
|
1051
1336
|
|
|
1052
|
-
|
|
1337
|
+
getCrons() {
|
|
1338
|
+
return [
|
|
1339
|
+
{
|
|
1340
|
+
name: 'sync-blocklet-status',
|
|
1341
|
+
time: '*/60 * * * * *', // 60s
|
|
1342
|
+
fn: this._syncBlockletStatus.bind(this),
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
name: 'sync-blocklet-list',
|
|
1346
|
+
time: '*/60 * * * * *', // 60s
|
|
1347
|
+
fn: this.refreshListCache.bind(this),
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
name: 'refresh-accessible-ip',
|
|
1351
|
+
time: '0 */10 * * * *', // 10min
|
|
1352
|
+
fn: async () => {
|
|
1353
|
+
const nodeInfo = await states.node.read();
|
|
1354
|
+
await refreshAccessibleExternalNodeIp(nodeInfo);
|
|
1355
|
+
},
|
|
1356
|
+
},
|
|
1357
|
+
{
|
|
1358
|
+
name: 'delete-expired-external-blocklet',
|
|
1359
|
+
time: '0 */30 * * * *', // 30min
|
|
1360
|
+
options: { runOnInit: false },
|
|
1361
|
+
fn: () => this._deleteExpiredExternalBlocklet(),
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
name: 'clean-expired-blocklet-data',
|
|
1365
|
+
time: '0 */20 0 * * *', // 每天凌晨 0 点的每 20 分钟
|
|
1366
|
+
fn: () => this._cleanExpiredBlockletData(),
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
name: 'record-blocklet-runtime-history',
|
|
1370
|
+
time: `*/${MONITOR_RECORD_INTERVAL_SEC} * * * * *`, // 10s
|
|
1371
|
+
fn: () => this.runtimeMonitor.monitAll(),
|
|
1372
|
+
},
|
|
1373
|
+
];
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// ============================================================================================
|
|
1377
|
+
// Private API that are used by self of helper function
|
|
1378
|
+
// ============================================================================================
|
|
1379
|
+
|
|
1380
|
+
/**
|
|
1381
|
+
*
|
|
1382
|
+
*
|
|
1383
|
+
* @param {{
|
|
1384
|
+
* blocklet: {},
|
|
1385
|
+
* context: {},
|
|
1386
|
+
* postAction: 'install' | 'upgrade',
|
|
1387
|
+
* oldBlocklet: {},
|
|
1388
|
+
* throwOnError: Error,
|
|
1389
|
+
* skipCheckStatusBeforeDownload: boolean,
|
|
1390
|
+
* selectedComponentDids: Array<did>,
|
|
1391
|
+
* }} params
|
|
1392
|
+
* @return {*}
|
|
1393
|
+
* @memberof BlockletManager
|
|
1394
|
+
*/
|
|
1395
|
+
async _downloadAndInstall(params) {
|
|
1396
|
+
const {
|
|
1397
|
+
blocklet,
|
|
1398
|
+
context,
|
|
1399
|
+
postAction,
|
|
1400
|
+
oldBlocklet,
|
|
1401
|
+
throwOnError,
|
|
1402
|
+
skipCheckStatusBeforeDownload,
|
|
1403
|
+
selectedComponentDids,
|
|
1404
|
+
} = params;
|
|
1053
1405
|
const { meta } = blocklet;
|
|
1054
1406
|
const { name, did, version } = meta;
|
|
1055
1407
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
});
|
|
1408
|
+
// check status
|
|
1409
|
+
if (!skipCheckStatusBeforeDownload) {
|
|
1410
|
+
try {
|
|
1411
|
+
await statusLock.acquire();
|
|
1412
|
+
|
|
1413
|
+
const b0 = await states.blocklet.getBlocklet(did);
|
|
1414
|
+
if (!b0 || ![BlockletStatus.waiting].includes(b0.status)) {
|
|
1415
|
+
if (!b0) {
|
|
1416
|
+
throw new Error('blocklet does not exist before downloading');
|
|
1417
|
+
} else {
|
|
1418
|
+
throw new Error(`blocklet status is invalid before downloading: ${fromBlockletStatus(b0.status)}`);
|
|
1419
|
+
}
|
|
1069
1420
|
}
|
|
1070
|
-
|
|
1421
|
+
statusLock.release();
|
|
1422
|
+
} catch (error) {
|
|
1423
|
+
statusLock.release();
|
|
1424
|
+
logger.error('Check blocklet status failed before downloading', {
|
|
1425
|
+
name,
|
|
1426
|
+
did,
|
|
1427
|
+
error,
|
|
1428
|
+
});
|
|
1071
1429
|
await this._rollback(postAction, did, oldBlocklet);
|
|
1072
1430
|
return;
|
|
1073
1431
|
}
|
|
1432
|
+
}
|
|
1074
1433
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1434
|
+
// download bundle
|
|
1435
|
+
try {
|
|
1436
|
+
const blockletForDownload = {
|
|
1437
|
+
...blocklet,
|
|
1438
|
+
children: (blocklet.children || []).filter((x) => {
|
|
1439
|
+
if (selectedComponentDids?.length) {
|
|
1440
|
+
return selectedComponentDids.includes(x.meta.did);
|
|
1441
|
+
}
|
|
1442
|
+
return x;
|
|
1443
|
+
}),
|
|
1444
|
+
};
|
|
1445
|
+
const { isCancelled } = await this._downloadBlocklet(blockletForDownload, context);
|
|
1081
1446
|
|
|
1082
1447
|
if (isCancelled) {
|
|
1083
|
-
logger.info('Download was canceled
|
|
1084
|
-
|
|
1448
|
+
logger.info('Download was canceled', { name, did, version });
|
|
1449
|
+
|
|
1450
|
+
// rollback on download cancelled
|
|
1451
|
+
await statusLock.acquire();
|
|
1452
|
+
try {
|
|
1453
|
+
if ((await states.blocklet.getBlockletStatus(did)) === BlockletStatus.downloading) {
|
|
1454
|
+
await this._rollback(postAction, did, oldBlocklet);
|
|
1455
|
+
}
|
|
1456
|
+
statusLock.release();
|
|
1457
|
+
} catch (error) {
|
|
1458
|
+
statusLock.release();
|
|
1459
|
+
logger.error('Rollback blocklet failed on download canceled', { postAction, name, did, version, error });
|
|
1460
|
+
}
|
|
1085
1461
|
return;
|
|
1086
1462
|
}
|
|
1087
1463
|
} catch (err) {
|
|
1088
1464
|
logger.error('Download blocklet tarball failed', { name, did, version });
|
|
1089
1465
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1466
|
+
this.emit(BlockletEvents.downloadFailed, {
|
|
1467
|
+
meta: { did },
|
|
1468
|
+
error: {
|
|
1469
|
+
message: err.message,
|
|
1470
|
+
},
|
|
1471
|
+
});
|
|
1472
|
+
this._createNotification(did, {
|
|
1094
1473
|
title: 'Blocklet Download Failed',
|
|
1095
1474
|
description: `Blocklet ${name}@${version} download failed with error: ${err.message}`,
|
|
1096
1475
|
entityType: 'blocklet',
|
|
@@ -1098,10 +1477,16 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1098
1477
|
severity: 'error',
|
|
1099
1478
|
});
|
|
1100
1479
|
|
|
1480
|
+
// rollback on download failed
|
|
1481
|
+
await statusLock.acquire();
|
|
1101
1482
|
try {
|
|
1102
|
-
await
|
|
1103
|
-
|
|
1104
|
-
|
|
1483
|
+
if ((await states.blocklet.getBlockletStatus(did)) === BlockletStatus.downloading) {
|
|
1484
|
+
await this._rollback(postAction, did, oldBlocklet);
|
|
1485
|
+
}
|
|
1486
|
+
statusLock.release();
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
statusLock.release();
|
|
1489
|
+
logger.error('Rollback blocklet failed on download failed', { postAction, name, did, version, error });
|
|
1105
1490
|
}
|
|
1106
1491
|
|
|
1107
1492
|
if (throwOnError) {
|
|
@@ -1111,16 +1496,41 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1111
1496
|
return;
|
|
1112
1497
|
}
|
|
1113
1498
|
|
|
1499
|
+
// update status
|
|
1500
|
+
try {
|
|
1501
|
+
await statusLock.acquire();
|
|
1502
|
+
|
|
1503
|
+
if ((await states.blocklet.getBlockletStatus(did)) !== BlockletStatus.downloading) {
|
|
1504
|
+
throw new Error('blocklet status changed durning download');
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
if (postAction === 'install') {
|
|
1508
|
+
const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.installing);
|
|
1509
|
+
this.emit(BlockletEvents.statusChange, state);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
if (postAction === 'upgrade') {
|
|
1513
|
+
const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.upgrading);
|
|
1514
|
+
this.emit(BlockletEvents.statusChange, state);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
statusLock.release();
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
logger.error(error.message);
|
|
1520
|
+
statusLock.release();
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// install
|
|
1114
1524
|
try {
|
|
1115
1525
|
// install blocklet
|
|
1116
1526
|
if (postAction === 'install') {
|
|
1117
|
-
await this.
|
|
1527
|
+
await this._onInstall({ blocklet, context, oldBlocklet });
|
|
1118
1528
|
return;
|
|
1119
1529
|
}
|
|
1120
1530
|
|
|
1121
1531
|
// upgrade blocklet
|
|
1122
|
-
if (
|
|
1123
|
-
await this.
|
|
1532
|
+
if (postAction === 'upgrade') {
|
|
1533
|
+
await this._onUpgrade({ oldBlocklet, newBlocklet: blocklet, context });
|
|
1124
1534
|
}
|
|
1125
1535
|
} catch (error) {
|
|
1126
1536
|
if (throwOnError) {
|
|
@@ -1129,26 +1539,27 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1129
1539
|
}
|
|
1130
1540
|
}
|
|
1131
1541
|
|
|
1132
|
-
async
|
|
1542
|
+
async _onInstall({ blocklet, context, oldBlocklet }) {
|
|
1133
1543
|
const { meta } = blocklet;
|
|
1134
1544
|
const { did, version } = meta;
|
|
1135
1545
|
logger.info('do install blocklet', { did, version });
|
|
1136
1546
|
|
|
1137
|
-
const state = await this.state.setBlockletStatus(did, BlockletStatus.installing);
|
|
1138
|
-
this.emit(BlockletEvents.statusChange, state);
|
|
1139
|
-
|
|
1140
1547
|
try {
|
|
1141
|
-
await this._installBlocklet({
|
|
1548
|
+
const installedBlocklet = await this._installBlocklet({
|
|
1142
1549
|
did,
|
|
1143
1550
|
context,
|
|
1551
|
+
oldBlocklet,
|
|
1144
1552
|
});
|
|
1145
1553
|
|
|
1146
1554
|
if (context.startImmediately) {
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1555
|
+
const missingProps = getAppMissingConfigs(installedBlocklet);
|
|
1556
|
+
if (!missingProps.length) {
|
|
1557
|
+
try {
|
|
1558
|
+
logger.info('start blocklet after installed', { did });
|
|
1559
|
+
await this.start({ did, checkHealthImmediately: true });
|
|
1560
|
+
} catch (error) {
|
|
1561
|
+
logger.warn('attempt to start immediately failed', { did, error });
|
|
1562
|
+
}
|
|
1152
1563
|
}
|
|
1153
1564
|
}
|
|
1154
1565
|
} catch (err) {
|
|
@@ -1156,12 +1567,9 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1156
1567
|
}
|
|
1157
1568
|
}
|
|
1158
1569
|
|
|
1159
|
-
async
|
|
1570
|
+
async _onUpgrade({ oldBlocklet, newBlocklet, context }) {
|
|
1160
1571
|
const { version, did } = newBlocklet.meta;
|
|
1161
|
-
logger.info(
|
|
1162
|
-
|
|
1163
|
-
const state = await this.state.setBlockletStatus(did, BlockletStatus.upgrading);
|
|
1164
|
-
this.emit(BlockletEvents.statusChange, state);
|
|
1572
|
+
logger.info('do upgrade blocklet', { did, version });
|
|
1165
1573
|
|
|
1166
1574
|
try {
|
|
1167
1575
|
await this._upgradeBlocklet({
|
|
@@ -1174,15 +1582,17 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1174
1582
|
}
|
|
1175
1583
|
}
|
|
1176
1584
|
|
|
1177
|
-
async
|
|
1585
|
+
async _onRestart({ did, context }) {
|
|
1178
1586
|
await this.stop({ did, updateStatus: false }, context);
|
|
1179
1587
|
await this.start({ did }, context);
|
|
1180
1588
|
}
|
|
1181
1589
|
|
|
1182
|
-
async
|
|
1183
|
-
const {
|
|
1590
|
+
async _onCheckIfStarted(jobInfo, { throwOnError } = {}) {
|
|
1591
|
+
const { did, context, minConsecutiveTime = 5000, timeout } = jobInfo;
|
|
1592
|
+
const blocklet = await this.getBlocklet(did);
|
|
1593
|
+
|
|
1184
1594
|
const { meta } = blocklet;
|
|
1185
|
-
const {
|
|
1595
|
+
const { name } = meta;
|
|
1186
1596
|
|
|
1187
1597
|
try {
|
|
1188
1598
|
// healthy check
|
|
@@ -1192,12 +1602,18 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1192
1602
|
const res = await this.status(did, { forceSync: true });
|
|
1193
1603
|
this.emit(BlockletEvents.started, res);
|
|
1194
1604
|
} catch (error) {
|
|
1605
|
+
const status = await states.blocklet.getBlockletStatus(did);
|
|
1606
|
+
if ([BlockletStatus.stopping, BlockletStatus.stopped].includes(status)) {
|
|
1607
|
+
logger.info(`Check blocklet healthy failing because blocklet is ${fromBlockletStatus(status)}`);
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1195
1611
|
logger.error('check blocklet if started failed', { did, name, context, timeout, error });
|
|
1196
1612
|
|
|
1197
1613
|
await this.deleteProcess({ did }, context);
|
|
1198
|
-
await
|
|
1614
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.error);
|
|
1199
1615
|
|
|
1200
|
-
this.
|
|
1616
|
+
this._createNotification(did, {
|
|
1201
1617
|
title: 'Blocklet Start Failed',
|
|
1202
1618
|
description: `Blocklet ${name} start failed: ${error.message}`,
|
|
1203
1619
|
entityType: 'blocklet',
|
|
@@ -1213,620 +1629,212 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1213
1629
|
}
|
|
1214
1630
|
}
|
|
1215
1631
|
|
|
1216
|
-
async
|
|
1217
|
-
const blockletWithEnv = await this.
|
|
1218
|
-
const blocklet = await
|
|
1219
|
-
const nodeInfo = await
|
|
1632
|
+
async _updateBlockletEnvironment(did) {
|
|
1633
|
+
const blockletWithEnv = await this.getBlocklet(did);
|
|
1634
|
+
const blocklet = await states.blocklet.getBlocklet(did);
|
|
1635
|
+
const nodeInfo = await states.node.read();
|
|
1220
1636
|
|
|
1221
|
-
const
|
|
1222
|
-
|
|
1223
|
-
|
|
1637
|
+
const appSystemEnvironments = {
|
|
1638
|
+
...getAppSystemEnvironments(blockletWithEnv, nodeInfo),
|
|
1639
|
+
...getAppOverwrittenEnvironments(blockletWithEnv, nodeInfo),
|
|
1640
|
+
};
|
|
1224
1641
|
|
|
1225
|
-
// fill environments to blocklet and
|
|
1642
|
+
// fill environments to blocklet and components
|
|
1226
1643
|
blocklet.environments = formatEnvironments({
|
|
1227
|
-
...
|
|
1228
|
-
...
|
|
1229
|
-
...rootSystemEnvironments,
|
|
1230
|
-
...overwrittenEnvironments,
|
|
1644
|
+
...getComponentSystemEnvironments(blockletWithEnv),
|
|
1645
|
+
...appSystemEnvironments,
|
|
1231
1646
|
});
|
|
1232
1647
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1648
|
+
const envMap = {};
|
|
1649
|
+
forEachBlockletSync(blockletWithEnv, (child, { ancestors }) => {
|
|
1650
|
+
const id = getComponentId(child, ancestors);
|
|
1651
|
+
envMap[id] = child;
|
|
1652
|
+
});
|
|
1653
|
+
|
|
1654
|
+
forEachChildSync(blocklet, (child, { ancestors }) => {
|
|
1655
|
+
const id = getComponentId(child, ancestors);
|
|
1656
|
+
|
|
1657
|
+
const childWithEnv = envMap[id];
|
|
1235
1658
|
if (childWithEnv) {
|
|
1236
|
-
const childConfig = childWithEnv.configObj;
|
|
1237
1659
|
child.environments = formatEnvironments({
|
|
1238
|
-
...
|
|
1239
|
-
...
|
|
1240
|
-
...getSystemEnvironments(childWithEnv), // system env of child blocklet
|
|
1241
|
-
...rootSystemEnvironments, // system env of root blocklet
|
|
1242
|
-
...overwrittenEnvironments,
|
|
1660
|
+
...getComponentSystemEnvironments(childWithEnv), // system env of child blocklet
|
|
1661
|
+
...appSystemEnvironments, // system env of root blocklet
|
|
1243
1662
|
});
|
|
1244
1663
|
}
|
|
1245
|
-
}
|
|
1664
|
+
});
|
|
1246
1665
|
|
|
1247
|
-
//
|
|
1248
|
-
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
async updateAllBlockletEnvironment() {
|
|
1252
|
-
const blocklets = await this.state.getBlocklets();
|
|
1253
|
-
for (let i = 0; i < blocklets.length; i++) {
|
|
1254
|
-
const blocklet = blocklets[i];
|
|
1255
|
-
await this.updateBlockletEnvironment(blocklet.meta.did);
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
async _installFromRegistry({ did, registry }, context) {
|
|
1260
|
-
logger.debug('start install blocklet', { did });
|
|
1261
|
-
if (!isValidDid(did)) {
|
|
1262
|
-
throw new Error('Blocklet did is invalid');
|
|
1263
|
-
}
|
|
1666
|
+
// put BLOCKLET_APP_ID at root level for indexing
|
|
1667
|
+
blocklet.appDid = appSystemEnvironments.BLOCKLET_APP_ID;
|
|
1264
1668
|
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
const blocklet = await this.registry.getBlocklet(did, registryUrl);
|
|
1268
|
-
if (!blocklet) {
|
|
1269
|
-
throw new Error('Can not install blocklet that not found in registry');
|
|
1669
|
+
if (!Array.isArray(blocklet.migratedFrom)) {
|
|
1670
|
+
blocklet.migratedFrom = [];
|
|
1270
1671
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
throw new Error('Can not install an already installed blocklet');
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
if (isFreeBlocklet(blocklet) === false && !context.blockletPurchaseVerified) {
|
|
1278
|
-
throw new Error('Can not install a non-free blocklet directly');
|
|
1672
|
+
// This can only be set once, can be used for indexing, will not change ever
|
|
1673
|
+
if (!blocklet.appPid) {
|
|
1674
|
+
blocklet.appPid = appSystemEnvironments.BLOCKLET_APP_PID;
|
|
1279
1675
|
}
|
|
1280
1676
|
|
|
1281
|
-
//
|
|
1282
|
-
return
|
|
1283
|
-
meta: blocklet,
|
|
1284
|
-
source: BlockletSource.registry,
|
|
1285
|
-
deployedFrom: info.cdnUrl || registryUrl,
|
|
1286
|
-
context,
|
|
1287
|
-
});
|
|
1677
|
+
// update state to db
|
|
1678
|
+
return states.blocklet.updateBlocklet(did, blocklet);
|
|
1288
1679
|
}
|
|
1289
1680
|
|
|
1290
|
-
async
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
const meta = await getBlockletMetaFromUrl(url);
|
|
1294
|
-
|
|
1295
|
-
if (!meta) {
|
|
1296
|
-
throw new Error(`Can not install blocklet that not found by url: ${url}`);
|
|
1681
|
+
async _attachRuntimeInfo({ did, nodeInfo, diskInfo = true, context, cachedBlocklet }) {
|
|
1682
|
+
if (!did) {
|
|
1683
|
+
throw new Error('did should not be empty');
|
|
1297
1684
|
}
|
|
1298
1685
|
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
if (exist) {
|
|
1302
|
-
return this._upgrade({
|
|
1303
|
-
meta,
|
|
1304
|
-
source: BlockletSource.url,
|
|
1305
|
-
deployedFrom: url,
|
|
1306
|
-
sync,
|
|
1307
|
-
context,
|
|
1308
|
-
});
|
|
1309
|
-
}
|
|
1686
|
+
try {
|
|
1687
|
+
const blocklet = await this.getBlocklet(did, { throwOnNotExist: false });
|
|
1310
1688
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
source: BlockletSource.url,
|
|
1315
|
-
deployedFrom: url,
|
|
1316
|
-
sync,
|
|
1317
|
-
context,
|
|
1318
|
-
});
|
|
1319
|
-
}
|
|
1689
|
+
if (!blocklet) {
|
|
1690
|
+
return null;
|
|
1691
|
+
}
|
|
1320
1692
|
|
|
1321
|
-
|
|
1322
|
-
logger.info('install blocklet', { from: 'upload file' });
|
|
1323
|
-
// download
|
|
1324
|
-
// const { filename, mimetype, encoding, createReadStream } = await file;
|
|
1325
|
-
const { filename, createReadStream } = await file;
|
|
1326
|
-
const cwd = path.join(this.dataDirs.tmp, 'download');
|
|
1327
|
-
const tarFile = path.join(cwd, `${path.basename(filename, path.extname(filename))}.tgz`);
|
|
1328
|
-
await fs.ensureDir(cwd);
|
|
1329
|
-
await new Promise((resolve, reject) => {
|
|
1330
|
-
const readStream = createReadStream();
|
|
1331
|
-
const writeStream = fs.createWriteStream(tarFile);
|
|
1332
|
-
readStream
|
|
1333
|
-
.pipe(new Throttle({ rate: 1024 * 1024 * 20 })) // 20MB
|
|
1334
|
-
.pipe(writeStream);
|
|
1335
|
-
readStream.on('error', (error) => {
|
|
1336
|
-
logger.error('File upload read stream failed', { error });
|
|
1337
|
-
writeStream.destroy(new Error('File upload read stream failed'));
|
|
1338
|
-
});
|
|
1339
|
-
writeStream.on('error', (error) => {
|
|
1340
|
-
logger.error('File upload write stream failed', { error });
|
|
1341
|
-
fs.removeSync(tarFile);
|
|
1342
|
-
reject(error);
|
|
1343
|
-
});
|
|
1344
|
-
writeStream.on('finish', resolve);
|
|
1345
|
-
});
|
|
1693
|
+
const fromCache = !!cachedBlocklet;
|
|
1346
1694
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
}
|
|
1353
|
-
if (oldBlocklet.meta.version !== diffVersion) {
|
|
1354
|
-
logger.error('Diff deploy: Blocklet version changed', {
|
|
1355
|
-
preVersion: diffVersion,
|
|
1356
|
-
changedVersion: oldBlocklet.meta.version,
|
|
1357
|
-
name: oldBlocklet.meta.name,
|
|
1358
|
-
did: oldBlocklet.meta.did,
|
|
1695
|
+
// if from cached data, only use cache data of runtime info (engine, diskInfo, runtimeInfo...)
|
|
1696
|
+
if (fromCache) {
|
|
1697
|
+
const cached = {};
|
|
1698
|
+
forEachBlockletSync(cachedBlocklet, (component, { id }) => {
|
|
1699
|
+
cached[id] = component;
|
|
1359
1700
|
});
|
|
1360
|
-
throw new Error('Blocklet version changed when diff deploying');
|
|
1361
|
-
}
|
|
1362
|
-
if (isInProgress(oldBlocklet.status)) {
|
|
1363
|
-
logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
|
|
1364
|
-
throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
const { meta } = await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldBlocklet);
|
|
1368
|
-
const childrenMeta = await getChildrenMeta(meta);
|
|
1369
|
-
mergeMeta(meta, childrenMeta);
|
|
1370
|
-
const newBlocklet = await this.state.getBlocklet(did);
|
|
1371
|
-
newBlocklet.meta = meta;
|
|
1372
|
-
newBlocklet.source = BlockletSource.upload;
|
|
1373
|
-
newBlocklet.deployedFrom = `Upload by ${context.user.did}`;
|
|
1374
|
-
newBlocklet.children = await this.state.getChildrenFromMetas(childrenMeta);
|
|
1375
|
-
await validateBlocklet(newBlocklet);
|
|
1376
|
-
await this._downloadBlocklet(newBlocklet, oldBlocklet);
|
|
1377
|
-
|
|
1378
|
-
return this._upgradeBlocklet({
|
|
1379
|
-
oldBlocklet,
|
|
1380
|
-
newBlocklet,
|
|
1381
|
-
context,
|
|
1382
|
-
});
|
|
1383
|
-
}
|
|
1384
1701
|
|
|
1385
|
-
|
|
1386
|
-
const { meta } = await this._resolveDownload(cwd, tarFile);
|
|
1387
|
-
const oldBlocklet = await this.state.getBlocklet(meta.did);
|
|
1702
|
+
Object.assign(blocklet, pick(cachedBlocklet, ['appRuntimeInfo', 'diskInfo']));
|
|
1388
1703
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1704
|
+
forEachBlockletSync(blocklet, (component, { id }) => {
|
|
1705
|
+
if (cached[id]) {
|
|
1706
|
+
Object.assign(component, pick(cached[id], ['runtimeInfo']));
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1393
1709
|
}
|
|
1394
1710
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
await validateBlocklet(newBlocklet);
|
|
1404
|
-
await this._downloadBlocklet(newBlocklet, oldBlocklet);
|
|
1405
|
-
|
|
1406
|
-
return this._upgradeBlocklet({
|
|
1407
|
-
oldBlocklet,
|
|
1408
|
-
newBlocklet,
|
|
1409
|
-
context,
|
|
1410
|
-
});
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
const childrenMeta = await getChildrenMeta(meta);
|
|
1414
|
-
mergeMeta(meta, childrenMeta);
|
|
1415
|
-
const blocklet = await this.state.addBlocklet({
|
|
1416
|
-
did: meta.did,
|
|
1417
|
-
meta,
|
|
1418
|
-
source: BlockletSource.upload,
|
|
1419
|
-
deployedFrom: `Upload by ${context.user.did}`,
|
|
1420
|
-
childrenMeta,
|
|
1421
|
-
});
|
|
1422
|
-
|
|
1423
|
-
await validateBlocklet(blocklet);
|
|
1424
|
-
|
|
1425
|
-
await this._setConfigs(meta.did);
|
|
1426
|
-
|
|
1427
|
-
// download
|
|
1428
|
-
await this._downloadBlocklet(blocklet);
|
|
1429
|
-
return this._installBlocklet({
|
|
1430
|
-
did: meta.did,
|
|
1431
|
-
context,
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
/**
|
|
1436
|
-
* add to download job queue
|
|
1437
|
-
* @param {string} did blocklet did
|
|
1438
|
-
* @param {object} blocklet object
|
|
1439
|
-
*/
|
|
1440
|
-
async download(did, blocklet) {
|
|
1441
|
-
await this.state.setBlockletStatus(did, BlockletStatus.waiting);
|
|
1442
|
-
this.installQueue.push(
|
|
1443
|
-
{
|
|
1444
|
-
entity: 'blocklet',
|
|
1445
|
-
action: 'download',
|
|
1446
|
-
id: did,
|
|
1447
|
-
blocklet: { ...blocklet },
|
|
1448
|
-
postAction: 'install',
|
|
1449
|
-
},
|
|
1450
|
-
did
|
|
1451
|
-
);
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
async getStatus(did) {
|
|
1455
|
-
if (!did) {
|
|
1456
|
-
throw new Error('did is required');
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
const blocklet = await this.state.findOne({ 'meta.did': did }, { meta: 1, status: 1 });
|
|
1460
|
-
if (!blocklet) {
|
|
1461
|
-
return null;
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
return { name: blocklet.meta.name, did: blocklet.meta.did, status: blocklet.status };
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
async prune() {
|
|
1468
|
-
const blocklets = await this.state.getBlocklets();
|
|
1469
|
-
await pruneBlockletBundle(blocklets, this.dataDirs.blocklets);
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
async getLatestBlockletVersion({ did, version }) {
|
|
1473
|
-
const blocklet = await this.state.getBlocklet(did);
|
|
1474
|
-
if (!blocklet) {
|
|
1475
|
-
throw new Error('the blocklet is not installed');
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
let versions = this.cachedBlockletVersions.get(did);
|
|
1711
|
+
// 处理 domainAliases#value SLOT_FOR_IP_DNS_SITE
|
|
1712
|
+
if (blocklet?.site?.domainAliases?.length) {
|
|
1713
|
+
const nodeIp = await getAccessibleExternalNodeIp(nodeInfo);
|
|
1714
|
+
blocklet.site.domainAliases = blocklet.site.domainAliases.map((x) => ({
|
|
1715
|
+
...x,
|
|
1716
|
+
value: util.replaceDomainSlot({ domain: x.value, context, nodeIp }),
|
|
1717
|
+
}));
|
|
1718
|
+
}
|
|
1479
1719
|
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
const tasks = blockletRegistryList.map((registry) =>
|
|
1483
|
-
this.registry
|
|
1484
|
-
.getBlockletMeta({ did, registryUrl: registry.url })
|
|
1485
|
-
.then((item) => ({ did, version: item.version, registryUrl: registry.url }))
|
|
1486
|
-
.catch((error) => {
|
|
1487
|
-
if (error.response && error.response.status === 404) {
|
|
1488
|
-
return;
|
|
1489
|
-
}
|
|
1490
|
-
logger.error('get blocklet meta from registry failed', { did, error });
|
|
1491
|
-
})); // prettier-ignore
|
|
1720
|
+
// app runtime info, app status
|
|
1721
|
+
blocklet.appRuntimeInfo = this.runtimeMonitor.getRuntimeInfo(blocklet.meta.did);
|
|
1492
1722
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1723
|
+
if (!fromCache) {
|
|
1724
|
+
// app disk info, component runtime info, component status, component engine
|
|
1725
|
+
await forEachBlocklet(blocklet, async (component, { level }) => {
|
|
1726
|
+
component.engine = getEngine(getBlockletEngineNameByPlatform(component.meta)).describe();
|
|
1496
1727
|
|
|
1497
|
-
|
|
1728
|
+
if (level === 0) {
|
|
1729
|
+
component.diskInfo = await getDiskInfo(component, {
|
|
1730
|
+
useFakeDiskInfo: !diskInfo,
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1498
1733
|
|
|
1499
|
-
|
|
1500
|
-
return null;
|
|
1501
|
-
}
|
|
1734
|
+
component.runtimeInfo = this.runtimeMonitor.getRuntimeInfo(blocklet.meta.did, component.env.id);
|
|
1502
1735
|
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1736
|
+
if (component.runtimeInfo?.status && shouldUpdateBlockletStatus(component.status)) {
|
|
1737
|
+
component.status = statusMap[component.runtimeInfo.status];
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1507
1740
|
}
|
|
1508
|
-
});
|
|
1509
|
-
|
|
1510
|
-
return latestBlockletVersion;
|
|
1511
|
-
}
|
|
1512
1741
|
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
},
|
|
1525
|
-
{
|
|
1526
|
-
name: 'refresh-accessible-ip',
|
|
1527
|
-
time: '0 */10 * * * *', // 10min
|
|
1528
|
-
fn: async () => {
|
|
1529
|
-
const nodeInfo = await this.node.read();
|
|
1530
|
-
await refreshAccessibleExternalNodeIp(nodeInfo);
|
|
1531
|
-
},
|
|
1532
|
-
},
|
|
1533
|
-
];
|
|
1742
|
+
return blocklet;
|
|
1743
|
+
} catch (err) {
|
|
1744
|
+
const simpleState = await states.blocklet.getBlocklet(did);
|
|
1745
|
+
logger.error('failed to get blocklet info', {
|
|
1746
|
+
did,
|
|
1747
|
+
name: get(simpleState, 'meta.name'),
|
|
1748
|
+
status: get(simpleState, 'status'),
|
|
1749
|
+
error: err,
|
|
1750
|
+
});
|
|
1751
|
+
return simpleState;
|
|
1752
|
+
}
|
|
1534
1753
|
}
|
|
1535
1754
|
|
|
1536
1755
|
async _syncBlockletStatus() {
|
|
1537
1756
|
const run = async (blocklet) => {
|
|
1538
1757
|
try {
|
|
1539
|
-
await this.status(blocklet.meta.did
|
|
1758
|
+
await this.status(blocklet.meta.did);
|
|
1540
1759
|
} catch (err) {
|
|
1541
1760
|
logger.error('sync blocklet status failed', { error: err });
|
|
1542
1761
|
}
|
|
1543
1762
|
};
|
|
1544
|
-
const blocklets = await
|
|
1763
|
+
const blocklets = await states.blocklet.getBlocklets();
|
|
1545
1764
|
blocklets.forEach(run);
|
|
1546
1765
|
}
|
|
1547
1766
|
|
|
1548
|
-
async
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
const { name, did, version } = meta;
|
|
1552
|
-
|
|
1553
|
-
const childrenMeta = await getChildrenMeta(meta);
|
|
1554
|
-
mergeMeta(meta, childrenMeta);
|
|
1555
|
-
try {
|
|
1556
|
-
const blocklet = await this.state.addBlocklet({
|
|
1557
|
-
did: meta.did,
|
|
1558
|
-
meta,
|
|
1559
|
-
source,
|
|
1560
|
-
deployedFrom,
|
|
1561
|
-
childrenMeta,
|
|
1562
|
-
});
|
|
1563
|
-
|
|
1564
|
-
await validateBlocklet(blocklet);
|
|
1565
|
-
|
|
1566
|
-
await this._setConfigs(did);
|
|
1567
|
-
|
|
1568
|
-
logger.info('blocklet added to database', { meta });
|
|
1569
|
-
|
|
1570
|
-
const blocklet1 = await this.state.setBlockletStatus(did, BlockletStatus.waiting);
|
|
1571
|
-
this.emit(BlockletEvents.added, blocklet1);
|
|
1572
|
-
|
|
1573
|
-
// download
|
|
1574
|
-
const downloadParams = {
|
|
1575
|
-
blocklet: { ...blocklet1 },
|
|
1576
|
-
context,
|
|
1577
|
-
postAction: 'install',
|
|
1578
|
-
};
|
|
1579
|
-
|
|
1580
|
-
if (sync) {
|
|
1581
|
-
await this.onDownload({ ...downloadParams, throwOnError: true });
|
|
1582
|
-
return this.state.getBlocklet(did);
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
const ticket = this.installQueue.push(
|
|
1586
|
-
{
|
|
1587
|
-
entity: 'blocklet',
|
|
1588
|
-
action: 'download',
|
|
1589
|
-
id: did,
|
|
1590
|
-
...downloadParams,
|
|
1591
|
-
},
|
|
1592
|
-
did
|
|
1593
|
-
);
|
|
1594
|
-
ticket.on('failed', async (err) => {
|
|
1595
|
-
logger.error('failed to install blocklet', { name, did, version, error: err });
|
|
1596
|
-
try {
|
|
1597
|
-
await this._delExtras(did);
|
|
1598
|
-
await this.state.deleteBlocklet(did);
|
|
1599
|
-
} catch (e) {
|
|
1600
|
-
logger.error('failed to remove blocklet on install error', { did: meta.did, error: e });
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
this.notification.create({
|
|
1604
|
-
title: 'Blocklet Install Failed',
|
|
1605
|
-
description: `Blocklet ${name}@${version} install failed with error: ${err.message || 'queue exception'}`,
|
|
1606
|
-
entityType: 'blocklet',
|
|
1607
|
-
entityId: did,
|
|
1608
|
-
severity: 'error',
|
|
1609
|
-
});
|
|
1610
|
-
});
|
|
1611
|
-
|
|
1612
|
-
return blocklet1;
|
|
1613
|
-
} catch (err) {
|
|
1614
|
-
logger.error('failed to install blocklet', { name, did, version, error: err });
|
|
1615
|
-
this.notification.create({
|
|
1616
|
-
title: 'Blocklet Install Failed',
|
|
1617
|
-
description: `Blocklet ${name}@${version} install failed with error: ${err.message || 'queue exception'}`,
|
|
1618
|
-
entityType: 'blocklet',
|
|
1619
|
-
entityId: did,
|
|
1620
|
-
severity: 'error',
|
|
1621
|
-
});
|
|
1622
|
-
|
|
1623
|
-
try {
|
|
1624
|
-
await this._rollback('install', did);
|
|
1625
|
-
} catch (e) {
|
|
1626
|
-
logger.error('failed to remove blocklet on install error', { did: meta.did, error: e });
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
throw err;
|
|
1767
|
+
async _getChildrenForInstallation(component) {
|
|
1768
|
+
if (!component) {
|
|
1769
|
+
return [];
|
|
1630
1770
|
}
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
async _upgrade({ meta, source, deployedFrom, context, sync }) {
|
|
1634
|
-
validateBlockletMeta(meta, { ensureDist: true });
|
|
1635
|
-
|
|
1636
|
-
const { name, version, did } = meta;
|
|
1637
|
-
|
|
1638
|
-
const oldBlocklet = await this.state.getBlocklet(did);
|
|
1639
|
-
|
|
1640
|
-
// NOTE: 目前的版本移除了降级通道,所以不需要考虑降级通道的情况
|
|
1641
|
-
const action = semver.gt(oldBlocklet.meta.version, version) ? 'downgrade' : 'upgrade';
|
|
1642
|
-
logger.info(`${action} blocklet`, { did, version });
|
|
1643
|
-
|
|
1644
|
-
const newBlocklet = await this.state.setBlockletStatus(did, BlockletStatus.waiting);
|
|
1645
|
-
const childrenMeta = await getChildrenMeta(meta);
|
|
1646
|
-
mergeMeta(meta, childrenMeta);
|
|
1647
|
-
newBlocklet.meta = meta;
|
|
1648
|
-
newBlocklet.source = source;
|
|
1649
|
-
newBlocklet.deployedFrom = deployedFrom;
|
|
1650
|
-
newBlocklet.children = await this.state.getChildrenFromMetas(childrenMeta);
|
|
1651
|
-
|
|
1652
|
-
await validateBlocklet(newBlocklet);
|
|
1653
|
-
|
|
1654
|
-
this.emit(BlockletEvents.statusChange, newBlocklet);
|
|
1655
1771
|
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
blocklet: { ...newBlocklet },
|
|
1660
|
-
version,
|
|
1661
|
-
context,
|
|
1662
|
-
postAction: action,
|
|
1663
|
-
};
|
|
1664
|
-
|
|
1665
|
-
if (sync) {
|
|
1666
|
-
await this.onDownload({ ...downloadParams, throwOnError: true });
|
|
1667
|
-
return this.state.getBlocklet(did);
|
|
1772
|
+
const { dynamicComponents } = await parseComponents(component);
|
|
1773
|
+
if (component.meta.group !== BlockletGroup.gateway) {
|
|
1774
|
+
dynamicComponents.unshift(component);
|
|
1668
1775
|
}
|
|
1669
1776
|
|
|
1670
|
-
const
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
action: 'download',
|
|
1674
|
-
id: did,
|
|
1675
|
-
...downloadParams,
|
|
1676
|
-
},
|
|
1677
|
-
did
|
|
1678
|
-
);
|
|
1679
|
-
|
|
1680
|
-
ticket.on('failed', async (err) => {
|
|
1681
|
-
logger.error('queue failed', { entity: 'blocklet', action, did, version, name, error: err });
|
|
1682
|
-
await this._rollback(action, did, oldBlocklet);
|
|
1683
|
-
this.emit(`blocklet.${action}.failed`, { did, version, err });
|
|
1684
|
-
this.notification.create({
|
|
1685
|
-
title: `Blocklet ${capitalize(action)} Failed`,
|
|
1686
|
-
description: `Blocklet ${name}@${version} ${action} failed with error: ${err.message || 'queue exception'}`,
|
|
1687
|
-
entityType: 'blocklet',
|
|
1688
|
-
entityId: did,
|
|
1689
|
-
severity: 'error',
|
|
1690
|
-
});
|
|
1691
|
-
});
|
|
1692
|
-
return newBlocklet;
|
|
1777
|
+
const children = filterDuplicateComponents(dynamicComponents);
|
|
1778
|
+
checkVersionCompatibility(children);
|
|
1779
|
+
return children;
|
|
1693
1780
|
}
|
|
1694
1781
|
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
let dir = tmp;
|
|
1720
|
-
const files = await asyncFs.readdir(dir);
|
|
1721
|
-
if (files.includes('package')) {
|
|
1722
|
-
dir = path.join(tmp, 'package');
|
|
1723
|
-
} else if (files.length === 1) {
|
|
1724
|
-
const d = path.join(dir, files[0]);
|
|
1725
|
-
if ((await asyncFs.stat(d)).isDirectory()) {
|
|
1726
|
-
dir = d;
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
if (fs.existsSync(path.join(dir, BLOCKLET_BUNDLE_FOLDER))) {
|
|
1731
|
-
dir = path.join(dir, BLOCKLET_BUNDLE_FOLDER);
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
logger.info('Move downloadDir to installDir', { downloadDir });
|
|
1735
|
-
await fs.move(dir, downloadDir, { overwrite: true });
|
|
1736
|
-
fs.removeSync(tmp);
|
|
1737
|
-
|
|
1738
|
-
meta = getBlockletMeta(downloadDir, { ensureMain: true });
|
|
1739
|
-
const { did, name, version } = meta;
|
|
1740
|
-
|
|
1741
|
-
// validate
|
|
1742
|
-
if (
|
|
1743
|
-
originalMeta &&
|
|
1744
|
-
(originalMeta.did !== did || originalMeta.name !== name || originalMeta.version !== version)
|
|
1745
|
-
) {
|
|
1746
|
-
logger.error('Meta has differences', { originalMeta, meta });
|
|
1747
|
-
throw new Error('There are differences between the meta from tarball file and the original meta');
|
|
1748
|
-
}
|
|
1749
|
-
await validateBlockletEntry(downloadDir, meta);
|
|
1782
|
+
async _addBlocklet({ component, mode = BLOCKLET_MODES.PRODUCTION, name, did, title, description }) {
|
|
1783
|
+
const meta = {
|
|
1784
|
+
name,
|
|
1785
|
+
did,
|
|
1786
|
+
title: title || component?.meta?.title || '',
|
|
1787
|
+
description: description || component?.meta?.description || '',
|
|
1788
|
+
version: BLOCKLET_DEFAULT_VERSION,
|
|
1789
|
+
group: BlockletGroup.gateway,
|
|
1790
|
+
interfaces: [
|
|
1791
|
+
{
|
|
1792
|
+
type: BLOCKLET_INTERFACE_TYPE_WEB,
|
|
1793
|
+
name: BLOCKLET_INTERFACE_PUBLIC,
|
|
1794
|
+
path: BLOCKLET_DEFAULT_PATH_REWRITE,
|
|
1795
|
+
prefix: BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
1796
|
+
port: BLOCKLET_DEFAULT_PORT_NAME,
|
|
1797
|
+
protocol: BLOCKLET_INTERFACE_PROTOCOL_HTTP,
|
|
1798
|
+
},
|
|
1799
|
+
],
|
|
1800
|
+
specVersion: BLOCKLET_LATEST_SPEC_VERSION,
|
|
1801
|
+
environments: component?.meta?.environments || [],
|
|
1802
|
+
timeout: {
|
|
1803
|
+
start: process.env.NODE_ENV === 'test' ? 10 : 60,
|
|
1804
|
+
},
|
|
1805
|
+
};
|
|
1750
1806
|
|
|
1751
|
-
|
|
1807
|
+
const children = component ? await this._getChildrenForInstallation(component) : [];
|
|
1752
1808
|
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
fs.removeSync(installDir);
|
|
1756
|
-
logger.info('cleanup blocklet upgrade dir', { name, version, installDir });
|
|
1757
|
-
} else {
|
|
1758
|
-
fs.mkdirSync(installDir, { recursive: true });
|
|
1759
|
-
}
|
|
1760
|
-
await fs.move(downloadDir, installDir, { overwrite: true });
|
|
1761
|
-
} catch (error) {
|
|
1762
|
-
fs.removeSync(downloadDir);
|
|
1763
|
-
fs.removeSync(tmp);
|
|
1764
|
-
throw error;
|
|
1809
|
+
if (children[0]?.meta?.logo) {
|
|
1810
|
+
meta.logo = children[0].meta.logo;
|
|
1765
1811
|
}
|
|
1766
1812
|
|
|
1767
|
-
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
async _resolveDiffDownload(cwd, tarFile, deleteSet, blocklet) {
|
|
1771
|
-
logger.info('Resolve diff download', { tarFile, cwd });
|
|
1772
|
-
const downloadDir = path.join(cwd, `${path.basename(tarFile, path.extname(tarFile))}`);
|
|
1773
|
-
const diffDir = `${downloadDir}-diff`;
|
|
1774
|
-
try {
|
|
1775
|
-
await expandTarball({ source: tarFile, dest: diffDir, strip: 0 });
|
|
1776
|
-
fs.removeSync(tarFile);
|
|
1777
|
-
} catch (error) {
|
|
1778
|
-
fs.removeSync(tarFile);
|
|
1779
|
-
logger.error('expand blocklet tar file error');
|
|
1780
|
-
throw error;
|
|
1781
|
-
}
|
|
1782
|
-
logger.info('Copy installDir to downloadDir', { installDir: this.installDir, downloadDir });
|
|
1783
|
-
await fs.copy(path.join(this.installDir, blocklet.meta.name, blocklet.meta.version), downloadDir);
|
|
1784
|
-
try {
|
|
1785
|
-
// delete
|
|
1786
|
-
logger.info('Delete files from downloadDir', { fileNum: deleteSet.length });
|
|
1787
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
1788
|
-
for (const file of deleteSet) {
|
|
1789
|
-
await fs.remove(path.join(downloadDir, file));
|
|
1790
|
-
}
|
|
1791
|
-
// walk & cover
|
|
1792
|
-
logger.info('Move files from diffDir to downloadDir', { diffDir, downloadDir });
|
|
1793
|
-
const walkDiff = async (dir) => {
|
|
1794
|
-
const files = await asyncFs.readdir(dir);
|
|
1795
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
1796
|
-
for (const file of files) {
|
|
1797
|
-
const p = path.join(dir, file);
|
|
1798
|
-
const stat = await asyncFs.stat(p);
|
|
1799
|
-
if (stat.isDirectory()) {
|
|
1800
|
-
await walkDiff(p);
|
|
1801
|
-
} else if (stat.isFile()) {
|
|
1802
|
-
await fs.move(p, path.join(downloadDir, path.relative(diffDir, p)), { overwrite: true });
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
};
|
|
1806
|
-
await walkDiff(diffDir);
|
|
1807
|
-
fs.removeSync(diffDir);
|
|
1808
|
-
const meta = getBlockletMeta(downloadDir, { ensureMain: true });
|
|
1813
|
+
await validateBlocklet({ meta, children });
|
|
1809
1814
|
|
|
1810
|
-
|
|
1815
|
+
// fake install bundle
|
|
1816
|
+
const bundleDir = getBundleDir(this.installDir, meta);
|
|
1817
|
+
fs.mkdirSync(bundleDir, { recursive: true });
|
|
1818
|
+
updateMetaFile(path.join(bundleDir, BLOCKLET_META_FILE), meta);
|
|
1811
1819
|
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1820
|
+
// add blocklet to db
|
|
1821
|
+
const blocklet = await states.blocklet.addBlocklet({
|
|
1822
|
+
meta,
|
|
1823
|
+
source: BlockletSource.custom,
|
|
1824
|
+
children,
|
|
1825
|
+
mode,
|
|
1826
|
+
});
|
|
1815
1827
|
|
|
1816
|
-
|
|
1817
|
-
} catch (error) {
|
|
1818
|
-
fs.removeSync(downloadDir);
|
|
1819
|
-
fs.removeSync(diffDir);
|
|
1820
|
-
throw error;
|
|
1821
|
-
}
|
|
1828
|
+
return blocklet;
|
|
1822
1829
|
}
|
|
1823
1830
|
|
|
1824
1831
|
/**
|
|
1825
1832
|
* @param {string} opt.did
|
|
1826
1833
|
* @param {object} opt.context
|
|
1827
1834
|
*/
|
|
1828
|
-
async _installBlocklet({ did, context }) {
|
|
1835
|
+
async _installBlocklet({ did, oldBlocklet, context, createNotification = true }) {
|
|
1829
1836
|
try {
|
|
1837
|
+
// should ensure blocklet integrity
|
|
1830
1838
|
let blocklet = await this.ensureBlocklet(did);
|
|
1831
1839
|
const { meta, source, deployedFrom } = blocklet;
|
|
1832
1840
|
|
|
@@ -1839,58 +1847,82 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1839
1847
|
}
|
|
1840
1848
|
|
|
1841
1849
|
// pre install
|
|
1842
|
-
|
|
1843
|
-
const preInstall = (b) =>
|
|
1844
|
-
hooks.preInstall({
|
|
1845
|
-
hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
|
|
1846
|
-
env: { ...nodeEnvironments },
|
|
1847
|
-
appDir: blocklet.env.appDir,
|
|
1848
|
-
did, // root blocklet did
|
|
1849
|
-
notification: this.notification,
|
|
1850
|
-
context,
|
|
1851
|
-
});
|
|
1852
|
-
await forEachBlocklet(blocklet, preInstall, { parallel: true });
|
|
1850
|
+
await this._runPreInstallHook(blocklet, context);
|
|
1853
1851
|
|
|
1854
1852
|
// Add environments
|
|
1855
|
-
await this.
|
|
1856
|
-
blocklet = await this.
|
|
1853
|
+
await this._updateBlockletEnvironment(meta.did);
|
|
1854
|
+
blocklet = await this.getBlocklet(did);
|
|
1857
1855
|
|
|
1858
1856
|
// post install
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1857
|
+
await this._runPostInstallHook(blocklet, context);
|
|
1858
|
+
|
|
1859
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.installed);
|
|
1860
|
+
blocklet = await this.getBlocklet(did);
|
|
1861
|
+
logger.info('blocklet installed', { source, did: meta.did });
|
|
1862
|
+
|
|
1863
|
+
// logo
|
|
1864
|
+
if (blocklet.children[0]?.meta?.logo) {
|
|
1865
|
+
const fileName = blocklet.children[0].meta.logo;
|
|
1866
|
+
const src = path.join(getBundleDir(this.installDir, blocklet.children[0].meta), fileName);
|
|
1867
|
+
const dist = path.join(getBundleDir(this.installDir, blocklet.meta), fileName);
|
|
1868
|
+
await fs.copy(src, dist);
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
await fs.writeFile(path.join(blocklet.env.dataDir, 'logo.svg'), createDidLogo(blocklet.meta.did));
|
|
1872
|
+
|
|
1873
|
+
// Init db
|
|
1874
|
+
await this.teamManager.initTeam(blocklet.meta.did);
|
|
1875
|
+
|
|
1876
|
+
// Update dependents
|
|
1877
|
+
await this._updateDependents(did);
|
|
1878
|
+
blocklet = await this.getBlocklet(did);
|
|
1879
|
+
|
|
1880
|
+
this.emit(BlockletEvents.installed, { blocklet, context });
|
|
1881
|
+
|
|
1882
|
+
// Update dynamic component meta in blocklet settings
|
|
1883
|
+
await this._ensureDeletedChildrenInSettings(blocklet);
|
|
1884
|
+
|
|
1885
|
+
if (context?.downloadTokenList?.length) {
|
|
1886
|
+
await states.blocklet.updateBlocklet(did, {
|
|
1887
|
+
tokens: {
|
|
1888
|
+
downloadTokenList: context.downloadTokenList,
|
|
1889
|
+
},
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
if (blocklet.controller && process.env.NODE_ENV !== 'test') {
|
|
1894
|
+
const nodeInfo = await states.node.read();
|
|
1895
|
+
await consumeServerlessNFT({ nftId: blocklet.controller.nftId, nodeInfo, blocklet });
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
if (createNotification) {
|
|
1899
|
+
this._createNotification(did, {
|
|
1900
|
+
title: 'Blocklet Installed',
|
|
1901
|
+
description: `Blocklet ${meta.name}@${meta.version} is installed successfully. (Source: ${
|
|
1902
|
+
deployedFrom || fromBlockletSource(source)
|
|
1903
|
+
})`,
|
|
1904
|
+
action: `/blocklets/${did}/overview`,
|
|
1905
|
+
entityType: 'blocklet',
|
|
1906
|
+
entityId: did,
|
|
1907
|
+
severity: 'success',
|
|
1867
1908
|
});
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
await this.state.setBlockletStatus(did, BlockletStatus.installed);
|
|
1871
|
-
blocklet = await this.ensureBlocklet(did);
|
|
1872
|
-
logger.info('blocklet installed', { source, meta });
|
|
1873
|
-
this.emit(BlockletEvents.installed, { blocklet, context });
|
|
1909
|
+
}
|
|
1874
1910
|
|
|
1875
|
-
this.
|
|
1876
|
-
title: 'Blocklet Installed',
|
|
1877
|
-
description: `Blocklet ${meta.name}@${meta.version} is installed successfully. (Source: ${
|
|
1878
|
-
deployedFrom || fromBlockletSource(source)
|
|
1879
|
-
})`,
|
|
1880
|
-
action: `/blocklets/${did}/overview`,
|
|
1881
|
-
entityType: 'blocklet',
|
|
1882
|
-
entityId: did,
|
|
1883
|
-
severity: 'success',
|
|
1884
|
-
});
|
|
1911
|
+
await this._rollbackCache.remove({ did: blocklet.meta.did });
|
|
1885
1912
|
return blocklet;
|
|
1886
1913
|
} catch (err) {
|
|
1887
|
-
const { meta } = await
|
|
1914
|
+
const { meta } = await states.blocklet.getBlocklet(did);
|
|
1888
1915
|
const { name, version } = meta;
|
|
1889
1916
|
logger.error('failed to install blocklet', { name, did, version, error: err });
|
|
1890
1917
|
try {
|
|
1891
|
-
await this._rollback('install', did);
|
|
1892
|
-
this.emit(BlockletEvents.installFailed, {
|
|
1893
|
-
|
|
1918
|
+
await this._rollback('install', did, oldBlocklet);
|
|
1919
|
+
this.emit(BlockletEvents.installFailed, {
|
|
1920
|
+
meta: { did },
|
|
1921
|
+
error: {
|
|
1922
|
+
message: err.message,
|
|
1923
|
+
},
|
|
1924
|
+
});
|
|
1925
|
+
this._createNotification(did, {
|
|
1894
1926
|
title: 'Blocklet Install Failed',
|
|
1895
1927
|
description: `Blocklet ${meta.name}@${meta.version} install failed with error: ${err.message}`,
|
|
1896
1928
|
entityType: 'blocklet',
|
|
@@ -1905,12 +1937,13 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1905
1937
|
}
|
|
1906
1938
|
}
|
|
1907
1939
|
|
|
1908
|
-
async _upgradeBlocklet({ newBlocklet, oldBlocklet, context }) {
|
|
1940
|
+
async _upgradeBlocklet({ newBlocklet, oldBlocklet, context = {} }) {
|
|
1909
1941
|
const { meta, source, deployedFrom, children } = newBlocklet;
|
|
1910
1942
|
const { did, version, name } = meta;
|
|
1911
1943
|
|
|
1912
|
-
|
|
1913
|
-
|
|
1944
|
+
// ids
|
|
1945
|
+
context.skippedProcessIds = getSkippedProcessIds({ newBlocklet, oldBlocklet, context });
|
|
1946
|
+
|
|
1914
1947
|
try {
|
|
1915
1948
|
// delete old process
|
|
1916
1949
|
try {
|
|
@@ -1921,60 +1954,38 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1921
1954
|
}
|
|
1922
1955
|
|
|
1923
1956
|
// update state
|
|
1924
|
-
await
|
|
1925
|
-
await this.
|
|
1957
|
+
await states.blocklet.upgradeBlocklet({ meta, source, deployedFrom, children });
|
|
1958
|
+
await this._setConfigsFromMeta(did);
|
|
1926
1959
|
|
|
1960
|
+
// should ensure blocklet integrity
|
|
1927
1961
|
let blocklet = await this.ensureBlocklet(did);
|
|
1928
1962
|
|
|
1929
1963
|
// pre install
|
|
1930
|
-
|
|
1931
|
-
const preInstall = (b) =>
|
|
1932
|
-
hooks.preInstall({
|
|
1933
|
-
hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
|
|
1934
|
-
env: { ...nodeEnvironments },
|
|
1935
|
-
appDir: blocklet.env.appDir,
|
|
1936
|
-
did, // root blocklet did
|
|
1937
|
-
notification: this.notification,
|
|
1938
|
-
context,
|
|
1939
|
-
});
|
|
1940
|
-
await forEachBlocklet(blocklet, preInstall, { parallel: true });
|
|
1964
|
+
await this._runPreInstallHook(blocklet, context);
|
|
1941
1965
|
|
|
1942
1966
|
// Add environments
|
|
1943
|
-
await this.
|
|
1944
|
-
blocklet = await this.
|
|
1967
|
+
await this._updateBlockletEnvironment(did);
|
|
1968
|
+
blocklet = await this.getBlocklet(did);
|
|
1945
1969
|
|
|
1946
1970
|
// post install
|
|
1947
|
-
|
|
1948
|
-
hooks.postInstall({
|
|
1949
|
-
hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
|
|
1950
|
-
env: getRuntimeEnvironments(b, nodeEnvironments),
|
|
1951
|
-
appDir: b.env.appDir,
|
|
1952
|
-
did, // root blocklet did
|
|
1953
|
-
notification: this.notification,
|
|
1954
|
-
context,
|
|
1955
|
-
});
|
|
1956
|
-
await forEachBlocklet(blocklet, postInstall, { parallel: true });
|
|
1971
|
+
await this._runPostInstallHook(blocklet, context);
|
|
1957
1972
|
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1973
|
+
logger.info('start migration');
|
|
1974
|
+
try {
|
|
1975
|
+
const oldVersions = {};
|
|
1976
|
+
forEachBlockletSync(oldBlocklet, (b, { id }) => {
|
|
1977
|
+
oldVersions[id] = b.meta.version;
|
|
1978
|
+
});
|
|
1979
|
+
const nodeEnvironments = await states.node.getEnvironments();
|
|
1980
|
+
const runMigration = (b, { id, ancestors }) => {
|
|
1962
1981
|
return runMigrationScripts({
|
|
1963
1982
|
blocklet: b,
|
|
1964
|
-
oldVersion,
|
|
1965
|
-
newVersion: version,
|
|
1966
|
-
env: getRuntimeEnvironments(b, nodeEnvironments),
|
|
1967
1983
|
appDir: b.env.appDir,
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1984
|
+
env: getRuntimeEnvironments(b, nodeEnvironments, ancestors),
|
|
1985
|
+
oldVersion: oldVersions[id],
|
|
1986
|
+
newVersion: b.meta.version,
|
|
1971
1987
|
});
|
|
1972
|
-
}
|
|
1973
|
-
return Promise.resolve();
|
|
1974
|
-
};
|
|
1975
|
-
logger.info('start migration');
|
|
1976
|
-
|
|
1977
|
-
try {
|
|
1988
|
+
};
|
|
1978
1989
|
await forEachBlocklet(blocklet, runMigration, { parallel: true });
|
|
1979
1990
|
} catch (error) {
|
|
1980
1991
|
logger.error('Failed to migrate blocklet', { did, error });
|
|
@@ -1984,28 +1995,29 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1984
1995
|
|
|
1985
1996
|
logger.info('updated blocklet for upgrading', { did, version, source, name });
|
|
1986
1997
|
|
|
1998
|
+
const status =
|
|
1999
|
+
oldBlocklet.status === BlockletStatus.installed ? BlockletStatus.installed : BlockletStatus.stopped;
|
|
2000
|
+
await states.blocklet.setBlockletStatus(did, status, { children: 'all' });
|
|
2001
|
+
|
|
1987
2002
|
// start new process
|
|
1988
2003
|
if (oldBlocklet.status === BlockletStatus.running) {
|
|
1989
2004
|
await this.start({ did }, context);
|
|
1990
2005
|
logger.info('started blocklet for upgrading', { did, version });
|
|
1991
|
-
} else {
|
|
1992
|
-
const status =
|
|
1993
|
-
oldBlocklet.status === BlockletStatus.installed ? BlockletStatus.installed : BlockletStatus.stopped;
|
|
1994
|
-
await this.state.setBlockletStatus(did, status);
|
|
1995
2006
|
}
|
|
1996
2007
|
|
|
1997
|
-
blocklet = await this.
|
|
2008
|
+
blocklet = await this.getBlocklet(did, context);
|
|
2009
|
+
|
|
2010
|
+
await fs.writeFile(path.join(blocklet.env.dataDir, 'logo.svg'), createDidLogo(blocklet.meta.did));
|
|
2011
|
+
|
|
2012
|
+
await this._updateDependents(did);
|
|
2013
|
+
|
|
1998
2014
|
this.refreshListCache();
|
|
1999
2015
|
|
|
2000
2016
|
try {
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
this.emit(eventNames[action], { blocklet, context });
|
|
2006
|
-
this.notification.create({
|
|
2007
|
-
title: `Blocklet ${capitalize(action)} Success`,
|
|
2008
|
-
description: `Blocklet ${name}@${version} ${action} successfully. (Source: ${
|
|
2017
|
+
this.emit(BlockletEvents.upgraded, { blocklet, context });
|
|
2018
|
+
this._createNotification(did, {
|
|
2019
|
+
title: 'Blocklet Upgrade Success',
|
|
2020
|
+
description: `Blocklet ${name}@${version} upgrade successfully. (Source: ${
|
|
2009
2021
|
deployedFrom || fromBlockletSource(source)
|
|
2010
2022
|
})`,
|
|
2011
2023
|
action: `/blocklets/${did}/overview`,
|
|
@@ -2017,14 +2029,26 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2017
2029
|
logger.error('emit upgrade notification failed', { name, version, error });
|
|
2018
2030
|
}
|
|
2019
2031
|
|
|
2032
|
+
// Update dynamic component meta in blocklet settings
|
|
2033
|
+
await this._ensureDeletedChildrenInSettings(blocklet);
|
|
2034
|
+
|
|
2035
|
+
await this._rollbackCache.remove({ did: blocklet.meta.did });
|
|
2036
|
+
|
|
2020
2037
|
return blocklet;
|
|
2021
2038
|
} catch (err) {
|
|
2022
|
-
const b = await this._rollback(
|
|
2023
|
-
logger.error(
|
|
2024
|
-
|
|
2025
|
-
this.
|
|
2026
|
-
|
|
2027
|
-
|
|
2039
|
+
const b = await this._rollback('upgrade', did, oldBlocklet);
|
|
2040
|
+
logger.error('failed to upgrade blocklet', { did, version, name, error: err });
|
|
2041
|
+
|
|
2042
|
+
this.emit(BlockletEvents.updated, b);
|
|
2043
|
+
|
|
2044
|
+
this.emit(BlockletEvents.upgradeFailed, {
|
|
2045
|
+
blocklet: { ...oldBlocklet, error: { message: err.message } },
|
|
2046
|
+
context,
|
|
2047
|
+
});
|
|
2048
|
+
|
|
2049
|
+
this._createNotification(did, {
|
|
2050
|
+
title: 'Blocklet Upgrade Failed',
|
|
2051
|
+
description: `Blocklet ${name}@${version} upgrade failed with error: ${err.message}`,
|
|
2028
2052
|
entityType: 'blocklet',
|
|
2029
2053
|
entityId: did,
|
|
2030
2054
|
severity: 'error',
|
|
@@ -2033,331 +2057,420 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2033
2057
|
}
|
|
2034
2058
|
}
|
|
2035
2059
|
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
* for cancel control: ctrlStore, rootDid
|
|
2040
|
-
*/
|
|
2041
|
-
async _downloadTarball({ url, cwd, tarball, did, integrity, verify = true, ctrlStore = {}, rootDid }) {
|
|
2042
|
-
fs.mkdirSync(cwd, { recursive: true });
|
|
2060
|
+
// Refresh deleted component in blocklet settings
|
|
2061
|
+
async _ensureDeletedChildrenInSettings(blocklet) {
|
|
2062
|
+
const { did } = blocklet.meta;
|
|
2043
2063
|
|
|
2044
|
-
|
|
2064
|
+
// TODO 不从 settings 中取值, 直接存在 extra 中
|
|
2065
|
+
let deletedChildren = await states.blockletExtras.getSettings(did, 'children', []);
|
|
2066
|
+
deletedChildren = deletedChildren.filter(
|
|
2067
|
+
(x) => x.status === BlockletStatus.deleted && !blocklet.children.some((y) => y.meta.did === x.meta.did)
|
|
2068
|
+
);
|
|
2045
2069
|
|
|
2046
|
-
|
|
2070
|
+
await states.blockletExtras.setSettings(did, { children: deletedChildren });
|
|
2071
|
+
}
|
|
2047
2072
|
|
|
2048
|
-
|
|
2073
|
+
/**
|
|
2074
|
+
*
|
|
2075
|
+
*
|
|
2076
|
+
* @param {{}} blocklet
|
|
2077
|
+
* @param {{}} [context={}]
|
|
2078
|
+
* @return {*}
|
|
2079
|
+
* @memberof BlockletManager
|
|
2080
|
+
*/
|
|
2081
|
+
async _downloadBlocklet(blocklet, context = {}) {
|
|
2082
|
+
const {
|
|
2083
|
+
meta: { did },
|
|
2084
|
+
} = blocklet;
|
|
2049
2085
|
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2086
|
+
return this.blockletDownloader.download(blocklet, {
|
|
2087
|
+
...context,
|
|
2088
|
+
preDownload: async ({ downloadComponentIds }) => {
|
|
2089
|
+
// update children status
|
|
2090
|
+
const blocklet1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.downloading, {
|
|
2091
|
+
children: downloadComponentIds,
|
|
2092
|
+
});
|
|
2093
|
+
this.emit(BlockletEvents.statusChange, blocklet1);
|
|
2094
|
+
},
|
|
2095
|
+
postDownload: async ({ isCancelled }) => {
|
|
2096
|
+
if (!isCancelled) {
|
|
2097
|
+
// since preferences only exist in blocklet bundle, we need to populate then after downloaded
|
|
2098
|
+
await this._setConfigsFromMeta(did);
|
|
2099
|
+
}
|
|
2100
|
+
},
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2058
2103
|
|
|
2059
|
-
|
|
2060
|
-
|
|
2104
|
+
async _syncPm2Status(pm2Status, did) {
|
|
2105
|
+
try {
|
|
2106
|
+
const state = await states.blocklet.getBlocklet(did);
|
|
2107
|
+
if (state && util.shouldUpdateBlockletStatus(state.status)) {
|
|
2108
|
+
const newStatus = pm2StatusMap[pm2Status];
|
|
2109
|
+
await states.blocklet.setBlockletStatus(did, newStatus);
|
|
2110
|
+
logger.info('sync pm2 status to blocklet', { did, pm2Status, newStatus });
|
|
2061
2111
|
}
|
|
2062
|
-
|
|
2112
|
+
} catch (error) {
|
|
2113
|
+
logger.error('sync pm2 status to blocklet failed', { did, pm2Status, error });
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2063
2116
|
|
|
2064
|
-
|
|
2117
|
+
/**
|
|
2118
|
+
* @param {string} action install, upgrade, downgrade
|
|
2119
|
+
* @param {string} did
|
|
2120
|
+
* @param {object} oldBlocklet
|
|
2121
|
+
*/
|
|
2122
|
+
async _rollback(action, did, oldBlocklet) {
|
|
2123
|
+
if (action === 'install') {
|
|
2124
|
+
const extraState = oldBlocklet?.extraState;
|
|
2065
2125
|
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
}
|
|
2126
|
+
// rollback blocklet extra state
|
|
2127
|
+
if (extraState) {
|
|
2128
|
+
await states.blockletExtras.update({ did }, extraState);
|
|
2129
|
+
} else {
|
|
2130
|
+
await states.blockletExtras.remove({ did });
|
|
2071
2131
|
}
|
|
2072
2132
|
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
}
|
|
2133
|
+
// remove blocklet state
|
|
2134
|
+
return this._deleteBlocklet({ did, keepData: true });
|
|
2076
2135
|
}
|
|
2077
2136
|
|
|
2078
|
-
if (
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
}
|
|
2137
|
+
if (['upgrade', 'downgrade'].includes(action)) {
|
|
2138
|
+
const { extraState, ...blocklet } = oldBlocklet;
|
|
2139
|
+
// rollback blocklet state
|
|
2140
|
+
const result = await states.blocklet.updateBlocklet(did, blocklet);
|
|
2141
|
+
|
|
2142
|
+
// rollback blocklet extra state
|
|
2143
|
+
await states.blockletExtras.update({ did: blocklet.meta.did }, extraState);
|
|
2144
|
+
|
|
2145
|
+
logger.info('blocklet rollback successfully', { did });
|
|
2146
|
+
this.emit(BlockletEvents.updated, result);
|
|
2147
|
+
return result;
|
|
2085
2148
|
}
|
|
2086
2149
|
|
|
2087
|
-
|
|
2150
|
+
logger.error('rollback action is invalid', { action });
|
|
2151
|
+
throw new Error(`rollback action is invalid: ${action}`);
|
|
2088
2152
|
}
|
|
2089
2153
|
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
await
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2154
|
+
async _deleteBlocklet({ did, keepData, keepLogsDir, keepConfigs }, context) {
|
|
2155
|
+
const blocklet = await states.blocklet.getBlocklet(did);
|
|
2156
|
+
const { name } = blocklet.meta;
|
|
2157
|
+
const cacheDir = path.join(this.dataDirs.cache, name);
|
|
2158
|
+
|
|
2159
|
+
// Cleanup db
|
|
2160
|
+
await this.teamManager.deleteTeam(blocklet.meta.did);
|
|
2161
|
+
|
|
2162
|
+
// Cleanup disk storage
|
|
2163
|
+
fs.removeSync(cacheDir);
|
|
2164
|
+
await this._cleanBlockletData({ blocklet, keepData, keepLogsDir, keepConfigs });
|
|
2165
|
+
|
|
2166
|
+
if (blocklet.mode !== BLOCKLET_MODES.DEVELOPMENT) {
|
|
2167
|
+
const nodeInfo = await states.node.read();
|
|
2168
|
+
const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
2169
|
+
didDocument
|
|
2170
|
+
.disableDNS({ wallet, didRegistryUrl: nodeInfo.didRegistry })
|
|
2171
|
+
.then(() => {
|
|
2172
|
+
logger.info(`disabled blocklet ${blocklet.appDid} dns`);
|
|
2173
|
+
})
|
|
2174
|
+
.catch((err) => {
|
|
2175
|
+
logger.error(`disable blocklet ${blocklet.appDid} dns failed`, { error: err });
|
|
2176
|
+
});
|
|
2113
2177
|
}
|
|
2114
2178
|
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
if (cacheList.length > 10) {
|
|
2118
|
-
// find and remove
|
|
2119
|
-
let minIndex = 0;
|
|
2120
|
-
let min = cacheList[0];
|
|
2121
|
-
cacheList.forEach((x, i) => {
|
|
2122
|
-
if (x.accessAt < min.accessAt) {
|
|
2123
|
-
minIndex = i;
|
|
2124
|
-
min = x;
|
|
2125
|
-
}
|
|
2126
|
-
});
|
|
2179
|
+
const result = await states.blocklet.deleteBlocklet(did);
|
|
2180
|
+
logger.info('blocklet removed successfully', { did });
|
|
2127
2181
|
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2182
|
+
let keepRouting = true;
|
|
2183
|
+
if (keepData === false || keepConfigs === false) {
|
|
2184
|
+
keepRouting = false;
|
|
2131
2185
|
}
|
|
2132
|
-
logger.info('add cache tarFile', { base58: integrity });
|
|
2133
2186
|
|
|
2134
|
-
|
|
2135
|
-
await this.cache.set(key, cacheList);
|
|
2136
|
-
}
|
|
2187
|
+
this.runtimeMonitor.delete(blocklet.meta.did);
|
|
2137
2188
|
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2189
|
+
this.emit(BlockletEvents.removed, {
|
|
2190
|
+
blocklet: result,
|
|
2191
|
+
context: {
|
|
2192
|
+
...context,
|
|
2193
|
+
keepRouting,
|
|
2194
|
+
},
|
|
2195
|
+
});
|
|
2196
|
+
return blocklet;
|
|
2197
|
+
}
|
|
2141
2198
|
|
|
2142
|
-
|
|
2143
|
-
const
|
|
2199
|
+
async _cleanBlockletData({ blocklet, keepData, keepLogsDir, keepConfigs }) {
|
|
2200
|
+
const { name } = blocklet.meta;
|
|
2144
2201
|
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
}
|
|
2202
|
+
const dataDir = path.join(this.dataDirs.data, name);
|
|
2203
|
+
const logsDir = path.join(this.dataDirs.logs, name);
|
|
2148
2204
|
|
|
2149
|
-
|
|
2150
|
-
}
|
|
2205
|
+
logger.info(`clean blocklet ${blocklet.meta.did} data`, { keepData, keepLogsDir, keepConfigs });
|
|
2151
2206
|
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
* @param {string} rootDid root blocklet did of the blocklet to be downloaded
|
|
2156
|
-
* @return {object} { isCancelled: Boolean }
|
|
2157
|
-
*/
|
|
2158
|
-
async _downloadBundle(meta, rootDid, url) {
|
|
2159
|
-
const { name, did, version, dist = {} } = meta;
|
|
2160
|
-
const { tarball, integrity } = dist;
|
|
2207
|
+
if (keepData === false) {
|
|
2208
|
+
fs.removeSync(dataDir);
|
|
2209
|
+
logger.info(`removed blocklet ${blocklet.meta.did} data dir: ${dataDir}`);
|
|
2161
2210
|
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
if (!lock) {
|
|
2165
|
-
lock = new Lock(lockName);
|
|
2166
|
-
this.downloadLocks[lockName] = lock;
|
|
2167
|
-
}
|
|
2211
|
+
fs.removeSync(logsDir);
|
|
2212
|
+
logger.info(`removed blocklet ${blocklet.meta.did} logs dir: ${logsDir}`);
|
|
2168
2213
|
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
const tarballPath = await this._downloadTarball({
|
|
2176
|
-
name,
|
|
2177
|
-
did,
|
|
2178
|
-
version,
|
|
2179
|
-
cwd,
|
|
2180
|
-
tarball,
|
|
2181
|
-
integrity,
|
|
2182
|
-
verify: true,
|
|
2183
|
-
ctrlStore: this.downloadCtrls,
|
|
2184
|
-
rootDid,
|
|
2185
|
-
url,
|
|
2186
|
-
});
|
|
2187
|
-
logger.info('downloaded blocklet tar file', { name, version, tarballPath });
|
|
2188
|
-
if (tarballPath === downloadFile.CANCEL) {
|
|
2189
|
-
lock.release();
|
|
2190
|
-
return { isCancelled: true };
|
|
2214
|
+
await states.blockletExtras.remove({ did: blocklet.meta.did });
|
|
2215
|
+
logger.info(`removed blocklet ${blocklet.meta.did} extra data`);
|
|
2216
|
+
} else {
|
|
2217
|
+
if (keepLogsDir === false) {
|
|
2218
|
+
fs.removeSync(logsDir);
|
|
2219
|
+
logger.info(`removed blocklet ${blocklet.meta.did} logs dir: ${logsDir}`);
|
|
2191
2220
|
}
|
|
2192
2221
|
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
logger.info('resolved blocklet tar file to install dir', { name, version });
|
|
2198
|
-
lock.release();
|
|
2199
|
-
return { isCancelled: false };
|
|
2200
|
-
} catch (error) {
|
|
2201
|
-
lock.release();
|
|
2202
|
-
throw error;
|
|
2222
|
+
if (keepConfigs === false) {
|
|
2223
|
+
await states.blockletExtras.remove({ did: blocklet.meta.did });
|
|
2224
|
+
logger.info(`removed blocklet ${blocklet.meta.did} extra data`);
|
|
2225
|
+
}
|
|
2203
2226
|
}
|
|
2204
2227
|
}
|
|
2205
2228
|
|
|
2206
|
-
async
|
|
2207
|
-
const {
|
|
2208
|
-
meta: { name, did },
|
|
2209
|
-
} = blocklet;
|
|
2229
|
+
async _setConfigsFromMeta(did, childDid) {
|
|
2230
|
+
const blocklet = await getBlocklet({ states, dataDirs: this.dataDirs, did });
|
|
2210
2231
|
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
get(oldBlocklet, 'meta.dist.integrity') !== get(blocklet, 'meta.dist.integrity')
|
|
2215
|
-
) {
|
|
2216
|
-
metas.push(blocklet.meta);
|
|
2217
|
-
}
|
|
2232
|
+
if (!childDid) {
|
|
2233
|
+
await forEachBlocklet(blocklet, async (b, { ancestors }) => {
|
|
2234
|
+
const environments = [...get(b.meta, 'environments', []), ...getConfigFromPreferences(b)];
|
|
2218
2235
|
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
return o;
|
|
2222
|
-
}, {});
|
|
2236
|
+
// remove default if ancestors has a value
|
|
2237
|
+
ensureEnvDefault(environments, ancestors);
|
|
2223
2238
|
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2239
|
+
// write configs to db
|
|
2240
|
+
await states.blockletExtras.setConfigs([...ancestors.map((x) => x.meta.did), b.meta.did], environments);
|
|
2241
|
+
});
|
|
2242
|
+
} else {
|
|
2243
|
+
const child = blocklet.children.find((x) => x.meta.did === childDid);
|
|
2244
|
+
await forEachBlocklet(child, async (b, { ancestors }) => {
|
|
2245
|
+
await states.blockletExtras.setConfigs(
|
|
2246
|
+
[blocklet.meta.did, ...ancestors.map((x) => x.meta.did), b.meta.did],
|
|
2247
|
+
[...get(b.meta, 'environments', []), ...getConfigFromPreferences(child)]
|
|
2248
|
+
);
|
|
2249
|
+
});
|
|
2229
2250
|
}
|
|
2251
|
+
}
|
|
2230
2252
|
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2253
|
+
// to be deleted
|
|
2254
|
+
async _setAppSk(did, appSk, context) {
|
|
2255
|
+
if (process.env.NODE_ENV === 'production' && !appSk) {
|
|
2256
|
+
throw new Error(`appSk for blocklet ${did} is required`);
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
if (appSk) {
|
|
2260
|
+
await this.config(
|
|
2261
|
+
{
|
|
2235
2262
|
did,
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
if (results.find((x) => x.isCancelled)) {
|
|
2243
|
-
return { isCancelled: true };
|
|
2244
|
-
}
|
|
2245
|
-
} catch (error) {
|
|
2246
|
-
logger.error('Download blocklet failed', { did, name, error });
|
|
2247
|
-
await this._cancelDownload(blocklet.meta);
|
|
2248
|
-
throw error;
|
|
2263
|
+
configs: [{ key: 'BLOCKLET_APP_SK', value: appSk, secure: true }],
|
|
2264
|
+
skipHook: true,
|
|
2265
|
+
skipDidDocument: true,
|
|
2266
|
+
},
|
|
2267
|
+
context
|
|
2268
|
+
);
|
|
2249
2269
|
}
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
async _getBlockletForInstallation(did) {
|
|
2273
|
+
const blocklet = await states.blocklet.getBlocklet(did, { decryptSk: false });
|
|
2274
|
+
if (!blocklet) {
|
|
2275
|
+
return null;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
const extraState = await states.blockletExtras.findOne({ did: blocklet.meta.did });
|
|
2279
|
+
blocklet.extraState = extraState;
|
|
2250
2280
|
|
|
2251
|
-
return
|
|
2281
|
+
return blocklet;
|
|
2252
2282
|
}
|
|
2253
2283
|
|
|
2254
|
-
async
|
|
2284
|
+
async _runPreInstallHook(blocklet, context) {
|
|
2285
|
+
const nodeEnvironments = await states.node.getEnvironments();
|
|
2286
|
+
|
|
2287
|
+
const preInstall = (b) =>
|
|
2288
|
+
hooks.preInstall(b.env.processId, {
|
|
2289
|
+
hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
|
|
2290
|
+
env: { ...nodeEnvironments },
|
|
2291
|
+
appDir: b.env.appDir,
|
|
2292
|
+
did: blocklet.meta.did, // root blocklet did
|
|
2293
|
+
notification: states.notification,
|
|
2294
|
+
context,
|
|
2295
|
+
});
|
|
2296
|
+
|
|
2297
|
+
await forEachBlocklet(blocklet, preInstall, { parallel: true });
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
async _runPostInstallHook(blocklet, context) {
|
|
2301
|
+
const nodeEnvironments = await states.node.getEnvironments();
|
|
2302
|
+
|
|
2303
|
+
const postInstall = (b, { ancestors }) =>
|
|
2304
|
+
hooks.postInstall(b.env.processId, {
|
|
2305
|
+
hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
|
|
2306
|
+
env: getRuntimeEnvironments(b, nodeEnvironments, ancestors),
|
|
2307
|
+
appDir: b.env.appDir,
|
|
2308
|
+
did: blocklet.meta.did, // root blocklet did
|
|
2309
|
+
notification: states.notification,
|
|
2310
|
+
context,
|
|
2311
|
+
});
|
|
2312
|
+
|
|
2313
|
+
await forEachBlocklet(blocklet, postInstall, { parallel: true });
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
async _createNotification(did, notification) {
|
|
2255
2317
|
try {
|
|
2256
|
-
const
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2318
|
+
const extra = await states.blockletExtras.getMeta(did);
|
|
2319
|
+
const isExternal = !!extra?.controller;
|
|
2320
|
+
|
|
2321
|
+
if (isExternal) {
|
|
2322
|
+
return;
|
|
2260
2323
|
}
|
|
2324
|
+
|
|
2325
|
+
await states.notification.create(notification);
|
|
2261
2326
|
} catch (error) {
|
|
2262
|
-
logger.error('
|
|
2327
|
+
logger.error('create notification failed', { error });
|
|
2263
2328
|
}
|
|
2264
2329
|
}
|
|
2265
2330
|
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2331
|
+
async _deleteExpiredExternalBlocklet() {
|
|
2332
|
+
try {
|
|
2333
|
+
logger.info('start check expired external blocklet');
|
|
2334
|
+
const blockletExtras = await states.blockletExtras.find(
|
|
2335
|
+
{
|
|
2336
|
+
controller: {
|
|
2337
|
+
$exists: true,
|
|
2338
|
+
},
|
|
2339
|
+
'controller.expiredAt': {
|
|
2340
|
+
$exists: false,
|
|
2341
|
+
},
|
|
2342
|
+
},
|
|
2343
|
+
{ did: 1, meta: 1, controller: 1 }
|
|
2344
|
+
);
|
|
2345
|
+
|
|
2346
|
+
for (const data of blockletExtras) {
|
|
2347
|
+
try {
|
|
2348
|
+
const assetState = await util.getNFTState(data.controller.chainHost, data.controller.nftId);
|
|
2349
|
+
const isExpired = isNFTExpired(assetState);
|
|
2269
2350
|
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2351
|
+
if (isExpired) {
|
|
2352
|
+
logger.info('the blocklet already expired', {
|
|
2353
|
+
blockletDid: data.meta.did,
|
|
2354
|
+
nftId: data.controller.nftId,
|
|
2355
|
+
});
|
|
2356
|
+
|
|
2357
|
+
await this.delete({ did: data.meta.did, keepData: true, keepConfigs: true, keepLogsDir: true });
|
|
2358
|
+
logger.info('the expired blocklet already deleted', {
|
|
2359
|
+
blockletDid: data.meta.did,
|
|
2360
|
+
nftId: data.controller.nftId,
|
|
2361
|
+
});
|
|
2362
|
+
|
|
2363
|
+
const expiredAt = getNftExpirationDate(assetState);
|
|
2364
|
+
await states.blockletExtras.updateExpireInfo({ did: data.meta.did, expiredAt });
|
|
2365
|
+
logger.info('updated expired blocklet extra info', {
|
|
2366
|
+
nftId: data.controller.nftId,
|
|
2367
|
+
blockletDid: data.meta.did,
|
|
2368
|
+
});
|
|
2369
|
+
|
|
2370
|
+
// 删除 blocklet 后会 reload nginx, 所以这里每次删除一个
|
|
2371
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
2372
|
+
await sleep(10 * 1000);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
} catch (error) {
|
|
2376
|
+
logger.error('delete expired blocklet failed', {
|
|
2377
|
+
blockletDid: data.meta.did,
|
|
2378
|
+
nftId: data.controller.nftId,
|
|
2379
|
+
error,
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2273
2382
|
}
|
|
2274
|
-
|
|
2383
|
+
|
|
2384
|
+
logger.info('check expired external blocklet end');
|
|
2385
|
+
} catch (error) {
|
|
2386
|
+
logger.info('check expired external blocklet failed', { error });
|
|
2275
2387
|
}
|
|
2276
2388
|
}
|
|
2277
2389
|
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2390
|
+
async _cleanExpiredBlockletData() {
|
|
2391
|
+
try {
|
|
2392
|
+
logger.info('start clean expired blocklet data');
|
|
2393
|
+
const blockletExtras = await states.blockletExtras.getExpiredList();
|
|
2394
|
+
if (blockletExtras.length === 0) {
|
|
2395
|
+
logger.info('no expired blocklet data');
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2281
2398
|
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
await this._rollback(postAction, did, oldBlocklet);
|
|
2399
|
+
const tasks = blockletExtras.map(async ({ did }) => {
|
|
2400
|
+
const blocklet = await states.blocklet.getBlocklet(did);
|
|
2401
|
+
await this._cleanBlockletData({ blocklet, keepData: false, keepLogsDir: false, keepConfigs: false });
|
|
2286
2402
|
|
|
2287
|
-
|
|
2288
|
-
|
|
2403
|
+
this.emit(BlockletEvents.dataCleaned, {
|
|
2404
|
+
blocklet,
|
|
2405
|
+
keepRouting: false,
|
|
2406
|
+
});
|
|
2289
2407
|
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
* @param {string} did
|
|
2293
|
-
* @param {object} oldBlocklet
|
|
2294
|
-
*/
|
|
2295
|
-
async _rollback(action, did, oldBlocklet) {
|
|
2296
|
-
if (action === 'install') {
|
|
2297
|
-
// remove blocklet
|
|
2298
|
-
return this._deleteBlocklet({ did, keepData: false });
|
|
2299
|
-
}
|
|
2408
|
+
logger.info(`cleaned expired blocklet blocklet ${did} data`);
|
|
2409
|
+
});
|
|
2300
2410
|
|
|
2301
|
-
|
|
2302
|
-
// rollback blocklet
|
|
2303
|
-
const { _id } = await this.state.getBlocklet(did);
|
|
2304
|
-
const result = await this.state.updateById(_id, { $set: oldBlocklet });
|
|
2305
|
-
await this._setConfigs(did);
|
|
2306
|
-
logger.info('blocklet rollback successfully', { did });
|
|
2307
|
-
this.emit(BlockletEvents.updated, { blocklet: result });
|
|
2308
|
-
return result;
|
|
2309
|
-
}
|
|
2411
|
+
await Promise.all(tasks);
|
|
2310
2412
|
|
|
2311
|
-
|
|
2312
|
-
|
|
2413
|
+
logger.info('clean expired blocklet data done');
|
|
2414
|
+
} catch (error) {
|
|
2415
|
+
logger.error('clean expired blocklet data failed', { error });
|
|
2416
|
+
}
|
|
2313
2417
|
}
|
|
2314
2418
|
|
|
2315
|
-
async
|
|
2316
|
-
const
|
|
2317
|
-
const { name } = blocklet.meta;
|
|
2318
|
-
const dataDir = path.join(this.dataDirs.data, name);
|
|
2319
|
-
const logsDir = path.join(this.dataDirs.logs, name);
|
|
2320
|
-
const cacheDir = path.join(this.dataDirs.cache, name);
|
|
2419
|
+
async _updateDidDocument(blocklet) {
|
|
2420
|
+
const nodeInfo = await states.node.read();
|
|
2321
2421
|
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2422
|
+
const { wallet } = getBlockletInfo(
|
|
2423
|
+
{
|
|
2424
|
+
meta: blocklet.meta,
|
|
2425
|
+
environments: [BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK, BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_WALLET_TYPE]
|
|
2426
|
+
.map((key) => ({ key, value: blocklet.configObj[key] }))
|
|
2427
|
+
.filter((x) => x.value),
|
|
2428
|
+
},
|
|
2429
|
+
nodeInfo.sk
|
|
2430
|
+
);
|
|
2431
|
+
const didDomain = getDidDomainForBlocklet({ appPid: blocklet.appPid, didDomain: nodeInfo.didDomain });
|
|
2332
2432
|
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
}
|
|
2433
|
+
const domainAliases = (get(blocklet, 'site.domainAliases') || []).filter(
|
|
2434
|
+
(item) => !item.value.endsWith(nodeInfo.didDomain) && !item.value.endsWith('did.staging.arcblock.io') // did.staging.arcblock.io 是旧 did domain, 但主要存在于比较旧的节点中, 需要做兼容
|
|
2435
|
+
);
|
|
2337
2436
|
|
|
2338
|
-
|
|
2339
|
-
logger.info('blocklet removed successfully', { did });
|
|
2437
|
+
domainAliases.push({ value: didDomain, isProtected: true });
|
|
2340
2438
|
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
}
|
|
2439
|
+
// 先更新 routing rule db 中的 domain aliases, 这一步的目的是为了后面用
|
|
2440
|
+
await states.site.updateDomainAliasList(blocklet.site.id, domainAliases);
|
|
2344
2441
|
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2442
|
+
this.emit(BlockletEvents.appDidChanged, blocklet);
|
|
2443
|
+
|
|
2444
|
+
await didDocument.updateBlockletDocument({
|
|
2445
|
+
wallet,
|
|
2446
|
+
appPid: blocklet.appPid,
|
|
2447
|
+
alsoKnownAs: getBlockletKnownAs(blocklet),
|
|
2448
|
+
daemonDidDomain: util.getServerDidDomain(nodeInfo),
|
|
2449
|
+
didRegistryUrl: nodeInfo.didRegistry,
|
|
2450
|
+
domain: nodeInfo.didDomain,
|
|
2451
|
+
});
|
|
2452
|
+
logger.info('updated blocklet dns document', { appPid: blocklet.appPid, appDid: blocklet.appDid });
|
|
2352
2453
|
}
|
|
2353
2454
|
|
|
2354
|
-
async
|
|
2355
|
-
const blocklet = await
|
|
2356
|
-
|
|
2357
|
-
await this.extras.delSettings(did);
|
|
2455
|
+
async _updateDependents(did) {
|
|
2456
|
+
const blocklet = await states.blocklet.getBlocklet(did);
|
|
2457
|
+
const map = {};
|
|
2358
2458
|
for (const child of blocklet.children) {
|
|
2359
|
-
|
|
2459
|
+
child.dependents = [];
|
|
2460
|
+
map[child.meta.did] = child;
|
|
2360
2461
|
}
|
|
2462
|
+
|
|
2463
|
+
forEachBlockletSync(blocklet, (x, { id }) => {
|
|
2464
|
+
if (x.dependencies) {
|
|
2465
|
+
x.dependencies.forEach((y) => {
|
|
2466
|
+
if (map[y.did]) {
|
|
2467
|
+
map[y.did].dependents.push({ id, required: y.required });
|
|
2468
|
+
}
|
|
2469
|
+
});
|
|
2470
|
+
}
|
|
2471
|
+
});
|
|
2472
|
+
|
|
2473
|
+
await states.blocklet.updateBlocklet(blocklet.meta.did, { children: blocklet.children });
|
|
2361
2474
|
}
|
|
2362
2475
|
}
|
|
2363
2476
|
|