@abtnode/core 1.17.8-beta-20260109-075740-5f484e08 → 1.17.8-beta-20260111-112953-aed5ff39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/lib/api/team/access-key-manager.js +104 -0
  2. package/lib/api/team/invitation-manager.js +461 -0
  3. package/lib/api/team/notification-manager.js +189 -0
  4. package/lib/api/team/oauth-manager.js +60 -0
  5. package/lib/api/team/org-crud-manager.js +202 -0
  6. package/lib/api/team/org-manager.js +56 -0
  7. package/lib/api/team/org-member-manager.js +403 -0
  8. package/lib/api/team/org-query-manager.js +126 -0
  9. package/lib/api/team/org-resource-manager.js +186 -0
  10. package/lib/api/team/passport-manager.js +670 -0
  11. package/lib/api/team/rbac-manager.js +335 -0
  12. package/lib/api/team/session-manager.js +540 -0
  13. package/lib/api/team/store-manager.js +198 -0
  14. package/lib/api/team/tag-manager.js +230 -0
  15. package/lib/api/team/user-auth-manager.js +132 -0
  16. package/lib/api/team/user-manager.js +78 -0
  17. package/lib/api/team/user-query-manager.js +299 -0
  18. package/lib/api/team/user-social-manager.js +354 -0
  19. package/lib/api/team/user-update-manager.js +224 -0
  20. package/lib/api/team/verify-code-manager.js +161 -0
  21. package/lib/api/team.js +439 -3287
  22. package/lib/blocklet/manager/disk/auth-manager.js +68 -0
  23. package/lib/blocklet/manager/disk/backup-manager.js +288 -0
  24. package/lib/blocklet/manager/disk/cleanup-manager.js +157 -0
  25. package/lib/blocklet/manager/disk/component-manager.js +83 -0
  26. package/lib/blocklet/manager/disk/config-manager.js +191 -0
  27. package/lib/blocklet/manager/disk/controller-manager.js +64 -0
  28. package/lib/blocklet/manager/disk/delete-reset-manager.js +328 -0
  29. package/lib/blocklet/manager/disk/download-manager.js +96 -0
  30. package/lib/blocklet/manager/disk/env-config-manager.js +311 -0
  31. package/lib/blocklet/manager/disk/federated-manager.js +651 -0
  32. package/lib/blocklet/manager/disk/hook-manager.js +124 -0
  33. package/lib/blocklet/manager/disk/install-component-manager.js +95 -0
  34. package/lib/blocklet/manager/disk/install-core-manager.js +448 -0
  35. package/lib/blocklet/manager/disk/install-download-manager.js +313 -0
  36. package/lib/blocklet/manager/disk/install-manager.js +36 -0
  37. package/lib/blocklet/manager/disk/install-upgrade-manager.js +340 -0
  38. package/lib/blocklet/manager/disk/job-manager.js +467 -0
  39. package/lib/blocklet/manager/disk/lifecycle-manager.js +26 -0
  40. package/lib/blocklet/manager/disk/notification-manager.js +343 -0
  41. package/lib/blocklet/manager/disk/query-manager.js +562 -0
  42. package/lib/blocklet/manager/disk/settings-manager.js +507 -0
  43. package/lib/blocklet/manager/disk/start-manager.js +611 -0
  44. package/lib/blocklet/manager/disk/stop-restart-manager.js +292 -0
  45. package/lib/blocklet/manager/disk/update-manager.js +153 -0
  46. package/lib/blocklet/manager/disk.js +669 -5796
  47. package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +5 -0
  48. package/lib/blocklet/manager/lock.js +18 -0
  49. package/lib/event/index.js +28 -24
  50. package/lib/util/blocklet/app-utils.js +192 -0
  51. package/lib/util/blocklet/blocklet-loader.js +258 -0
  52. package/lib/util/blocklet/config-manager.js +232 -0
  53. package/lib/util/blocklet/did-document.js +240 -0
  54. package/lib/util/blocklet/environment.js +555 -0
  55. package/lib/util/blocklet/health-check.js +449 -0
  56. package/lib/util/blocklet/install-utils.js +365 -0
  57. package/lib/util/blocklet/logo.js +57 -0
  58. package/lib/util/blocklet/meta-utils.js +269 -0
  59. package/lib/util/blocklet/port-manager.js +141 -0
  60. package/lib/util/blocklet/process-manager.js +504 -0
  61. package/lib/util/blocklet/runtime-info.js +105 -0
  62. package/lib/util/blocklet/validation.js +418 -0
  63. package/lib/util/blocklet.js +98 -3066
  64. package/lib/util/wallet-app-notification.js +40 -0
  65. package/package.json +22 -22
