@abtnode/core 1.17.8-beta-20260109-075740-5f484e08 → 1.17.8-beta-20260113-015027-32a1cec4
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
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Information Module
|
|
3
|
+
*
|
|
4
|
+
* Functions for getting blocklet runtime and disk information
|
|
5
|
+
* Extracted from blocklet.js for better modularity
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet:runtime-info');
|
|
9
|
+
const getFolderSize = require('@abtnode/util/lib/get-folder-size');
|
|
10
|
+
const { getDisplayName } = require('@blocklet/meta/lib/util');
|
|
11
|
+
|
|
12
|
+
const { getProcessInfo } = require('./process-manager');
|
|
13
|
+
const getDockerRuntimeInfo = require('../docker/get-docker-runtime-info');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Cache for disk info tasks to avoid duplicate calculations
|
|
17
|
+
*/
|
|
18
|
+
const _diskInfoTasks = {};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Internal function to get disk info for a blocklet
|
|
22
|
+
* @param {object} blocklet - Blocklet object with env
|
|
23
|
+
* @returns {Promise<{ app: number, cache: number, log: number, data: number }>}
|
|
24
|
+
*/
|
|
25
|
+
const _getDiskInfo = async (blocklet) => {
|
|
26
|
+
try {
|
|
27
|
+
const { env } = blocklet;
|
|
28
|
+
const [app, cache, log, data] = await Promise.all([
|
|
29
|
+
getFolderSize(env.appDir),
|
|
30
|
+
getFolderSize(env.cacheDir),
|
|
31
|
+
getFolderSize(env.logsDir),
|
|
32
|
+
getFolderSize(env.dataDir),
|
|
33
|
+
]);
|
|
34
|
+
return { app, cache, log, data };
|
|
35
|
+
} catch (error) {
|
|
36
|
+
logger.error('Get disk info failed', { name: getDisplayName(blocklet), error });
|
|
37
|
+
return { app: 0, cache: 0, log: 0, data: 0 };
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get disk info for a blocklet with caching
|
|
43
|
+
* @param {object} blocklet - Blocklet object
|
|
44
|
+
* @param {object} options - Options
|
|
45
|
+
* @param {boolean} options.useFakeDiskInfo - Return zeros instead of actual values
|
|
46
|
+
* @returns {Promise<{ app: number, cache: number, log: number, data: number }>}
|
|
47
|
+
*/
|
|
48
|
+
const getDiskInfo = (blocklet, { useFakeDiskInfo } = {}) => {
|
|
49
|
+
if (useFakeDiskInfo) {
|
|
50
|
+
return { app: 0, cache: 0, log: 0, data: 0 };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { appDid } = blocklet;
|
|
54
|
+
|
|
55
|
+
// Cache disk info results for 5 minutes
|
|
56
|
+
_diskInfoTasks[appDid] ??= _getDiskInfo(blocklet).finally(() => {
|
|
57
|
+
setTimeout(
|
|
58
|
+
() => {
|
|
59
|
+
delete _diskInfoTasks[appDid];
|
|
60
|
+
},
|
|
61
|
+
5 * 60 * 1000
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
_diskInfoTasks[appDid].then(resolve).catch(() => {
|
|
67
|
+
resolve({ app: 0, cache: 0, log: 0, data: 0 });
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get runtime info for a process
|
|
74
|
+
* @param {string} processId - PM2 process ID
|
|
75
|
+
* @returns {Promise<object>} Runtime info including pid, uptime, memory, cpu, status, port
|
|
76
|
+
*/
|
|
77
|
+
const getRuntimeInfo = async (processId) => {
|
|
78
|
+
const proc = await getProcessInfo(processId);
|
|
79
|
+
const dockerName = proc.pm2_env?.env?.dockerName;
|
|
80
|
+
if (dockerName) {
|
|
81
|
+
const dockerInfo = await getDockerRuntimeInfo(dockerName);
|
|
82
|
+
return {
|
|
83
|
+
...dockerInfo,
|
|
84
|
+
pid: proc.pid,
|
|
85
|
+
uptime: proc.pm2_env ? Date.now() - Number(proc.pm2_env.pm_uptime) : 0,
|
|
86
|
+
port: proc.pm2_env ? proc.pm2_env.BLOCKLET_PORT : null,
|
|
87
|
+
status: proc.pm2_env ? proc.pm2_env.status : null,
|
|
88
|
+
runningDocker: !!dockerName,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
pid: proc.pid,
|
|
93
|
+
uptime: proc.pm2_env ? Date.now() - Number(proc.pm2_env.pm_uptime) : 0,
|
|
94
|
+
memoryUsage: proc.monit.memory,
|
|
95
|
+
cpuUsage: proc.monit.cpu,
|
|
96
|
+
status: proc.pm2_env ? proc.pm2_env.status : null,
|
|
97
|
+
port: proc.pm2_env ? proc.pm2_env.BLOCKLET_PORT : null,
|
|
98
|
+
runningDocker: false,
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
module.exports = {
|
|
103
|
+
getDiskInfo,
|
|
104
|
+
getRuntimeInfo,
|
|
105
|
+
};
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Module
|
|
3
|
+
*
|
|
4
|
+
* Functions for validating blocklet configurations, structures, and compatibility
|
|
5
|
+
* Extracted from blocklet.js for better modularity
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const semver = require('semver');
|
|
9
|
+
const isEmpty = require('lodash/isEmpty');
|
|
10
|
+
const isUrl = require('is-url');
|
|
11
|
+
|
|
12
|
+
const { types } = require('@ocap/mcrypto');
|
|
13
|
+
const { fromSecretKey } = require('@ocap/wallet');
|
|
14
|
+
const { isHex } = require('@ocap/util');
|
|
15
|
+
const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet:validation');
|
|
16
|
+
const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
|
|
17
|
+
const { APP_STRUCT_VERSION } = require('@abtnode/constant');
|
|
18
|
+
const { chainInfo: chainInfoSchema } = require('@arcblock/did-connect-js/lib/schema');
|
|
19
|
+
|
|
20
|
+
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
21
|
+
const { titleSchema, descriptionSchema, logoSchema } = require('@blocklet/meta/lib/schema');
|
|
22
|
+
const {
|
|
23
|
+
forEachBlockletSync,
|
|
24
|
+
forEachComponentV2,
|
|
25
|
+
getDisplayName,
|
|
26
|
+
getChainInfo,
|
|
27
|
+
hasStartEngine,
|
|
28
|
+
} = require('@blocklet/meta/lib/util');
|
|
29
|
+
const { getBlockletInfo } = require('@blocklet/meta/lib/info');
|
|
30
|
+
const { hasMountPoint } = require('@blocklet/meta/lib/engine');
|
|
31
|
+
|
|
32
|
+
const formatName = require('@abtnode/util/lib/format-name');
|
|
33
|
+
const isRequirementsSatisfied = require('../requirement');
|
|
34
|
+
const { validate: validateEngine } = require('../../blocklet/manager/engine');
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validate blocklet requirements and engine
|
|
38
|
+
* @param {object} blocklet - Blocklet object
|
|
39
|
+
* @param {Function} getBlockletEngineNameByPlatform - Function to get engine name
|
|
40
|
+
*/
|
|
41
|
+
const validateBlocklet = (blocklet, getBlockletEngineNameByPlatform) =>
|
|
42
|
+
forEachComponentV2(blocklet, (b) => {
|
|
43
|
+
isRequirementsSatisfied(b.meta.requirements);
|
|
44
|
+
validateEngine(getBlockletEngineNameByPlatform(b.meta));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validate blocklet chain info configuration
|
|
49
|
+
* @param {object} blocklet - Blocklet object with configObj
|
|
50
|
+
* @returns {object} Validated chain info
|
|
51
|
+
*/
|
|
52
|
+
const validateBlockletChainInfo = (blocklet) => {
|
|
53
|
+
const chainInfo = getChainInfo({
|
|
54
|
+
CHAIN_TYPE: blocklet.configObj[BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_CHAIN_TYPE],
|
|
55
|
+
CHAIN_ID: blocklet.configObj[BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_CHAIN_ID],
|
|
56
|
+
CHAIN_HOST: blocklet.configObj[BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_CHAIN_HOST],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const { error } = chainInfoSchema.validate(chainInfo);
|
|
60
|
+
if (error) {
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return chainInfo;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check for duplicate components
|
|
69
|
+
* @param {Array} components - Array of components
|
|
70
|
+
*/
|
|
71
|
+
const checkDuplicateComponents = (components = []) => {
|
|
72
|
+
const duplicates = components.filter(
|
|
73
|
+
(item, index) => components.findIndex((x) => x.meta.did === item.meta.did) !== index
|
|
74
|
+
);
|
|
75
|
+
if (duplicates.length) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Cannot add duplicate component${duplicates.length > 1 ? 's' : ''}: ${duplicates
|
|
78
|
+
.map((x) => getDisplayName(x, true))
|
|
79
|
+
.join(', ')}`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Validate app config
|
|
86
|
+
* @param {object} config - Config object
|
|
87
|
+
* @param {object} states - State manager
|
|
88
|
+
*/
|
|
89
|
+
const validateAppConfig = async (config, states) => {
|
|
90
|
+
const x = config;
|
|
91
|
+
|
|
92
|
+
// sk should be force secured while other app prop should not be secured
|
|
93
|
+
config.secure = x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK;
|
|
94
|
+
|
|
95
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK) {
|
|
96
|
+
if (x.value) {
|
|
97
|
+
let wallet;
|
|
98
|
+
try {
|
|
99
|
+
wallet = fromSecretKey(x.value, { role: types.RoleType.ROLE_APPLICATION });
|
|
100
|
+
} catch {
|
|
101
|
+
try {
|
|
102
|
+
wallet = fromSecretKey(x.value, 'eth');
|
|
103
|
+
} catch {
|
|
104
|
+
throw new Error('Invalid custom blocklet secret key');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Ensure sk is not used by existing blocklets, otherwise we may encounter appDid collision
|
|
109
|
+
const exist = await states.blocklet.hasBlocklet(wallet.address);
|
|
110
|
+
if (exist) {
|
|
111
|
+
throw new Error('Invalid custom blocklet secret key: already used by existing blocklet');
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
delete x.value;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_URL) {
|
|
119
|
+
if (isEmpty(x.value)) {
|
|
120
|
+
throw new Error(`${x.key} can not be empty`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!isUrl(x.value)) {
|
|
124
|
+
throw new Error(`${x.key}(${x.value}) is not a valid URL`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_NAME) {
|
|
129
|
+
x.value = await titleSchema.validateAsync(x.value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_DESCRIPTION) {
|
|
133
|
+
x.value = await descriptionSchema.validateAsync(x.value);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
[
|
|
138
|
+
BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO,
|
|
139
|
+
BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_RECT,
|
|
140
|
+
BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_RECT_DARK,
|
|
141
|
+
BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_SQUARE,
|
|
142
|
+
BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_SQUARE_DARK,
|
|
143
|
+
BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_FAVICON,
|
|
144
|
+
BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPLASH_PORTRAIT,
|
|
145
|
+
BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPLASH_LANDSCAPE,
|
|
146
|
+
].includes(x.key)
|
|
147
|
+
) {
|
|
148
|
+
x.value = await logoSchema.validateAsync(x.value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_CHAIN_TYPE) {
|
|
152
|
+
if (['arcblock', 'ethereum'].includes(x.value) === false) {
|
|
153
|
+
throw new Error('Invalid blocklet wallet type, only "default" and "eth" are supported');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_DELETABLE) {
|
|
158
|
+
if (['yes', 'no'].includes(x.value) === false) {
|
|
159
|
+
throw new Error('BLOCKLET_DELETABLE must be either "yes" or "no"');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_PASSPORT_COLOR) {
|
|
164
|
+
if (x.value && x.value !== 'auto') {
|
|
165
|
+
if (x.value.length !== 7 || !isHex(x.value.slice(-6))) {
|
|
166
|
+
throw new Error('BLOCKLET_PASSPORT_COLOR must be a hex encoded color, eg. #ffeeaa');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT) {
|
|
172
|
+
// @note: value 置空以表删除
|
|
173
|
+
if (x.value && !isUrl(x.value)) {
|
|
174
|
+
throw new Error(`${x.key}(${x.value}) is not a valid URL`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACES_URL) {
|
|
179
|
+
if (isEmpty(x.value)) {
|
|
180
|
+
throw new Error(`${x.key} can not be empty`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!isUrl(x.value)) {
|
|
184
|
+
throw new Error(`${x.key}(${x.value}) is not a valid URL`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_BACKUP_ENDPOINT) {
|
|
189
|
+
// @note: value 置空以表删除
|
|
190
|
+
if (isEmpty(x.value)) {
|
|
191
|
+
x.value = '';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!isEmpty(x.value) && !isUrl(x.value)) {
|
|
195
|
+
throw new Error(`${x.key}(${x.value}) is not a valid URL`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_CLUSTER_SIZE) {
|
|
200
|
+
if (isEmpty(x.value)) {
|
|
201
|
+
x.value = '';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const v = Number(x.value);
|
|
205
|
+
if (Number.isNaN(v)) {
|
|
206
|
+
throw new Error(`${x.key} must be number`);
|
|
207
|
+
}
|
|
208
|
+
if (!Number.isInteger(v)) {
|
|
209
|
+
throw new Error(`${x.key} must be integer`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Check for duplicate app secret key
|
|
216
|
+
* @param {object} options - Options
|
|
217
|
+
* @param {string} options.sk - Secret key
|
|
218
|
+
* @param {string} options.did - Blocklet DID
|
|
219
|
+
* @param {object} options.states - State manager
|
|
220
|
+
*/
|
|
221
|
+
const checkDuplicateAppSk = async ({ sk, did, states }) => {
|
|
222
|
+
if (!sk && !did) {
|
|
223
|
+
throw new Error('sk and did is empty');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let appSk = sk;
|
|
227
|
+
if (!sk) {
|
|
228
|
+
const nodeInfo = await states.node.read();
|
|
229
|
+
const blocklet = await states.blocklet.getBlocklet(did);
|
|
230
|
+
const configs = await states.blockletExtras.getConfigs([did]);
|
|
231
|
+
const { wallet } = getBlockletInfo(
|
|
232
|
+
{
|
|
233
|
+
meta: blocklet.meta,
|
|
234
|
+
environments: (configs || []).filter((x) => x.value),
|
|
235
|
+
},
|
|
236
|
+
nodeInfo.sk
|
|
237
|
+
);
|
|
238
|
+
appSk = wallet.secretKey;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let wallet;
|
|
242
|
+
try {
|
|
243
|
+
wallet = fromSecretKey(appSk, { role: types.RoleType.ROLE_APPLICATION });
|
|
244
|
+
} catch {
|
|
245
|
+
try {
|
|
246
|
+
wallet = fromSecretKey(appSk, 'eth');
|
|
247
|
+
} catch {
|
|
248
|
+
throw new Error('Invalid custom blocklet secret key');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const exist = await states.blocklet.hasBlocklet(wallet.address);
|
|
253
|
+
if (exist) {
|
|
254
|
+
throw new Error(`blocklet secret key already used by ${exist.meta.title || exist.meta.name}`);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check for duplicate mount point
|
|
260
|
+
* @param {object} app - App blocklet
|
|
261
|
+
* @param {string} mountPoint - Mount point to check
|
|
262
|
+
*/
|
|
263
|
+
const checkDuplicateMountPoint = (app, mountPoint) => {
|
|
264
|
+
const err = new Error(`cannot add duplicate mount point, ${mountPoint || '/'} already exist`);
|
|
265
|
+
|
|
266
|
+
for (const component of app.children || []) {
|
|
267
|
+
if (
|
|
268
|
+
hasStartEngine(component.meta) &&
|
|
269
|
+
normalizePathPrefix(component.mountPoint) === normalizePathPrefix(mountPoint)
|
|
270
|
+
) {
|
|
271
|
+
throw err;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Resolve mount point conflict by generating alternative mount point
|
|
278
|
+
* @param {object} comp - Component
|
|
279
|
+
* @param {object} blocklet - Parent blocklet
|
|
280
|
+
* @returns {object} Component with resolved mount point
|
|
281
|
+
*/
|
|
282
|
+
const resolveMountPointConflict = (comp, blocklet) => {
|
|
283
|
+
try {
|
|
284
|
+
if (!comp?.mountPoint) return comp;
|
|
285
|
+
|
|
286
|
+
const children = (blocklet?.children || []).filter((x) => x?.meta && hasMountPoint(x.meta));
|
|
287
|
+
|
|
288
|
+
const existingComponent = children.find((x) => x.mountPoint === comp.mountPoint && x.meta?.did !== comp.meta?.did);
|
|
289
|
+
if (!existingComponent) return comp;
|
|
290
|
+
|
|
291
|
+
const baseName = formatName(comp?.meta?.name) || formatName(comp?.meta?.title);
|
|
292
|
+
comp.mountPoint = children.some((x) => x.mountPoint === `/${baseName}`)
|
|
293
|
+
? comp.meta.did
|
|
294
|
+
: baseName.toLocaleLowerCase();
|
|
295
|
+
|
|
296
|
+
return comp;
|
|
297
|
+
} catch (error) {
|
|
298
|
+
logger.error('Failed to resolve mount point:', error);
|
|
299
|
+
comp.mountPoint = comp.meta.did;
|
|
300
|
+
return comp;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Validate store URL in serverless mode
|
|
306
|
+
* @param {object} nodeInfo - Node info
|
|
307
|
+
* @param {string} storeUrl - Store URL
|
|
308
|
+
*/
|
|
309
|
+
const validateStore = (nodeInfo, storeUrl) => {
|
|
310
|
+
if (nodeInfo.mode !== 'serverless') {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const storeUrlObj = new URL(storeUrl);
|
|
315
|
+
|
|
316
|
+
// Check trusted blocklet sources from environment variable first
|
|
317
|
+
const trustedSources = process.env.ABT_NODE_TRUSTED_SOURCES;
|
|
318
|
+
if (trustedSources) {
|
|
319
|
+
const trustedHosts = trustedSources
|
|
320
|
+
.split(',')
|
|
321
|
+
.map((url) => url.trim())
|
|
322
|
+
.filter(Boolean)
|
|
323
|
+
.map((url) => new URL(url).host);
|
|
324
|
+
|
|
325
|
+
if (trustedHosts.includes(storeUrlObj.host)) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const registerUrlObj = new URL(nodeInfo.registerUrl);
|
|
331
|
+
|
|
332
|
+
// 信任 Launcher 打包的应用
|
|
333
|
+
if (registerUrlObj.host === storeUrlObj.host) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const inStoreList = nodeInfo.blockletRegistryList.find((item) => {
|
|
338
|
+
const itemURLObj = new URL(item.url);
|
|
339
|
+
|
|
340
|
+
return itemURLObj.host === storeUrlObj.host;
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
if (!inStoreList) {
|
|
344
|
+
throw new Error('Must be installed from the compliant blocklet store list');
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Validate blocklet in serverless mode
|
|
350
|
+
* @param {object} options - Options
|
|
351
|
+
* @param {object} options.blockletMeta - Blocklet meta
|
|
352
|
+
*/
|
|
353
|
+
const validateInServerless = ({ blockletMeta }) => {
|
|
354
|
+
const { interfaces } = blockletMeta;
|
|
355
|
+
const externalPortInterfaces = (interfaces || []).filter((item) => !!item.port?.external);
|
|
356
|
+
|
|
357
|
+
if (externalPortInterfaces.length > 0) {
|
|
358
|
+
throw new Error('Blocklets with exposed ports cannot be installed');
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Check struct version
|
|
364
|
+
* @param {object} blocklet - Blocklet object
|
|
365
|
+
*/
|
|
366
|
+
const checkStructVersion = (blocklet) => {
|
|
367
|
+
if (blocklet.structVersion !== APP_STRUCT_VERSION) {
|
|
368
|
+
throw new Error('You should migrate the application first');
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Check if version is compatible
|
|
374
|
+
* @param {string} actualVersion - Actual version
|
|
375
|
+
* @param {string} expectedRange - Expected version range
|
|
376
|
+
* @returns {boolean}
|
|
377
|
+
*/
|
|
378
|
+
const isVersionCompatible = (actualVersion, expectedRange) =>
|
|
379
|
+
!expectedRange || expectedRange === 'latest' || semver.satisfies(actualVersion, expectedRange);
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Check version compatibility of components
|
|
383
|
+
* @param {Array} components - Array of components
|
|
384
|
+
*/
|
|
385
|
+
const checkVersionCompatibility = (components) => {
|
|
386
|
+
for (const component of components) {
|
|
387
|
+
// eslint-disable-next-line no-loop-func
|
|
388
|
+
forEachBlockletSync(component, (x) => {
|
|
389
|
+
const dependencies = x.dependencies || [];
|
|
390
|
+
dependencies.forEach((dep) => {
|
|
391
|
+
const { did, version: expectedRange } = dep;
|
|
392
|
+
const exist = components.find((y) => y.meta.did === did);
|
|
393
|
+
if (exist && !isVersionCompatible(exist.meta.version, expectedRange)) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
`Check version compatible failed: ${component.meta.title || component.meta.did} expects ${
|
|
396
|
+
exist.meta.title || exist.meta.did
|
|
397
|
+
}'s version to be ${expectedRange}, but actual is ${exist.meta.version}`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
module.exports = {
|
|
406
|
+
validateBlocklet,
|
|
407
|
+
validateBlockletChainInfo,
|
|
408
|
+
checkDuplicateComponents,
|
|
409
|
+
validateAppConfig,
|
|
410
|
+
checkDuplicateAppSk,
|
|
411
|
+
checkDuplicateMountPoint,
|
|
412
|
+
resolveMountPointConflict,
|
|
413
|
+
validateStore,
|
|
414
|
+
validateInServerless,
|
|
415
|
+
checkStructVersion,
|
|
416
|
+
isVersionCompatible,
|
|
417
|
+
checkVersionCompatibility,
|
|
418
|
+
};
|