@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.
- package/lib/api/team/access-key-manager.js +104 -0
- package/lib/api/team/invitation-manager.js +461 -0
- package/lib/api/team/notification-manager.js +189 -0
- package/lib/api/team/oauth-manager.js +60 -0
- package/lib/api/team/org-crud-manager.js +202 -0
- package/lib/api/team/org-manager.js +56 -0
- package/lib/api/team/org-member-manager.js +403 -0
- package/lib/api/team/org-query-manager.js +126 -0
- package/lib/api/team/org-resource-manager.js +186 -0
- package/lib/api/team/passport-manager.js +670 -0
- package/lib/api/team/rbac-manager.js +335 -0
- package/lib/api/team/session-manager.js +540 -0
- package/lib/api/team/store-manager.js +198 -0
- package/lib/api/team/tag-manager.js +230 -0
- package/lib/api/team/user-auth-manager.js +132 -0
- package/lib/api/team/user-manager.js +78 -0
- package/lib/api/team/user-query-manager.js +299 -0
- package/lib/api/team/user-social-manager.js +354 -0
- package/lib/api/team/user-update-manager.js +224 -0
- package/lib/api/team/verify-code-manager.js +161 -0
- package/lib/api/team.js +439 -3287
- package/lib/blocklet/manager/disk/auth-manager.js +68 -0
- package/lib/blocklet/manager/disk/backup-manager.js +288 -0
- package/lib/blocklet/manager/disk/cleanup-manager.js +157 -0
- package/lib/blocklet/manager/disk/component-manager.js +83 -0
- package/lib/blocklet/manager/disk/config-manager.js +191 -0
- package/lib/blocklet/manager/disk/controller-manager.js +64 -0
- package/lib/blocklet/manager/disk/delete-reset-manager.js +328 -0
- package/lib/blocklet/manager/disk/download-manager.js +96 -0
- package/lib/blocklet/manager/disk/env-config-manager.js +311 -0
- package/lib/blocklet/manager/disk/federated-manager.js +651 -0
- package/lib/blocklet/manager/disk/hook-manager.js +124 -0
- package/lib/blocklet/manager/disk/install-component-manager.js +95 -0
- package/lib/blocklet/manager/disk/install-core-manager.js +448 -0
- package/lib/blocklet/manager/disk/install-download-manager.js +313 -0
- package/lib/blocklet/manager/disk/install-manager.js +36 -0
- package/lib/blocklet/manager/disk/install-upgrade-manager.js +340 -0
- package/lib/blocklet/manager/disk/job-manager.js +467 -0
- package/lib/blocklet/manager/disk/lifecycle-manager.js +26 -0
- package/lib/blocklet/manager/disk/notification-manager.js +343 -0
- package/lib/blocklet/manager/disk/query-manager.js +562 -0
- package/lib/blocklet/manager/disk/settings-manager.js +507 -0
- package/lib/blocklet/manager/disk/start-manager.js +611 -0
- package/lib/blocklet/manager/disk/stop-restart-manager.js +292 -0
- package/lib/blocklet/manager/disk/update-manager.js +153 -0
- package/lib/blocklet/manager/disk.js +669 -5796
- package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +5 -0
- package/lib/blocklet/manager/lock.js +18 -0
- package/lib/event/index.js +28 -24
- package/lib/util/blocklet/app-utils.js +192 -0
- package/lib/util/blocklet/blocklet-loader.js +258 -0
- package/lib/util/blocklet/config-manager.js +232 -0
- package/lib/util/blocklet/did-document.js +240 -0
- package/lib/util/blocklet/environment.js +555 -0
- package/lib/util/blocklet/health-check.js +449 -0
- package/lib/util/blocklet/install-utils.js +365 -0
- package/lib/util/blocklet/logo.js +57 -0
- package/lib/util/blocklet/meta-utils.js +269 -0
- package/lib/util/blocklet/port-manager.js +141 -0
- package/lib/util/blocklet/process-manager.js +504 -0
- package/lib/util/blocklet/runtime-info.js +105 -0
- package/lib/util/blocklet/validation.js +418 -0
- package/lib/util/blocklet.js +98 -3066
- package/lib/util/wallet-app-notification.js +40 -0
- package/package.json +22 -22
|
@@ -458,6 +458,11 @@ const blueGreenStartBlocklet = async (
|
|
|
458
458
|
// 根据情况更新 route table, 会判断只有包含多 interfaces 的 DID 才会更新 route table
|
|
459
459
|
// 如果是蓝绿更新发起的,则不更新 route table,因为蓝绿更新会自动更新 route table
|
|
460
460
|
if (!['true', '1'].includes(process.env.ABT_NODE_DISABLE_BLUE_GREEN) && !ignoreBlockletStartedEvent) {
|
|
461
|
+
// 检查 blocklet 是否仍然存在(可能在异步执行期间被删除)
|
|
462
|
+
if (!(await manager.hasBlocklet({ did }))) {
|
|
463
|
+
logger.warn('skip blueOrGreenStarted event: blocklet no longer exists', { did });
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
461
466
|
const latestBlocklet = await manager.getBlocklet(did, { e2eMode });
|
|
462
467
|
manager.emit(BlockletEvents.blueOrGreenStarted, {
|
|
463
468
|
blocklet: latestBlocklet,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const { DBCache, getAbtNodeRedisAndSQLiteUrl } = require('@abtnode/db-cache');
|
|
2
|
+
|
|
3
|
+
const statusLock = new DBCache(() => ({
|
|
4
|
+
prefix: 'blocklet-status-lock',
|
|
5
|
+
ttl: 120_000,
|
|
6
|
+
...getAbtNodeRedisAndSQLiteUrl(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
const startLock = new DBCache(() => ({
|
|
10
|
+
prefix: 'blocklet-start-lock',
|
|
11
|
+
ttl: process.env.NODE_ENV === 'test' ? 1_000 : 120_000,
|
|
12
|
+
...getAbtNodeRedisAndSQLiteUrl(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
statusLock,
|
|
17
|
+
startLock,
|
|
18
|
+
};
|
package/lib/event/index.js
CHANGED
|
@@ -23,7 +23,7 @@ const {
|
|
|
23
23
|
const { joinURL } = require('ufo');
|
|
24
24
|
const { encode } = require('@abtnode/util/lib/base32');
|
|
25
25
|
const dayjs = require('@abtnode/util/lib/dayjs');
|
|
26
|
-
|
|
26
|
+
const { deleteBlockletCache, clearBlockletInfoCache } = require('@abtnode/util/lib/blocklet-cache');
|
|
27
27
|
const { isWorkerInstance } = require('@abtnode/util/lib/pm2/is-instance-worker');
|
|
28
28
|
const { isInServerlessMode } = require('@abtnode/util/lib/serverless');
|
|
29
29
|
const { NodeMonitSender } = require('../monitor/node-monit-sender');
|
|
@@ -37,12 +37,8 @@ const { getBackupEndpoint, getBackupFilesUrlFromEndpoint, getDIDSpacesUrlFromEnd
|
|
|
37
37
|
const { autoBackupHandlerFactory, autoBackupHandler } = require('./auto-backup-handler');
|
|
38
38
|
|
|
39
39
|
const eventBusHandler = require('../blocklet/webhook/event-bus');
|
|
40
|
-
const {
|
|
41
|
-
|
|
42
|
-
deleteBlockletCache,
|
|
43
|
-
updateDidDocument,
|
|
44
|
-
updateDidDocumentStateOnly,
|
|
45
|
-
} = require('../util/blocklet');
|
|
40
|
+
const { isDevelopmentMode, updateDidDocument, updateDidDocumentStateOnly } = require('../util/blocklet');
|
|
41
|
+
|
|
46
42
|
const { backupBlockletSites, cleanBlockletSitesBackup, rollbackBlockletSites } = require('./util');
|
|
47
43
|
const { ensureBlockletHasMultipleInterfaces } = require('../router/helper');
|
|
48
44
|
const { sendServerlessHeartbeat } = require('../util/launcher');
|
|
@@ -93,7 +89,32 @@ module.exports = ({
|
|
|
93
89
|
}
|
|
94
90
|
};
|
|
95
91
|
|
|
92
|
+
/**
|
|
93
|
+
* 缓存清除事件列表,所有进程(master + workers)都必须处理
|
|
94
|
+
* 触发场景:
|
|
95
|
+
* - blocklet.upgraded: configTheme, blocklet.componentRemoved, blocklet.componentInstalled, installed, removed, spaceConnected
|
|
96
|
+
* - blocklet.blueOrGreenStarted: started,单个和多个的启动
|
|
97
|
+
*/
|
|
98
|
+
[
|
|
99
|
+
BlockletEvents.started,
|
|
100
|
+
BlockletEvents.updated,
|
|
101
|
+
BlockletEvents.upgraded,
|
|
102
|
+
BlockletEvents.blueOrGreenStarted,
|
|
103
|
+
BlockletEvents.stopped,
|
|
104
|
+
BlockletEvents.appDidChanged,
|
|
105
|
+
].forEach((name) => {
|
|
106
|
+
eventHub.on(name, (data) => {
|
|
107
|
+
const did = get(data, 'meta.did');
|
|
108
|
+
if (did) {
|
|
109
|
+
logger.info(`delete blocklet cache on ${name}`, { did });
|
|
110
|
+
deleteBlockletCache(did);
|
|
111
|
+
clearBlockletInfoCache(did);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
96
116
|
// Listen events from eventHub and call eventHandler
|
|
117
|
+
// Only master/primary process handles full event processing to avoid redundant operations
|
|
97
118
|
[...Object.values(BlockletEvents), ...Object.values(TeamEvents), ...Object.values(EVENTS)].forEach((name) => {
|
|
98
119
|
if (isWorkerInstance()) {
|
|
99
120
|
return;
|
|
@@ -111,23 +132,6 @@ module.exports = ({
|
|
|
111
132
|
});
|
|
112
133
|
}
|
|
113
134
|
|
|
114
|
-
// clear blocklet cache
|
|
115
|
-
if (
|
|
116
|
-
[
|
|
117
|
-
BlockletEvents.updated,
|
|
118
|
-
BlockletEvents.started,
|
|
119
|
-
BlockletEvents.removed,
|
|
120
|
-
BlockletEvents.statusChange,
|
|
121
|
-
BlockletEvents.installed,
|
|
122
|
-
].includes(name)
|
|
123
|
-
) {
|
|
124
|
-
const did = get(data, 'meta.did');
|
|
125
|
-
if (did) {
|
|
126
|
-
logger.info(`delete blocklet cache on ${name}`, { did });
|
|
127
|
-
deleteBlockletCache(did);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
135
|
if (typeof eventHandler === 'function') {
|
|
132
136
|
eventHandler({ name, data });
|
|
133
137
|
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Utils Module
|
|
3
|
+
*
|
|
4
|
+
* Functions for app-level utilities like archive creation, SK utilities, and domain handling
|
|
5
|
+
* Logo functions moved to ./logo.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs-extra');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const os = require('node:os');
|
|
11
|
+
const get = require('lodash/get');
|
|
12
|
+
const uniq = require('lodash/uniq');
|
|
13
|
+
const createArchive = require('archiver');
|
|
14
|
+
|
|
15
|
+
const { toAddress } = require('@ocap/util');
|
|
16
|
+
const { getDidDomainForBlocklet } = require('@abtnode/util/lib/get-domain-for-blocklet');
|
|
17
|
+
const { BLOCKLET_CONFIGURABLE_KEY, BLOCKLET_MODES } = require('@blocklet/constant');
|
|
18
|
+
|
|
19
|
+
const { getSlpDid, shouldEnableSlpDomain, getBlockletKnownAs } = require('./did-document');
|
|
20
|
+
|
|
21
|
+
// updateBlockletFallbackLogo, ensureAppLogo
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create a zip archive of a data directory
|
|
25
|
+
* @param {string} dataDir - Directory to archive
|
|
26
|
+
* @param {string} fileName - Output filename
|
|
27
|
+
* @returns {Promise<string>} Path to created archive
|
|
28
|
+
*/
|
|
29
|
+
const createDataArchive = (dataDir, fileName) => {
|
|
30
|
+
const zipPath = path.join(os.tmpdir(), fileName);
|
|
31
|
+
if (fs.existsSync(zipPath)) {
|
|
32
|
+
fs.removeSync(zipPath);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const archive = createArchive('zip', { zlib: { level: 9 } });
|
|
36
|
+
const stream = fs.createWriteStream(zipPath);
|
|
37
|
+
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
archive
|
|
40
|
+
.directory(dataDir, false)
|
|
41
|
+
.on('error', (err) => reject(err))
|
|
42
|
+
.pipe(stream);
|
|
43
|
+
|
|
44
|
+
stream.on('close', () => resolve(zipPath));
|
|
45
|
+
archive.finalize();
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if an app SK is currently in use
|
|
51
|
+
* @param {object} blocklet - Blocklet with environments and migratedFrom
|
|
52
|
+
* @param {string} appSk - App SK to check
|
|
53
|
+
* @returns {boolean}
|
|
54
|
+
*/
|
|
55
|
+
const isBlockletAppSkUsed = ({ environments, migratedFrom = [] }, appSk) => {
|
|
56
|
+
const isUsedInEnv = environments.find((e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK)?.value === appSk;
|
|
57
|
+
const isUsedInHistory = migratedFrom.some((x) => x.appSk === appSk);
|
|
58
|
+
return isUsedInEnv || isUsedInHistory;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if the app SK is being rotated
|
|
63
|
+
* @param {Array} newConfigs - New configuration array
|
|
64
|
+
* @param {Array} oldConfigs - Old configuration array
|
|
65
|
+
* @param {string} externalSk - External SK if any
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
const isRotatingAppSk = (newConfigs, oldConfigs, externalSk) => {
|
|
69
|
+
const newSk = newConfigs.find((x) => BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK === x.key);
|
|
70
|
+
if (!newSk) {
|
|
71
|
+
// If no newSk found, we are not rotating the appSk
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const oldSk = oldConfigs.find((x) => BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK === x.key);
|
|
76
|
+
if (!oldSk) {
|
|
77
|
+
// If we have no oldSk, we are setting the initial appSk for external managed apps
|
|
78
|
+
// If we have no oldSk, but we are not external managed apps, we are rotating the appSk
|
|
79
|
+
return !externalSk;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Otherwise, we must be rotating the appSk
|
|
83
|
+
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
|
|
84
|
+
if (oldSk.value !== newSk.value) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return false;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get blocklet URL for launcher
|
|
93
|
+
* @param {object} options - Options
|
|
94
|
+
* @param {object} options.blocklet - Blocklet object
|
|
95
|
+
* @param {object} options.nodeInfo - Node info
|
|
96
|
+
* @returns {string} Blocklet URL
|
|
97
|
+
*/
|
|
98
|
+
const getBlockletURLForLauncher = ({ blocklet, nodeInfo }) => {
|
|
99
|
+
const enableSlpDomain = shouldEnableSlpDomain(nodeInfo.mode);
|
|
100
|
+
let didDomain = '';
|
|
101
|
+
if (enableSlpDomain) {
|
|
102
|
+
didDomain = getDidDomainForBlocklet({
|
|
103
|
+
did: getSlpDid(nodeInfo.did, blocklet.appPid),
|
|
104
|
+
didDomain: nodeInfo.slpDomain,
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
didDomain = getDidDomainForBlocklet({
|
|
108
|
+
did: blocklet.appPid,
|
|
109
|
+
didDomain: nodeInfo.didDomain,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return `https://${didDomain}`;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get list of DID domains for a blocklet
|
|
118
|
+
* @param {object} blocklet - Blocklet object
|
|
119
|
+
* @param {object} nodeInfo - Node info
|
|
120
|
+
* @returns {Array} Array of domain aliases
|
|
121
|
+
*/
|
|
122
|
+
const getBlockletDidDomainList = (blocklet, nodeInfo) => {
|
|
123
|
+
const domainAliases = [];
|
|
124
|
+
const alsoKnownAs = getBlockletKnownAs(blocklet);
|
|
125
|
+
|
|
126
|
+
const dids = [blocklet.appPid, blocklet.appDid, ...alsoKnownAs].filter(Boolean).map((did) => toAddress(did));
|
|
127
|
+
|
|
128
|
+
uniq(dids).forEach((did) => {
|
|
129
|
+
const domain = getDidDomainForBlocklet({ did, didDomain: nodeInfo.didDomain });
|
|
130
|
+
|
|
131
|
+
domainAliases.push({ value: domain, isProtected: true });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const enableSlpDomain = shouldEnableSlpDomain(nodeInfo.mode);
|
|
135
|
+
if (enableSlpDomain) {
|
|
136
|
+
const slpDid = getSlpDid(nodeInfo.did, blocklet.appPid);
|
|
137
|
+
const domain = getDidDomainForBlocklet({ did: slpDid, didDomain: nodeInfo.slpDomain });
|
|
138
|
+
|
|
139
|
+
domainAliases.push({ value: domain, isProtected: true });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return domainAliases;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get component names with version for display
|
|
147
|
+
* @param {object} app - App blocklet
|
|
148
|
+
* @param {Array} componentDids - Component DIDs
|
|
149
|
+
* @returns {string} Comma-separated component names with versions
|
|
150
|
+
*/
|
|
151
|
+
const getComponentNamesWithVersion = (app = {}, componentDids = []) => {
|
|
152
|
+
const str = uniq(componentDids)
|
|
153
|
+
.map((x) => {
|
|
154
|
+
const component = (app.children || []).find((y) => y.meta.did === x);
|
|
155
|
+
return `${component.meta.title}@${component.meta.version}`;
|
|
156
|
+
})
|
|
157
|
+
.join(', ');
|
|
158
|
+
return str;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Check if blocklet is in development mode
|
|
163
|
+
* @param {import('@blocklet/server-js').BlockletState} blocklet
|
|
164
|
+
* @returns {boolean}
|
|
165
|
+
*/
|
|
166
|
+
const isDevelopmentMode = (blocklet) => blocklet?.mode === BLOCKLET_MODES.DEVELOPMENT;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get hook arguments for a blocklet
|
|
170
|
+
* @param {object} blocklet - Blocklet object
|
|
171
|
+
* @returns {object} Hook arguments
|
|
172
|
+
*/
|
|
173
|
+
const getHookArgs = (blocklet) => ({
|
|
174
|
+
output: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT ? '' : path.join(blocklet.env.logsDir, 'output.log'),
|
|
175
|
+
error: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT ? '' : path.join(blocklet.env.logsDir, 'error.log'),
|
|
176
|
+
timeout:
|
|
177
|
+
Math.max(
|
|
178
|
+
get(blocklet, 'meta.timeout.script', 120),
|
|
179
|
+
...(blocklet?.children || []).map((child) => child.meta?.timeout?.script || 0)
|
|
180
|
+
) * 1000,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
module.exports = {
|
|
184
|
+
createDataArchive,
|
|
185
|
+
isBlockletAppSkUsed,
|
|
186
|
+
isRotatingAppSk,
|
|
187
|
+
getBlockletURLForLauncher,
|
|
188
|
+
getBlockletDidDomainList,
|
|
189
|
+
getComponentNamesWithVersion,
|
|
190
|
+
isDevelopmentMode,
|
|
191
|
+
getHookArgs,
|
|
192
|
+
};
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blocklet Loader Module
|
|
3
|
+
*
|
|
4
|
+
* Functions for loading blocklet state from database and cache
|
|
5
|
+
* Handles blocklet retrieval, caching, and bundle expansion
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs-extra');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const get = require('lodash/get');
|
|
11
|
+
const pick = require('lodash/pick');
|
|
12
|
+
|
|
13
|
+
const { toAddress } = require('@ocap/util');
|
|
14
|
+
const { isValid: isValidDid } = require('@arcblock/did');
|
|
15
|
+
const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet:blocklet-loader');
|
|
16
|
+
const { DBCache, getAbtNodeRedisAndSQLiteUrl } = require('@abtnode/db-cache');
|
|
17
|
+
const { BLOCKLET_CACHE_TTL } = require('@abtnode/constant');
|
|
18
|
+
const { BLOCKLET_BUNDLE_FILE } = require('@blocklet/constant');
|
|
19
|
+
const { parseOptionalComponents } = require('@blocklet/resolver');
|
|
20
|
+
const { forEachBlockletSync, getComponentName } = require('@blocklet/meta/lib/util');
|
|
21
|
+
const { getComponentProcessId } = require('@blocklet/meta/lib/get-component-process-id');
|
|
22
|
+
|
|
23
|
+
const { expandBundle } = require('../index');
|
|
24
|
+
const { getComponentDirs, fillBlockletConfigs } = require('./environment');
|
|
25
|
+
const { formatBlockletTheme } = require('./meta-utils');
|
|
26
|
+
|
|
27
|
+
// Blocklet state cache
|
|
28
|
+
const blockletCache = new DBCache(() => ({
|
|
29
|
+
prefix: 'blocklet-state',
|
|
30
|
+
ttl: BLOCKLET_CACHE_TTL,
|
|
31
|
+
...getAbtNodeRedisAndSQLiteUrl(),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Delete blocklet from cache
|
|
36
|
+
* @param {string} did - Blocklet DID
|
|
37
|
+
*/
|
|
38
|
+
const deleteBlockletCache = async (did) => {
|
|
39
|
+
await blockletCache.del(did);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Ensure blocklet bundle is expanded
|
|
44
|
+
* @param {object} _meta - Blocklet meta (unused)
|
|
45
|
+
* @param {string} appDir - App directory path
|
|
46
|
+
*/
|
|
47
|
+
const ensureBlockletExpanded = async (_meta, appDir) => {
|
|
48
|
+
const bundlePath = path.join(appDir, BLOCKLET_BUNDLE_FILE);
|
|
49
|
+
if (fs.existsSync(bundlePath)) {
|
|
50
|
+
try {
|
|
51
|
+
const nodeModulesPath = path.join(appDir, 'node_modules');
|
|
52
|
+
if (fs.existsSync(nodeModulesPath)) {
|
|
53
|
+
await fs.remove(nodeModulesPath);
|
|
54
|
+
}
|
|
55
|
+
await expandBundle(bundlePath, appDir);
|
|
56
|
+
await fs.remove(bundlePath);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
throw new Error(`Failed to expand blocklet bundle: ${err.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Internal function to get blocklet with full state
|
|
65
|
+
* @param {object} options - Options
|
|
66
|
+
* @returns {Promise<object>} Blocklet object
|
|
67
|
+
*/
|
|
68
|
+
const _getBlocklet = async ({
|
|
69
|
+
did,
|
|
70
|
+
dataDirs,
|
|
71
|
+
states,
|
|
72
|
+
e2eMode = false,
|
|
73
|
+
throwOnNotExist = true,
|
|
74
|
+
ensureIntegrity = false,
|
|
75
|
+
getOptionalComponents = false,
|
|
76
|
+
} = {}) => {
|
|
77
|
+
if (!did) {
|
|
78
|
+
throw new Error('Blocklet did does not exist');
|
|
79
|
+
}
|
|
80
|
+
if (!isValidDid(did)) {
|
|
81
|
+
logger.error('Blocklet did is invalid', { did });
|
|
82
|
+
throw new Error('Blocklet did is invalid');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!dataDirs) {
|
|
86
|
+
throw new Error('dataDirs does not exist');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!states) {
|
|
90
|
+
throw new Error('states does not exist');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const blocklet = await states.blocklet.getBlocklet(did);
|
|
94
|
+
if (!blocklet) {
|
|
95
|
+
if (throwOnNotExist || ensureIntegrity) {
|
|
96
|
+
logger.error('can not find blocklet in database by did', { did });
|
|
97
|
+
throw new Error('can not find blocklet in database by did');
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 优化:并行查询独立数据(只查询一次 extraDoc,然后从内存中同步提取)
|
|
103
|
+
const [extraDoc, nodeInfo, site] = await Promise.all([
|
|
104
|
+
states.blockletExtras.getExtraByDid(blocklet.meta.did),
|
|
105
|
+
states.node.read(),
|
|
106
|
+
states.site.findOneByBlocklet(blocklet.meta.did),
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
// 从 extraDoc 中同步提取 settings(不需要再次查询数据库)
|
|
110
|
+
const extrasMeta = extraDoc ? pick(extraDoc, ['did', 'meta', 'controller']) : null;
|
|
111
|
+
const settings = states.blockletExtras.getFromDoc({ doc: extraDoc, dids: [blocklet.meta.did], name: 'settings' });
|
|
112
|
+
|
|
113
|
+
// app settings
|
|
114
|
+
// FIXME: @zhanghan 在 server 开发模式下,使用 `node /workspace/arcblock/blocklet-server/core/cli/tools/dev.js` 运行的 blocklet,blocklet.meta.did 和 blocklet.appPid 是不一致的
|
|
115
|
+
blocklet.trustedPassports = get(settings, 'trustedPassports') || [];
|
|
116
|
+
blocklet.trustedFactories = (get(settings, 'trustedFactories') || []).map((x) => {
|
|
117
|
+
if (!x.passport.ttlPolicy) {
|
|
118
|
+
x.passport.ttlPolicy = 'never';
|
|
119
|
+
x.passport.ttl = 0;
|
|
120
|
+
}
|
|
121
|
+
if (x.factoryAddress) {
|
|
122
|
+
x.factoryAddress = toAddress(x.factoryAddress);
|
|
123
|
+
}
|
|
124
|
+
return x;
|
|
125
|
+
});
|
|
126
|
+
blocklet.enablePassportIssuance = get(settings, 'enablePassportIssuance', true);
|
|
127
|
+
blocklet.settings = settings || {};
|
|
128
|
+
|
|
129
|
+
if (extrasMeta) {
|
|
130
|
+
blocklet.controller = extrasMeta.controller;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
blocklet.settings.storeList = blocklet.settings.storeList || [];
|
|
134
|
+
blocklet.settings.theme = formatBlockletTheme(blocklet.settings.theme);
|
|
135
|
+
blocklet.settings.languages = blocklet.settings.languages || [];
|
|
136
|
+
|
|
137
|
+
// 移除第一个版本中 from 为 tmpl 的导航
|
|
138
|
+
if (blocklet?.settings?.navigations && Array.isArray(blocklet.settings.navigations)) {
|
|
139
|
+
blocklet.settings.navigations = (blocklet.settings.navigations || []).filter(
|
|
140
|
+
(item) => !(item?.parent === '/team' && ['tmpl'].includes(item.from))
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
(nodeInfo?.blockletRegistryList || []).forEach((store) => {
|
|
145
|
+
if (!blocklet.settings.storeList.find((x) => x.url === store.url)) {
|
|
146
|
+
blocklet.settings.storeList.push({
|
|
147
|
+
...store,
|
|
148
|
+
protected: true,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
blocklet.site = site;
|
|
154
|
+
blocklet.enableDocker = nodeInfo.enableDocker;
|
|
155
|
+
blocklet.enableDockerNetwork = nodeInfo.enableDockerNetwork;
|
|
156
|
+
|
|
157
|
+
// 第一次 forEachBlockletSync:收集所有组件的 dids
|
|
158
|
+
const componentConfigRequests = [];
|
|
159
|
+
forEachBlockletSync(blocklet, (component, { ancestors }) => {
|
|
160
|
+
const dids = [...ancestors.map((x) => x.meta.did), component.meta.did];
|
|
161
|
+
componentConfigRequests.push({
|
|
162
|
+
componentDid: component.meta.did,
|
|
163
|
+
dids,
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// 基于缓存文档,为每个组件提取 configs(同步操作,不需要再次查询数据库)
|
|
168
|
+
const configsMap = new Map();
|
|
169
|
+
componentConfigRequests.forEach(({ componentDid, dids }) => {
|
|
170
|
+
const configs = states.blockletExtras.getFromDoc({ doc: extraDoc, dids, name: 'configs' });
|
|
171
|
+
configsMap.set(componentDid, configs);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// 第二次 forEachBlockletSync:填充组件
|
|
175
|
+
forEachBlockletSync(blocklet, (component, { id, level, ancestors }) => {
|
|
176
|
+
// component env
|
|
177
|
+
try {
|
|
178
|
+
// Validate component has required meta fields for getComponentDirs
|
|
179
|
+
if (!component.meta) {
|
|
180
|
+
throw new Error(`Component missing meta field: ${component.meta?.did || id}`);
|
|
181
|
+
}
|
|
182
|
+
if (!component.meta.name && !component.meta.bundleName) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Component missing meta.name and meta.bundleName: ${component.meta.did || id}. ` +
|
|
185
|
+
'This may indicate a migration issue with blocklet_children table.'
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
component.env = {
|
|
190
|
+
id,
|
|
191
|
+
name: getComponentName(component, ancestors),
|
|
192
|
+
processId: getComponentProcessId(component, ancestors),
|
|
193
|
+
...getComponentDirs(component, {
|
|
194
|
+
dataDirs,
|
|
195
|
+
ensure: ensureIntegrity,
|
|
196
|
+
ancestors,
|
|
197
|
+
e2eMode: level === 0 ? e2eMode : false,
|
|
198
|
+
}),
|
|
199
|
+
};
|
|
200
|
+
} catch (error) {
|
|
201
|
+
logger.error('Failed to set component env in _getBlocklet', {
|
|
202
|
+
componentDid: component.meta?.did,
|
|
203
|
+
componentName: component.meta?.name,
|
|
204
|
+
componentBundleName: component.meta?.bundleName,
|
|
205
|
+
error: error.message,
|
|
206
|
+
stack: error.stack,
|
|
207
|
+
});
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// component config - 从预取的 configsMap 中获取
|
|
212
|
+
const configs = configsMap.get(component.meta.did) || [];
|
|
213
|
+
const rootBlocklet = ancestors.length > 0 ? ancestors[0] : blocklet;
|
|
214
|
+
fillBlockletConfigs(component, configs, { rootBlocklet, nodeInfo, dataDirs });
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (getOptionalComponents) {
|
|
218
|
+
const optionalComponents = await parseOptionalComponents(blocklet);
|
|
219
|
+
blocklet.optionalComponents = optionalComponents;
|
|
220
|
+
} else {
|
|
221
|
+
blocklet.optionalComponents = [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return blocklet;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get blocklet with optional caching
|
|
229
|
+
* @param {object} options - Options
|
|
230
|
+
* @returns {Promise<object>} Blocklet object
|
|
231
|
+
*/
|
|
232
|
+
const getBlocklet = ({
|
|
233
|
+
did,
|
|
234
|
+
ensureIntegrity = false,
|
|
235
|
+
getOptionalComponents = false,
|
|
236
|
+
useCache = false,
|
|
237
|
+
...rest
|
|
238
|
+
} = {}) => {
|
|
239
|
+
let cacheKey = '';
|
|
240
|
+
|
|
241
|
+
if (useCache) {
|
|
242
|
+
cacheKey = JSON.stringify({
|
|
243
|
+
ensureIntegrity,
|
|
244
|
+
getOptionalComponents,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return blockletCache.autoCacheGroup(did, cacheKey, () => {
|
|
249
|
+
return _getBlocklet({ did, ensureIntegrity, getOptionalComponents, ...rest });
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
module.exports = {
|
|
254
|
+
blockletCache,
|
|
255
|
+
deleteBlockletCache,
|
|
256
|
+
ensureBlockletExpanded,
|
|
257
|
+
getBlocklet,
|
|
258
|
+
};
|