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