@@ -0,0 +1,191 @@
1
+ const pick = require('lodash/pick');
2
+ const defaultsDeep = require('lodash/defaultsDeep');
3
+ const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager:config');
4
+ const { getBlockletInfo } = require('@blocklet/meta/lib/info');
5
+ const { CustomError } = require('@blocklet/error');
6
+ const { sendToUser } = require('@blocklet/sdk/lib/util/send-notification');
7
+ const { emailConfigSchema } = require('@abtnode/auth/lib/email');
8
+ const { BlockletEvents, BlockletInternalEvents } = require('@blocklet/constant');
9
+
10
+ const states = require('../../../states');
11
+ const { blockletThemeSchema } = require('../../../validators/theme');
12
+
13
+ /**
14
+ * Config navigations
15
+ * @param {Object} manager - BlockletManager instance
16
+ * @param {Object} params
17
+ * @param {Object} context
18
+ * @returns {Promise<Object>}
19
+ */
20
+ async function configNavigations(manager, { did, navigations = [] }, context) {
21
+ if (!Array.isArray(navigations)) {
22
+ throw new Error('navigations is not an array');
23
+ }
24
+ await states.blockletExtras.setSettings(did, { navigations });
25
+
26
+ const newState = await manager.getBlocklet(did);
27
+ manager.emit(BlockletInternalEvents.appSettingChanged, { appDid: did });
28
+ manager.emit(BlockletEvents.updated, { ...newState, context });
29
+ return newState;
30
+ }
31
+
32
+ /**
33
+ * Config theme
34
+ * @param {Object} manager - BlockletManager instance
35
+ * @param {Object} params
36
+ * @param {Object} context
37
+ * @returns {Promise<Object>}
38
+ */
39
+ async function configTheme(manager, { did, theme = {} }, context) {
40
+ const { error, value } = blockletThemeSchema.validate(theme);
41
+ if (error) {
42
+ throw new Error(error.message);
43
+ }
44
+
45
+ const currentSettings = await states.blockletExtras.getSettings(did);
46
+ const currentTheme = currentSettings.theme || {};
47
+
48
+ if (value.concepts && currentTheme.meta?.locked) {
49
+ throw new CustomError(403, 'Theme is locked and cannot be modified');
50
+ }
51
+
52
+ const updatedTheme = { ...currentTheme, ...value };
53
+ await states.blockletExtras.setSettings(did, { theme: updatedTheme });
54
+
55
+ const newState = await manager.getBlocklet(did);
56
+ manager.emit(BlockletInternalEvents.appSettingChanged, { appDid: did });
57
+ manager.emit(BlockletEvents.updated, { ...newState, context });
58
+ manager.emit(BlockletEvents.configTheme, { args: { did, theme }, result: newState.settings.theme, context });
59
+ return newState;
60
+ }
61
+
62
+ /**
63
+ * Config authentication
64
+ * @param {Object} manager - BlockletManager instance
65
+ * @param {Object} params
66
+ * @param {Object} context
67
+ * @returns {Promise<Object>}
68
+ */
69
+ async function configAuthentication(manager, { did, authentication = {} }, context) {
70
+ const oldConfig = await states.blockletExtras.getSettings(did, 'authentication', {});
71
+ const parsedData = JSON.parse(authentication);
72
+ const mergeConfig = defaultsDeep(parsedData, oldConfig);
73
+ await states.blockletExtras.setSettings(did, { authentication: mergeConfig });
74
+ const newState = await manager.getBlocklet(did);
75
+ manager.emit(BlockletInternalEvents.appSettingChanged, { appDid: did });
76
+ manager.emit(BlockletEvents.updated, { ...newState, context });
77
+ return newState;
78
+ }
79
+
80
+ /**
81
+ * Config DID connect
82
+ * @param {Object} manager - BlockletManager instance
83
+ * @param {Object} params
84
+ * @param {Object} context
85
+ * @returns {Promise<Object>}
86
+ */
87
+ async function configDidConnect(manager, { did, didConnect = {} }, context) {
88
+ const oldConfig = await states.blockletExtras.getSettings(did, 'didConnect', {});
89
+ const parsedData = JSON.parse(didConnect);
90
+ const mergeConfig = defaultsDeep(parsedData, oldConfig);
91
+ await states.blockletExtras.setSettings(did, { didConnect: mergeConfig });
92
+ const newState = await manager.getBlocklet(did);
93
+ manager.emit(BlockletInternalEvents.appSettingChanged, { appDid: did });
94
+ manager.emit(BlockletEvents.updated, { ...newState, context });
95
+ return newState;
96
+ }
97
+
98
+ /**
99
+ * Config notification
100
+ * @param {Object} manager - BlockletManager instance
101
+ * @param {Object} params
102
+ * @param {Object} context
103
+ * @returns {Promise<Object>}
104
+ */
105
+ async function configNotification(manager, { did, notification = {} }, context) {
106
+ let newConfig = {};
107
+ try {
108
+ newConfig = JSON.parse(notification);
109
+ } catch (error) {
110
+ logger.error('parse configNotification error', { error });
111
+ throw new Error('parse configNotification error');
112
+ }
113
+ const enabled = newConfig?.email?.enabled;
114
+ if (enabled) {
115
+ const { error } = emailConfigSchema.validate(
116
+ pick(newConfig?.email || {}, ['from', 'host', 'port', 'user', 'password', 'secure'])
117
+ );
118
+ if (error) {
119
+ logger.error('configNotification validate error', { error });
120
+ throw new Error(error.message);
121
+ }
122
+ }
123
+ const oldConfig = await states.blockletExtras.getSettings(did, 'notification', {});
124
+ const mergeConfig = { ...oldConfig, ...newConfig };
125
+ await states.blockletExtras.setSettings(did, { notification: mergeConfig });
126
+ const newState = await manager.getBlocklet(did);
127
+ manager.emit(BlockletInternalEvents.appSettingChanged, { appDid: did });
128
+ manager.emit(BlockletEvents.updated, { ...newState, context });
129
+ return newState;
130
+ }
131
+
132
+ /**
133
+ * Send test email
134
+ * @param {Object} manager - BlockletManager instance
135
+ * @param {Object} params
136
+ * @returns {Promise<Object>}
137
+ */
138
+ async function sendEmail(manager, { did, receiver, email }) {
139
+ const blocklet = await manager.getBlocklet(did);
140
+ const nodeInfo = await states.node.read();
141
+ const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
142
+
143
+ const [result] = await sendToUser(
144
+ receiver,
145
+ JSON.parse(email),
146
+ {
147
+ appDid: wallet.address,
148
+ appSk: wallet.secretKey,
149
+ },
150
+ undefined,
151
+ 'send-to-mail'
152
+ );
153
+ if (result && result.status === 'fulfilled') {
154
+ return result.value;
155
+ }
156
+ throw new Error(result?.reason || 'failed to send email');
157
+ }
158
+
159
+ /**
160
+ * Send push notification
161
+ * @param {Object} manager - BlockletManager instance
162
+ * @param {Object} params
163
+ * @returns {Promise<Object>}
164
+ */
165
+ async function sendPush(manager, { did, receiver, notification }) {
166
+ const blocklet = await manager.getBlocklet(did);
167
+ const nodeInfo = await states.node.read();
168
+ const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
169
+
170
+ const result = await sendToUser(
171
+ receiver,
172
+ JSON.parse(notification),
173
+ {
174
+ appDid: wallet.address,
175
+ appSk: wallet.secretKey,
176
+ },
177
+ undefined,
178
+ 'send-to-push-kit'
179
+ );
180
+ return result;
181
+ }
182
+
183
+ module.exports = {
184
+ configNavigations,
185
+ configTheme,
186
+ configAuthentication,
187
+ configDidConnect,
188
+ configNotification,
189
+ sendEmail,
190
+ sendPush,
191
+ };
@@ -0,0 +1,64 @@
1
+ const isEmpty = require('lodash/isEmpty');
2
+ const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager:controller');
3
+ const { BLOCKLET_CONTROLLER_STATUS, SUSPENDED_REASON } = require('@blocklet/constant');
4
+
5
+ const states = require('../../../states');
6
+ const launcher = require('../../../util/launcher');
7
+
8
+ /**
9
+ * Check controller status
10
+ * @param {Object} manager - BlockletManager instance
11
+ * @param {Object} blocklet - Blocklet
12
+ * @param {string} action - Action name
13
+ * @returns {Promise<void>}
14
+ */
15
+ async function checkControllerStatus(manager, blocklet, action) {
16
+ if (isEmpty(blocklet.controller)) {
17
+ return;
18
+ }
19
+
20
+ const isExpired = await launcher.isBlockletExpired(blocklet.meta.did, blocklet.controller);
21
+
22
+ if (isExpired) {
23
+ if (blocklet.controller.status?.value !== BLOCKLET_CONTROLLER_STATUS.suspended) {
24
+ await states.blockletExtras.updateByDid(blocklet.meta.did, {
25
+ controller: {
26
+ ...blocklet.controller,
27
+ status: {
28
+ value: BLOCKLET_CONTROLLER_STATUS.suspended,
29
+ reason: SUSPENDED_REASON.expired,
30
+ },
31
+ },
32
+ });
33
+ logger.info('update blocklet controller status to suspended', {
34
+ did: blocklet.meta.did,
35
+ nftId: blocklet.controller?.nftId,
36
+ });
37
+ }
38
+ logger.error(`try to ${action} an expired blocklet`, {
39
+ did: blocklet.meta.did,
40
+ nftId: blocklet.controller?.nftId,
41
+ });
42
+ throw new Error(`Can not ${action} an expired blocklet`);
43
+ }
44
+
45
+ if (
46
+ blocklet.controller.status?.value === BLOCKLET_CONTROLLER_STATUS.suspended &&
47
+ blocklet.controller.status?.reason === SUSPENDED_REASON.expired
48
+ ) {
49
+ // 如果是因为过期被暂停的,续期后, 并且不影响启动
50
+ await states.blockletExtras.updateByDid(blocklet.meta.did, {
51
+ controller: {
52
+ ...blocklet.controller,
53
+ status: {
54
+ value: BLOCKLET_CONTROLLER_STATUS.normal,
55
+ reason: '',
56
+ },
57
+ },
58
+ });
59
+ }
60
+ }
61
+
62
+ module.exports = {
63
+ checkControllerStatus,
64
+ };
@@ -0,0 +1,328 @@
1
+ /* eslint-disable no-await-in-loop */
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const pick = require('lodash/pick');
5
+ const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager:delete-reset');
6
+ const { BlockletEvents, BlockletStatus } = require('@blocklet/constant');
7
+ const {
8
+ forEachBlockletSync,
9
+ isInProgress,
10
+ getDisplayName,
11
+ isExternalBlocklet,
12
+ isDeletableBlocklet,
13
+ } = require('@blocklet/meta/lib/util');
14
+
15
+ const { APP_CONFIG_DIR, COMPONENT_ENV_FILE_NAME } = require('@blocklet/constant');
16
+ const states = require('../../../states');
17
+ const hooks = require('../../hooks');
18
+ const {
19
+ deleteBlockletProcess,
20
+ getRuntimeEnvironments,
21
+ getHookArgs,
22
+ removeAppConfigsFromComponent,
23
+ } = require('../../../util/blocklet');
24
+ const checkNeedRunDocker = require('../../../util/docker/check-need-run-docker');
25
+ const { dockerExec } = require('../../../util/docker/docker-exec');
26
+ const { dockerExecChown } = require('../../../util/docker/docker-exec-chown');
27
+ const checkDockerRunHistory = require('../../../util/docker/check-docker-run-history');
28
+ const parseDockerName = require('../../../util/docker/parse-docker-name');
29
+ const { removeDockerNetwork } = require('../../../util/docker/docker-network');
30
+
31
+ /**
32
+ * Safely remove directories
33
+ */
34
+ async function _safeRemoveDir(manager, did, childDid, inputDirs) {
35
+ const dirs = inputDirs.filter(Boolean);
36
+ try {
37
+ for (const dir of dirs) {
38
+ fs.removeSync(dir);
39
+ logger.info(`removed blocklet ${did} ${childDid}: ${dir}`);
40
+ }
41
+ } catch (error) {
42
+ const nodeInfo = await states.node.read();
43
+ // 如果删除因为权限问题失败,尝试用 docker 用户去调整用户组为宿主机当前用户
44
+ if (checkDockerRunHistory(nodeInfo) && error?.message?.includes('EACCES')) {
45
+ await dockerExecChown({ name: `${did}-${childDid}-remove-catch`, dirs, force: true });
46
+ for (const dir of dirs) {
47
+ fs.removeSync(dir);
48
+ logger.info(`removed blocklet ${did} ${childDid}: ${dir}`);
49
+ }
50
+ } else {
51
+ throw error;
52
+ }
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Delete blocklet
58
+ */
59
+ async function deleteBlocklet(manager, { did, keepData, keepLogsDir, keepConfigs, sessionId }, context) {
60
+ logger.info('delete blocklet', { did, keepData, sessionId });
61
+
62
+ const blocklet = await manager.getBlocklet(did);
63
+
64
+ try {
65
+ if (isDeletableBlocklet(blocklet) === false) {
66
+ throw new Error('Blocklet is protected from accidental deletion');
67
+ }
68
+
69
+ const nodeInfo = await states.node.read();
70
+ const nodeEnvironments = await states.node.getEnvironments(nodeInfo);
71
+ await deleteBlockletProcess(blocklet, {
72
+ preDelete: async (b, { ancestors }) => {
73
+ const needRunDocker = await checkNeedRunDocker(
74
+ b.meta,
75
+ getRuntimeEnvironments(b, nodeEnvironments, ancestors),
76
+ nodeInfo,
77
+ isExternalBlocklet(blocklet)
78
+ );
79
+ const hookArgs = getHookArgs(b);
80
+ const nextEnv = getRuntimeEnvironments(b, nodeEnvironments, ancestors);
81
+ if (needRunDocker) {
82
+ if (b.meta.scripts?.preUninstall) {
83
+ await dockerExec({
84
+ blocklet,
85
+ meta: b.meta,
86
+ env: nextEnv,
87
+ script: b.meta.scripts.preUninstall,
88
+ hookName: 'preUninstall',
89
+ nodeInfo,
90
+ retry: 0,
91
+ ...hookArgs,
92
+ });
93
+ }
94
+ return null;
95
+ }
96
+ return hooks.preUninstall(b.meta.title, {
97
+ appDir: b.env.appDir,
98
+ hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
99
+ env: nextEnv,
100
+ did, // root blocklet did
101
+ notification: states.notification,
102
+ context,
103
+ exitOnError: false,
104
+ ...hookArgs,
105
+ });
106
+ },
107
+ });
108
+
109
+ const doc = await manager._deleteBlocklet({ did, keepData, keepLogsDir, keepConfigs }, context);
110
+ manager._createNotification(
111
+ doc.meta.did,
112
+ {
113
+ title: 'Blocklet Deleted',
114
+ description: `Blocklet ${getDisplayName(blocklet)} is deleted.`,
115
+ entityType: 'blocklet',
116
+ entityId: doc.meta.did,
117
+ severity: 'success',
118
+ },
119
+ {
120
+ skipGetBlocklet: true,
121
+ }
122
+ );
123
+ await removeDockerNetwork(parseDockerName(did, 'docker-network'));
124
+ return doc;
125
+ } catch (error) {
126
+ // If we installed a corrupted blocklet accidentally, just cleanup the disk and state db
127
+ logger.error('blocklet delete failed, will delete again', { did, error });
128
+ const doc = await manager._deleteBlocklet({ did, keepData, keepLogsDir, keepConfigs }, context);
129
+ await removeDockerNetwork(parseDockerName(did, 'docker-network'));
130
+
131
+ manager._createNotification(
132
+ doc.meta.did,
133
+ {
134
+ title: 'Blocklet Deleted',
135
+ description: `Blocklet ${getDisplayName(blocklet)} is deleted.`,
136
+ entityType: 'blocklet',
137
+ entityId: doc.meta.did,
138
+ severity: 'success',
139
+ },
140
+ {
141
+ skipGetBlocklet: true,
142
+ }
143
+ );
144
+
145
+ return doc;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Reset blocklet
151
+ */
152
+ async function reset(manager, { did, childDid }, context = {}) {
153
+ logger.info('reset blocklet', { did, childDid });
154
+
155
+ const blocklet = await manager.getBlocklet(did);
156
+
157
+ if (!childDid) {
158
+ if (isInProgress(blocklet.status || blocklet.status === BlockletStatus.running)) {
159
+ throw new Error('Cannot reset when blocklet is in progress');
160
+ }
161
+
162
+ try {
163
+ await manager.deleteProcess({ did }, context);
164
+ } catch {
165
+ // do nothing
166
+ }
167
+
168
+ // Cleanup disk storage
169
+ const { cacheDir, logsDir, dataDir } = blocklet.env;
170
+ await _safeRemoveDir(manager, did, blocklet.meta.did, [cacheDir, dataDir, logsDir]);
171
+
172
+ // Reset config in db
173
+ await manager._resetExtras(blocklet.meta.did);
174
+ await manager._updateBlockletEnvironment(did);
175
+ await manager.resetSiteByDid(did, context);
176
+ } else {
177
+ const child = blocklet.children.find((x) => x.meta.did === childDid);
178
+
179
+ if (!child) {
180
+ throw new Error('Child does not exist');
181
+ }
182
+
183
+ if (isInProgress(child.status || child.status === BlockletStatus.running)) {
184
+ throw new Error('Cannot reset when component is in progress');
185
+ }
186
+
187
+ try {
188
+ await manager.deleteProcess({ did, componentDids: [child.meta.did] }, context);
189
+ } catch {
190
+ // do nothing
191
+ }
192
+
193
+ // Cleanup disk storage
194
+ const { cacheDir, logsDir, dataDir } = child.env;
195
+ await _safeRemoveDir(manager, did, child.meta.did, [cacheDir, dataDir, logsDir]);
196
+
197
+ // Reset config in db
198
+ await states.blockletExtras.delConfigs([blocklet.meta.did, child.meta.did]);
199
+ await manager._setConfigsFromMeta(blocklet.meta.did, child.meta.did);
200
+ await manager._updateBlockletEnvironment(did);
201
+ }
202
+
203
+ logger.info('blocklet reset', { did, childDid });
204
+ return blocklet;
205
+ }
206
+
207
+ /**
208
+ * Delete component
209
+ */
210
+ async function deleteComponent(manager, { did, rootDid, keepData, keepState, sessionId }, context) {
211
+ logger.info('delete blocklet component', { did, rootDid, keepData, keepState, sessionId });
212
+
213
+ const nodeInfo = await states.node.read();
214
+ if (checkDockerRunHistory(nodeInfo)) {
215
+ await dockerExecChown({
216
+ name: `${did}-${rootDid}-deleteComponent`,
217
+ dirs: [path.join(process.env.ABT_NODE_DATA_DIR, 'data', rootDid)],
218
+ force: true,
219
+ });
220
+ }
221
+ const app = await manager.getBlocklet(rootDid);
222
+
223
+ const child = app.children.find((x) => x.meta.did === did);
224
+ if (!child) {
225
+ throw new Error('Component does not exist');
226
+ }
227
+
228
+ // delete state
229
+ const doc = await states.blocklet.getBlocklet(rootDid);
230
+ doc.children = doc.children.filter((x) => x.meta.did !== did);
231
+ const deletedChildren = await states.blockletExtras.getSettings(did, 'children', []);
232
+ if (keepData !== false && keepState !== false) {
233
+ deletedChildren.push({
234
+ meta: pick(child.meta, ['did', 'name', 'bundleDid', 'bundleName', 'version', 'title', 'description']),
235
+ mountPoint: child.mountPoint,
236
+ status: BlockletStatus.deleted,
237
+ deletedAt: new Date(),
238
+ });
239
+ }
240
+
241
+ await states.blocklet.updateBlocklet(rootDid, doc);
242
+ states.blockletExtras.setSettings(doc.meta.did, { children: deletedChildren });
243
+
244
+ // delete process
245
+ try {
246
+ const skippedProcessIds = [];
247
+ forEachBlockletSync(app, (b) => {
248
+ if (!b.env.id.startsWith(child.env.id)) {
249
+ skippedProcessIds.push(b.env.processId);
250
+ }
251
+ });
252
+ await deleteBlockletProcess(app, { skippedProcessIds });
253
+ logger.info('delete blocklet process for deleting component', { did, rootDid });
254
+ } catch (err) {
255
+ logger.error('delete blocklet process for deleting component', { did, rootDid, error: err });
256
+ }
257
+
258
+ // delete storage
259
+ const { cacheDir, logsDir, dataDir } = child.env;
260
+ await _safeRemoveDir(
261
+ manager,
262
+ rootDid,
263
+ child.meta.did,
264
+ [
265
+ cacheDir,
266
+ logsDir,
267
+ path.join(app.env.dataDir, APP_CONFIG_DIR, child.meta.did, COMPONENT_ENV_FILE_NAME),
268
+ keepData === false ? dataDir : null,
269
+ ].filter(Boolean)
270
+ );
271
+ if (keepData === false) {
272
+ const componentEnvs = await states.blockletExtras.getConfigs([app.meta.did, child.meta.did]);
273
+ await states.blockletExtras.delConfigs([app.meta.did, child.meta.did]);
274
+
275
+ // remove app configs if no component use it
276
+ const tmpApp = await manager.getBlocklet(rootDid);
277
+ await removeAppConfigsFromComponent(componentEnvs, tmpApp, states.blockletExtras);
278
+ }
279
+ const newBlocklet = await manager.getBlocklet(rootDid);
280
+
281
+ if (newBlocklet.controller) {
282
+ const componentDids = [did];
283
+ const eventType = 'uninstall';
284
+
285
+ manager.reportComponentUsageQueue.push({
286
+ entity: 'blocklet',
287
+ action: 'reportComponentUsage',
288
+ did: newBlocklet.meta.did,
289
+ time: new Date().toISOString(),
290
+ componentDids,
291
+ eventType,
292
+ context,
293
+ });
294
+
295
+ logger.info('pushed reporting uninstall components event job to queue', {
296
+ did: newBlocklet.meta.did,
297
+ componentDids,
298
+ eventType,
299
+ });
300
+ }
301
+
302
+ await manager._updateDependents(rootDid);
303
+
304
+ // support edge case
305
+ if (newBlocklet.children.length === 0) {
306
+ await states.blocklet.setBlockletStatus(newBlocklet.meta.did, BlockletStatus.stopped);
307
+ await removeDockerNetwork(parseDockerName(app.meta.did, 'docker-network'));
308
+ }
309
+
310
+ manager.emit(BlockletEvents.upgraded, { blocklet: newBlocklet, context: { ...context, createAuditLog: false } }); // trigger router refresh
311
+
312
+ manager._createNotification(newBlocklet.meta.did, {
313
+ title: 'Component delete succeed',
314
+ description: `${child.meta.title} is successfully deleted for ${getDisplayName(newBlocklet)}.`,
315
+ entityType: 'blocklet',
316
+ entityId: newBlocklet.meta.did,
317
+ severity: 'success',
318
+ });
319
+
320
+ return newBlocklet;
321
+ }
322
+
323
+ module.exports = {
324
+ _safeRemoveDir,
325
+ delete: deleteBlocklet,
326
+ reset,
327
+ deleteComponent,
328
+ };
@@ -0,0 +1,96 @@
1
+ const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager:download');
2
+ const { BlockletStatus, fromBlockletStatus } = require('@blocklet/constant');
3
+ const { forEachComponentV2Sync } = require('@blocklet/meta/lib/util');
4
+
5
+ const states = require('../../../states');
6
+
7
+ /**
8
+ * Cancel download
9
+ * @param {Object} manager - BlockletManager instance
10
+ * @param {Object} params
11
+ * @param {string} params.did - Blocklet DID
12
+ * @param {Object} statusLock - Status lock
13
+ * @returns {Promise<Object>}
14
+ */
15
+ async function cancelDownload(manager, { did: inputDid }, statusLock) {
16
+ await statusLock.acquire('cancel-download');
17
+ try {
18
+ const blocklet = await states.blocklet.getBlocklet(inputDid);
19
+ if (!blocklet) {
20
+ throw new Error(`Can not cancel download for non-exist blocklet in database. did: ${inputDid}`);
21
+ }
22
+
23
+ const { name, did, version } = blocklet.meta;
24
+
25
+ if (
26
+ ![BlockletStatus.downloading, BlockletStatus.waiting].includes(blocklet.greenStatus) &&
27
+ ![BlockletStatus.downloading, BlockletStatus.waiting].includes(blocklet.status)
28
+ ) {
29
+ throw new Error(`Can not cancel blocklet that status is ${fromBlockletStatus(blocklet.status)}`);
30
+ }
31
+
32
+ const job = await manager.installQueue.get(did);
33
+
34
+ // cancel job
35
+ if (blocklet.greenStatus === BlockletStatus.downloading || blocklet.status === BlockletStatus.downloading) {
36
+ try {
37
+ await manager.blockletDownloader.cancelDownload(blocklet.meta.did);
38
+ } catch (error) {
39
+ logger.error('failed to exec blockletDownloader.download', { did: blocklet.meta.did, error });
40
+ }
41
+ } else if (blocklet.greenStatus === BlockletStatus.waiting || blocklet.status === BlockletStatus.waiting) {
42
+ try {
43
+ await manager.installQueue.cancel(blocklet.meta.did);
44
+ } catch (error) {
45
+ logger.error('failed to cancel waiting', { did: blocklet.meta.did, error });
46
+ }
47
+ }
48
+
49
+ // rollback
50
+ if (job) {
51
+ const { postAction, oldBlocklet } = job;
52
+ await manager._rollback(postAction, did, oldBlocklet);
53
+ } else {
54
+ const data = await manager._rollbackCache.restore({ did });
55
+ if (data) {
56
+ const { action, oldBlocklet } = data;
57
+ await manager._rollback(action, did, oldBlocklet);
58
+ await manager._rollbackCache.remove({ did: inputDid });
59
+ } else {
60
+ throw new Error(`Cannot find rollback data in queue or backup file of blocklet ${inputDid}`);
61
+ }
62
+ }
63
+
64
+ logger.info('cancel download blocklet', { did, name, version, status: fromBlockletStatus(blocklet.status) });
65
+
66
+ return blocklet;
67
+ } catch (error) {
68
+ try {
69
+ // fallback blocklet status to error
70
+ const blocklet = await states.blocklet.getBlocklet(inputDid);
71
+ if (blocklet) {
72
+ const componentDids = [];
73
+ forEachComponentV2Sync(blocklet, (x) => {
74
+ if (
75
+ [BlockletStatus.waiting, BlockletStatus.downloading].includes(x.status) ||
76
+ [BlockletStatus.waiting, BlockletStatus.downloading].includes(x.greenStatus)
77
+ ) {
78
+ componentDids.push(x.meta.did);
79
+ }
80
+ });
81
+
82
+ await states.blocklet.setBlockletStatus(blocklet.meta.did, BlockletStatus.error, { componentDids });
83
+ }
84
+ } catch (err) {
85
+ logger.error('Failed to fallback blocklet status to error on cancelDownload', { error });
86
+ }
87
+
88
+ throw error;
89
+ } finally {
90
+ await statusLock.releaseLock('cancel-download');
91
+ }
92
+ }
93
+
94
+ module.exports = {
95
+ cancelDownload,
96
+ };