@abtnode/core 1.17.7-beta-20251227-001958-ea2ba3f5 → 1.17.7-beta-20251229-085620-84f09930
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/blocklet/manager/disk.js +73 -32
- package/lib/blocklet/manager/ensure-blocklet-running.js +1 -1
- package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +1 -1
- package/lib/blocklet/manager/helper/install-application-from-general.js +2 -3
- package/lib/blocklet/manager/helper/install-component-from-url.js +7 -4
- package/lib/blocklet/migration-dist/migration.cjs +5 -4
- package/lib/blocklet/passport/index.js +10 -3
- package/lib/blocklet/project/index.js +7 -2
- package/lib/blocklet/security/index.js +2 -2
- package/lib/cert.js +6 -3
- package/lib/event/index.js +98 -87
- package/lib/event/util.js +7 -13
- package/lib/index.js +15 -26
- package/lib/migrations/1.5.0-site.js +3 -7
- package/lib/migrations/1.5.15-site.js +3 -7
- package/lib/monitor/blocklet-runtime-monitor.js +37 -5
- package/lib/monitor/node-runtime-monitor.js +4 -4
- package/lib/router/helper.js +525 -452
- package/lib/router/index.js +280 -104
- package/lib/router/manager.js +14 -28
- package/lib/states/blocklet-child.js +93 -1
- package/lib/states/blocklet-extras.js +1 -1
- package/lib/states/blocklet.js +429 -197
- package/lib/states/node.js +0 -10
- package/lib/states/site.js +87 -4
- package/lib/team/manager.js +2 -21
- package/lib/util/blocklet.js +39 -19
- package/lib/util/get-accessible-external-node-ip.js +21 -6
- package/lib/util/index.js +3 -3
- package/lib/util/ip.js +15 -1
- package/lib/util/launcher.js +11 -11
- package/lib/util/ready.js +2 -9
- package/lib/util/reset-node.js +6 -5
- package/lib/validators/router.js +0 -3
- package/lib/webhook/sender/api/index.js +5 -0
- package/package.json +23 -25
- package/lib/migrations/1.0.36-snapshot.js +0 -10
- package/lib/migrations/1.1.9-snapshot.js +0 -7
- package/lib/states/routing-snapshot.js +0 -146
package/lib/states/node.js
CHANGED
|
@@ -200,16 +200,6 @@ class NodeState extends BaseState {
|
|
|
200
200
|
return doc;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
async updateNodeRouting(entity = {}) {
|
|
204
|
-
if (isEmpty(entity)) {
|
|
205
|
-
throw new CustomError(400, 'empty entity');
|
|
206
|
-
}
|
|
207
|
-
const nodeInfo = await this.updateNodeInfo({ routing: entity });
|
|
208
|
-
this.emit(EVENTS.ROUTING_UPDATED, nodeInfo);
|
|
209
|
-
|
|
210
|
-
return nodeInfo;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
203
|
cleanupDirtyMaintainState() {
|
|
214
204
|
return this.read().then((doc) => {
|
|
215
205
|
if (doc.nextVersion && semver.lte(doc.nextVersion, doc.version)) {
|
package/lib/states/site.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const logger = require('@abtnode/logger')('@abtnode/core:states:site');
|
|
2
|
+
const { Op, Sequelize } = require('sequelize');
|
|
3
|
+
const { BLOCKLET_SITE_GROUP_SUFFIX } = require('@abtnode/constant');
|
|
2
4
|
|
|
3
5
|
const BaseState = require('./base');
|
|
4
6
|
const { getBlockletDomainGroupName } = require('../util/router');
|
|
@@ -46,6 +48,17 @@ class SiteState extends BaseState {
|
|
|
46
48
|
return SiteState.renameIdFiledName(result);
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
async getSystemSites() {
|
|
52
|
+
const result = await this.find({
|
|
53
|
+
where: {
|
|
54
|
+
domain: {
|
|
55
|
+
[Op.notLike]: `%${BLOCKLET_SITE_GROUP_SUFFIX}`,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
return SiteState.renameIdFiledName(result);
|
|
60
|
+
}
|
|
61
|
+
|
|
49
62
|
async getSitesByBlocklet(did) {
|
|
50
63
|
const sites = await this.getSites();
|
|
51
64
|
return sites.filter((x) => x.rules.some((r) => r.to?.did === did));
|
|
@@ -74,14 +87,84 @@ class SiteState extends BaseState {
|
|
|
74
87
|
return site.rules.find((x) => x.id === id);
|
|
75
88
|
}
|
|
76
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Check if domain exists as primary domain or alias
|
|
92
|
+
* Optimized: O(1) for primary domain, efficient DB query for alias
|
|
93
|
+
* - PostgreSQL: Uses jsonb @> operator (can leverage GIN index)
|
|
94
|
+
* - SQLite: Uses json_each and json_extract for reliable array element search
|
|
95
|
+
*/
|
|
77
96
|
async domainExists(domain) {
|
|
78
|
-
|
|
79
|
-
|
|
97
|
+
// Fast path: check if domain is a primary domain (indexed query)
|
|
98
|
+
const byPrimaryDomain = await this.findOne({ domain });
|
|
99
|
+
if (byPrimaryDomain) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const dialect = this.model.sequelize.getDialect();
|
|
104
|
+
if (dialect === 'postgres') {
|
|
105
|
+
// PostgreSQL: Use jsonb @> operator for efficient array containment check
|
|
106
|
+
// This can leverage GIN index on domainAliases if available
|
|
107
|
+
// Use JSON.stringify to safely construct JSON, then escape single quotes for SQL
|
|
108
|
+
const searchJson = JSON.stringify([{ value: domain }]).replace(/'/g, "''");
|
|
109
|
+
const count = await this.count({
|
|
110
|
+
where: Sequelize.literal(`"domainAliases" @> '${searchJson}'::jsonb`),
|
|
111
|
+
});
|
|
112
|
+
return count > 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// SQLite: Use json_each and json_extract for reliable array element search
|
|
116
|
+
// This avoids issues with JSON formatting/spacing in LIKE queries
|
|
117
|
+
const escapedDomain = domain.replace(/'/g, "''");
|
|
118
|
+
const count = await this.count({
|
|
119
|
+
where: Sequelize.literal(`
|
|
120
|
+
EXISTS (
|
|
121
|
+
SELECT 1 FROM json_each("domainAliases")
|
|
122
|
+
WHERE json_extract(value, '$.value') = '${escapedDomain}'
|
|
123
|
+
)
|
|
124
|
+
`),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return count > 0;
|
|
80
128
|
}
|
|
81
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Find site by domain alias
|
|
132
|
+
* Optimized: O(1) for primary domain, efficient DB query for alias
|
|
133
|
+
* - PostgreSQL: Uses jsonb @> operator (can leverage GIN index)
|
|
134
|
+
* - SQLite: Uses json_each and json_extract for reliable array element search
|
|
135
|
+
*/
|
|
82
136
|
async findByDomainAlias(domain) {
|
|
83
|
-
|
|
84
|
-
|
|
137
|
+
// Fast path: check if it's actually a primary domain
|
|
138
|
+
const byPrimaryDomain = await this.findOne({ domain });
|
|
139
|
+
if (byPrimaryDomain) {
|
|
140
|
+
return byPrimaryDomain;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const dialect = this.model.sequelize.getDialect();
|
|
144
|
+
if (dialect === 'postgres') {
|
|
145
|
+
// PostgreSQL: Use jsonb @> operator for efficient array containment check
|
|
146
|
+
// This can leverage GIN index on domainAliases if available
|
|
147
|
+
// Use JSON.stringify to safely construct JSON, then escape single quotes for SQL
|
|
148
|
+
const searchJson = JSON.stringify([{ value: domain }]).replace(/'/g, "''");
|
|
149
|
+
const sites = await this.find({
|
|
150
|
+
where: Sequelize.literal(`"domainAliases" @> '${searchJson}'::jsonb`),
|
|
151
|
+
});
|
|
152
|
+
return sites[0] || null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// SQLite: Use json_each and json_extract for reliable array element search
|
|
156
|
+
// This avoids issues with JSON formatting/spacing in LIKE queries
|
|
157
|
+
const escapedDomain = domain.replace(/'/g, "''");
|
|
158
|
+
const sites = await this.find({
|
|
159
|
+
where: Sequelize.literal(`
|
|
160
|
+
EXISTS (
|
|
161
|
+
SELECT 1 FROM json_each("domainAliases")
|
|
162
|
+
WHERE json_extract(value, '$.value') = '${escapedDomain}'
|
|
163
|
+
)
|
|
164
|
+
`),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return sites[0] || null;
|
|
85
168
|
}
|
|
86
169
|
|
|
87
170
|
findOneByBlocklet(did) {
|
package/lib/team/manager.js
CHANGED
|
@@ -118,25 +118,6 @@ class TeamManager extends EventEmitter {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
async init() {
|
|
121
|
-
// listen blocklet state
|
|
122
|
-
['add', 'upgrade'].forEach((event) => {
|
|
123
|
-
this.states.blocklet.on(event, ({ meta: { did } }) => {
|
|
124
|
-
this.cache[did] = getDefaultTeamState();
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// init blocklet
|
|
129
|
-
this.states.blocklet
|
|
130
|
-
.getBlocklets()
|
|
131
|
-
.then((blocklets) => {
|
|
132
|
-
blocklets.forEach(({ meta: { did } }) => {
|
|
133
|
-
this.cache[did] = getDefaultTeamState();
|
|
134
|
-
});
|
|
135
|
-
})
|
|
136
|
-
.catch((error) => {
|
|
137
|
-
logger.error('get blocklets failed', error);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
121
|
// init server
|
|
141
122
|
this.cache[this.nodeDid] = getDefaultTeamState();
|
|
142
123
|
logger.info('init node team manager', { nodeDid: this.nodeDid });
|
|
@@ -347,7 +328,7 @@ class TeamManager extends EventEmitter {
|
|
|
347
328
|
}
|
|
348
329
|
}
|
|
349
330
|
|
|
350
|
-
const domains = await getDomainsByDid(isServices ? teamDid : this.nodeDid);
|
|
331
|
+
const domains = await getDomainsByDid(isServices ? teamDid : this.nodeDid, this);
|
|
351
332
|
const customDomains = domains.filter((d) => isCustomDomain(d));
|
|
352
333
|
|
|
353
334
|
// 优先显示自定义域名
|
|
@@ -1035,7 +1016,7 @@ class TeamManager extends EventEmitter {
|
|
|
1035
1016
|
const dbPath = await this.getDataFileByDid(did);
|
|
1036
1017
|
logger.info('initDatabase', { did, dbPath });
|
|
1037
1018
|
try {
|
|
1038
|
-
await doSchemaMigration(dbPath, 'blocklet');
|
|
1019
|
+
await doSchemaMigration(dbPath, 'blocklet', false, `blocklet:${did}`);
|
|
1039
1020
|
} catch (error) {
|
|
1040
1021
|
// This error is not fatal, just log it, will happen when there are multiple service processes
|
|
1041
1022
|
logger.error('initDatabase failed', { did, dbPath, error });
|
package/lib/util/blocklet.js
CHANGED
|
@@ -25,6 +25,7 @@ const isUrl = require('is-url');
|
|
|
25
25
|
const semver = require('semver');
|
|
26
26
|
const { chainInfo: chainInfoSchema } = require('@arcblock/did-connect-js/lib/schema');
|
|
27
27
|
|
|
28
|
+
const { types } = require('@ocap/mcrypto');
|
|
28
29
|
const { urlPathFriendly } = require('@blocklet/meta/lib/url-path-friendly');
|
|
29
30
|
const { fromSecretKey, fromPublicKey } = require('@ocap/wallet');
|
|
30
31
|
const { toHex, isHex, toDid, toAddress, toBuffer } = require('@ocap/util');
|
|
@@ -65,7 +66,7 @@ const { getComponentApiKey } = require('@abtnode/util/lib/blocklet');
|
|
|
65
66
|
const { toSvg: createDidLogo } = require('@arcblock/did-motif');
|
|
66
67
|
const { createBlockiesSvg } = require('@blocklet/meta/lib/blockies');
|
|
67
68
|
const formatName = require('@abtnode/util/lib/format-name');
|
|
68
|
-
const { hasMountPoint } = require('@blocklet/meta/lib/engine');
|
|
69
|
+
const { hasMountPoint, getBlockletEngine } = require('@blocklet/meta/lib/engine');
|
|
69
70
|
const { fixAvatar } = require('@blocklet/sdk/lib/util/user');
|
|
70
71
|
|
|
71
72
|
const SCRIPT_ENGINES_WHITE_LIST = ['npm', 'npx', 'pnpm', 'yarn'];
|
|
@@ -87,9 +88,9 @@ const {
|
|
|
87
88
|
BLOCKLET_TENANT_MODES,
|
|
88
89
|
PROJECT,
|
|
89
90
|
BLOCKLET_INTERFACE_TYPE_DOCKER,
|
|
91
|
+
STATIC_SERVER_ENGINE_DID,
|
|
90
92
|
} = require('@blocklet/constant');
|
|
91
93
|
const { validateBlockletEntry } = require('@blocklet/meta/lib/entry');
|
|
92
|
-
const { getBlockletEngine } = require('@blocklet/meta/lib/engine');
|
|
93
94
|
const { getBlockletInfo } = require('@blocklet/meta/lib/info');
|
|
94
95
|
const { getApplicationWallet: getBlockletWallet } = require('@blocklet/meta/lib/wallet');
|
|
95
96
|
const {
|
|
@@ -360,7 +361,7 @@ const getComponentStartEngine = (component, { e2eMode = false } = {}) => {
|
|
|
360
361
|
const cwd = appDir;
|
|
361
362
|
|
|
362
363
|
// get app dirs
|
|
363
|
-
const {
|
|
364
|
+
const { group } = component.meta;
|
|
364
365
|
|
|
365
366
|
let startFromDevEntry = '';
|
|
366
367
|
if (component.mode === BLOCKLET_MODES.DEVELOPMENT && component.meta.scripts) {
|
|
@@ -386,9 +387,6 @@ const getComponentStartEngine = (component, { e2eMode = false } = {}) => {
|
|
|
386
387
|
} else if (group === 'dapp') {
|
|
387
388
|
script = blockletEngineInfo.source || BLOCKLET_ENTRY_FILE;
|
|
388
389
|
args = blockletEngineInfo.args || [];
|
|
389
|
-
} else if (group === 'static') {
|
|
390
|
-
script = require.resolve('@abtnode/static-server');
|
|
391
|
-
environmentObj.BLOCKLET_MAIN_DIR = path.join(appDir, main);
|
|
392
390
|
}
|
|
393
391
|
|
|
394
392
|
if (component.mode !== BLOCKLET_MODES.DEVELOPMENT) {
|
|
@@ -1116,6 +1114,16 @@ const deleteBlockletProcess = async (
|
|
|
1116
1114
|
if (!hasStartEngine(b.meta)) {
|
|
1117
1115
|
return;
|
|
1118
1116
|
}
|
|
1117
|
+
|
|
1118
|
+
// Skip deleting static-server engine processes since they were never started
|
|
1119
|
+
if (b.meta?.group === 'static') {
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
const engine = getBlockletEngine(b.meta);
|
|
1123
|
+
if (engine.interpreter === 'blocklet' && engine.source?.name === STATIC_SERVER_ENGINE_DID) {
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1119
1127
|
await preDelete(b, { ancestors });
|
|
1120
1128
|
if (isStopGreenAndBlue) {
|
|
1121
1129
|
// eslint-disable-next-line no-use-before-define
|
|
@@ -1150,6 +1158,15 @@ const reloadBlockletProcess = (blocklet, { componentDids } = {}) =>
|
|
|
1150
1158
|
return;
|
|
1151
1159
|
}
|
|
1152
1160
|
|
|
1161
|
+
// Skip reloading static-server engine processes since they were never started
|
|
1162
|
+
if (b.meta?.group === 'static') {
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
const engine = getBlockletEngine(b.meta);
|
|
1166
|
+
if (engine.interpreter === 'blocklet' && engine.source?.name === STATIC_SERVER_ENGINE_DID) {
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1153
1170
|
// eslint-disable-next-line no-use-before-define
|
|
1154
1171
|
await reloadProcess(b.env.processId);
|
|
1155
1172
|
logger.info('done reload process', { processId: b.env.processId });
|
|
@@ -2244,19 +2261,20 @@ const validateAppConfig = async (config, states) => {
|
|
|
2244
2261
|
|
|
2245
2262
|
if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK) {
|
|
2246
2263
|
if (x.value) {
|
|
2264
|
+
let wallet;
|
|
2247
2265
|
try {
|
|
2248
|
-
fromSecretKey(x.value);
|
|
2266
|
+
wallet = fromSecretKey(x.value, { role: types.RoleType.ROLE_APPLICATION });
|
|
2249
2267
|
} catch {
|
|
2250
2268
|
try {
|
|
2251
|
-
fromSecretKey(x.value, 'eth');
|
|
2269
|
+
wallet = fromSecretKey(x.value, 'eth');
|
|
2252
2270
|
} catch {
|
|
2253
2271
|
throw new Error('Invalid custom blocklet secret key');
|
|
2254
2272
|
}
|
|
2255
2273
|
}
|
|
2256
2274
|
|
|
2257
2275
|
// Ensure sk is not used by existing blocklets, otherwise we may encounter appDid collision
|
|
2258
|
-
const
|
|
2259
|
-
if (
|
|
2276
|
+
const exist = await states.blocklet.hasBlocklet(wallet.address);
|
|
2277
|
+
if (exist) {
|
|
2260
2278
|
throw new Error('Invalid custom blocklet secret key: already used by existing blocklet');
|
|
2261
2279
|
}
|
|
2262
2280
|
} else {
|
|
@@ -2380,14 +2398,18 @@ const checkDuplicateAppSk = async ({ sk, did, states }) => {
|
|
|
2380
2398
|
appSk = wallet.secretKey;
|
|
2381
2399
|
}
|
|
2382
2400
|
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2401
|
+
let wallet;
|
|
2402
|
+
try {
|
|
2403
|
+
wallet = fromSecretKey(appSk, { role: types.RoleType.ROLE_APPLICATION });
|
|
2404
|
+
} catch {
|
|
2405
|
+
try {
|
|
2406
|
+
wallet = fromSecretKey(appSk, 'eth');
|
|
2407
|
+
} catch {
|
|
2408
|
+
throw new Error('Invalid custom blocklet secret key');
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2390
2411
|
|
|
2412
|
+
const exist = await states.blocklet.hasBlocklet(wallet.address);
|
|
2391
2413
|
if (exist) {
|
|
2392
2414
|
throw new Error(`blocklet secret key already used by ${exist.meta.title || exist.meta.name}`);
|
|
2393
2415
|
}
|
|
@@ -2898,8 +2920,6 @@ const updateDidDocument = async ({ did, nodeInfo, teamManager, states }) => {
|
|
|
2898
2920
|
blocklet.settings = await states.blockletExtras.getSettings(did);
|
|
2899
2921
|
blocklet.controller = blockletExtra?.controller;
|
|
2900
2922
|
|
|
2901
|
-
logger.debug('update did document', { blocklet });
|
|
2902
|
-
|
|
2903
2923
|
const ownerDid = blocklet.settings?.owner?.did;
|
|
2904
2924
|
let ownerInfo;
|
|
2905
2925
|
if (ownerDid) {
|
|
@@ -9,7 +9,8 @@ const getNodeDomain = (ip) => (ip ? DEFAULT_IP_DOMAIN.replace(/^\*/, ip.replace(
|
|
|
9
9
|
let cache = null;
|
|
10
10
|
let cacheAt = 0;
|
|
11
11
|
let cacheMissCount = 0;
|
|
12
|
-
|
|
12
|
+
let pendingFetch = null; // Prevents concurrent fetches
|
|
13
|
+
const cacheMissTTL = 1000 * 60 * 30; // 30 minutes
|
|
13
14
|
|
|
14
15
|
const timeout = process.env.NODE_ENV === 'test' ? 500 : 5000;
|
|
15
16
|
|
|
@@ -40,19 +41,22 @@ const checkConnected = async ({ ip, nodeInfo }) => {
|
|
|
40
41
|
*/
|
|
41
42
|
const fetch = async (nodeInfo) => {
|
|
42
43
|
const { external, internal } = await getIp();
|
|
43
|
-
logger.info('refresh external ip:', external);
|
|
44
|
-
|
|
45
44
|
if ([external, internal].includes(cache)) {
|
|
45
|
+
logger.info('reuse cached accessible ip:', { cache });
|
|
46
46
|
return cache;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
logger.info('refresh accessible ip:', { external, internal, cache });
|
|
50
|
+
|
|
49
51
|
// prefer external ip
|
|
50
52
|
try {
|
|
51
53
|
if (external) {
|
|
52
54
|
await checkConnected({ ip: external, nodeInfo });
|
|
53
55
|
cache = external;
|
|
56
|
+
logger.info('cache external ip as accessible ip', { external });
|
|
54
57
|
}
|
|
55
|
-
} catch {
|
|
58
|
+
} catch (err) {
|
|
59
|
+
logger.error('failed to check external ip', { external, error: err });
|
|
56
60
|
cacheMissCount += 1;
|
|
57
61
|
cache = null;
|
|
58
62
|
}
|
|
@@ -63,8 +67,10 @@ const fetch = async (nodeInfo) => {
|
|
|
63
67
|
if (internal) {
|
|
64
68
|
await checkConnected({ ip: internal, nodeInfo });
|
|
65
69
|
cache = internal;
|
|
70
|
+
logger.info('cache internal ip as accessible ip', { internal });
|
|
66
71
|
}
|
|
67
|
-
} catch {
|
|
72
|
+
} catch (err) {
|
|
73
|
+
logger.error('failed to check internal ip', { internal, error: err });
|
|
68
74
|
cacheMissCount += 1;
|
|
69
75
|
cache = null;
|
|
70
76
|
}
|
|
@@ -86,7 +92,16 @@ module.exports.getFromCache = (nodeInfo) => {
|
|
|
86
92
|
return cache;
|
|
87
93
|
}
|
|
88
94
|
if (nodeInfo) {
|
|
89
|
-
|
|
95
|
+
if (cache) {
|
|
96
|
+
return cache;
|
|
97
|
+
}
|
|
98
|
+
// Use pending promise to prevent concurrent fetches
|
|
99
|
+
if (!pendingFetch) {
|
|
100
|
+
pendingFetch = fetch(nodeInfo).finally(() => {
|
|
101
|
+
pendingFetch = null;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return pendingFetch;
|
|
90
105
|
}
|
|
91
106
|
return cache;
|
|
92
107
|
};
|
package/lib/util/index.js
CHANGED
|
@@ -29,10 +29,10 @@ const {
|
|
|
29
29
|
DEFAULT_HTTPS_PORT,
|
|
30
30
|
SLOT_FOR_IP_DNS_SITE,
|
|
31
31
|
BLOCKLET_SITE_GROUP_SUFFIX,
|
|
32
|
+
DEFAULT_WELLKNOWN_PORT,
|
|
32
33
|
} = require('@abtnode/constant');
|
|
33
34
|
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
34
35
|
|
|
35
|
-
const DEFAULT_WELLKNOWN_PORT = 8088;
|
|
36
36
|
const APP_CONFIG_IMAGE_KEYS = [
|
|
37
37
|
BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO_FAVICON,
|
|
38
38
|
BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPLASH_PORTRAIT,
|
|
@@ -219,8 +219,8 @@ const getBaseUrls = async (node, ips) => {
|
|
|
219
219
|
return { protocol, port };
|
|
220
220
|
};
|
|
221
221
|
|
|
222
|
-
if (info.routing.provider && info.routing.
|
|
223
|
-
const sites = await node.
|
|
222
|
+
if (info.routing.provider && info.routing.adminPath) {
|
|
223
|
+
const sites = await node.getSitesFromState('system');
|
|
224
224
|
const { ipWildcardDomain } = info.routing;
|
|
225
225
|
const adminPath = normalizePathPrefix(info.routing.adminPath);
|
|
226
226
|
const tmpHttpPort = getPort(httpPort, DEFAULT_HTTP_PORT);
|
package/lib/util/ip.js
CHANGED
|
@@ -8,6 +8,8 @@ const { promisify } = require('util');
|
|
|
8
8
|
const lookup = promisify(dns.lookup);
|
|
9
9
|
|
|
10
10
|
let cache = null;
|
|
11
|
+
let pendingFetch = null; // Prevents concurrent fetches
|
|
12
|
+
|
|
11
13
|
const fetch = async (args = {}) => {
|
|
12
14
|
try {
|
|
13
15
|
const start = Date.now();
|
|
@@ -21,7 +23,19 @@ const fetch = async (args = {}) => {
|
|
|
21
23
|
return cache;
|
|
22
24
|
};
|
|
23
25
|
|
|
24
|
-
const get = (args) =>
|
|
26
|
+
const get = (args) => {
|
|
27
|
+
if (cache) {
|
|
28
|
+
return cache;
|
|
29
|
+
}
|
|
30
|
+
// Use pending promise to prevent concurrent fetches
|
|
31
|
+
if (!pendingFetch) {
|
|
32
|
+
pendingFetch = fetch(args).finally(() => {
|
|
33
|
+
pendingFetch = null;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return pendingFetch;
|
|
37
|
+
};
|
|
38
|
+
|
|
25
39
|
const cron = {
|
|
26
40
|
name: 'refetch-ip',
|
|
27
41
|
time: '0 */30 * * * *', // refetch every 30 minutes
|
package/lib/util/launcher.js
CHANGED
|
@@ -166,17 +166,16 @@ const getCpuUtilization = async () => {
|
|
|
166
166
|
|
|
167
167
|
const getComponentsAggregate = async () => {
|
|
168
168
|
try {
|
|
169
|
-
|
|
169
|
+
// Use efficient SQL GROUP BY COUNT instead of loading all children into memory
|
|
170
|
+
// This is O(1) memory vs O(n) for the old implementation
|
|
171
|
+
const { total, counts: statusCounts } = await states.blockletChild.getStatusCounts();
|
|
172
|
+
|
|
173
|
+
// Convert numeric status keys to string keys using fromBlockletStatus
|
|
170
174
|
const counts = {};
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const key = fromBlockletStatus(child.status) || 'unknown';
|
|
176
|
-
counts[key] = (counts[key] || 0) + 1;
|
|
177
|
-
total += 1;
|
|
178
|
-
});
|
|
179
|
-
});
|
|
175
|
+
for (const [status, count] of Object.entries(statusCounts)) {
|
|
176
|
+
const key = fromBlockletStatus(Number(status)) || 'unknown';
|
|
177
|
+
counts[key] = (counts[key] || 0) + count;
|
|
178
|
+
}
|
|
180
179
|
|
|
181
180
|
return { total, counts };
|
|
182
181
|
} catch (error) {
|
|
@@ -660,7 +659,7 @@ const launchBlockletByLauncher = async (node, extraParams, context) => {
|
|
|
660
659
|
};
|
|
661
660
|
|
|
662
661
|
const launchBlockletWithoutWallet = async (node, extraParams, context) => {
|
|
663
|
-
logger.
|
|
662
|
+
logger.info('launchBlockletWithoutWallet start', { extraParams });
|
|
664
663
|
|
|
665
664
|
extraParams.locale = context.locale || 'en';
|
|
666
665
|
|
|
@@ -679,6 +678,7 @@ const launchBlockletWithoutWallet = async (node, extraParams, context) => {
|
|
|
679
678
|
description,
|
|
680
679
|
},
|
|
681
680
|
};
|
|
681
|
+
logger.info('launchBlockletWithoutWallet meta fetched', { blocklet });
|
|
682
682
|
|
|
683
683
|
if (!blocklet) {
|
|
684
684
|
throw new Error('Blocklet not found');
|
package/lib/util/ready.js
CHANGED
|
@@ -3,18 +3,11 @@ const logger = require('@abtnode/logger')('@abtnode/core:ready');
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
|
|
5
5
|
const createStateReadyHandler =
|
|
6
|
-
(
|
|
7
|
-
|
|
8
|
-
const snapshotHash = await routingSnapshot.init();
|
|
9
|
-
|
|
6
|
+
() =>
|
|
7
|
+
({ states, options }) => {
|
|
10
8
|
return states.node
|
|
11
9
|
.read()
|
|
12
10
|
.then(async (state) => {
|
|
13
|
-
if (snapshotHash && !state.routing.snapshotHash) {
|
|
14
|
-
logger.info('set snapshot hash because its empty');
|
|
15
|
-
await states.node.updateNodeRouting({ ...state.routing, snapshotHash });
|
|
16
|
-
}
|
|
17
|
-
|
|
18
11
|
// Set default sender/receiver for notification center
|
|
19
12
|
states.notification.setDefaultSender(state.did);
|
|
20
13
|
if (state.nodeOwner) {
|
package/lib/util/reset-node.js
CHANGED
|
@@ -62,7 +62,8 @@ const resetDirs = () => {
|
|
|
62
62
|
|
|
63
63
|
/* istanbul ignore next */
|
|
64
64
|
const resetBlocklets = async ({ context, blockletManager }) => {
|
|
65
|
-
const
|
|
65
|
+
const result = await blockletManager.list({ includeRuntimeInfo: false }, context);
|
|
66
|
+
const blocklets = result.blocklets || [];
|
|
66
67
|
for (let i = 0; i < blocklets.length; i++) {
|
|
67
68
|
const blocklet = blocklets[i];
|
|
68
69
|
// eslint-disable-next-line no-await-in-loop
|
|
@@ -75,7 +76,7 @@ const resetBlocklets = async ({ context, blockletManager }) => {
|
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
/* istanbul ignore next */
|
|
78
|
-
const resetSites = async ({ context, routerManager,
|
|
79
|
+
const resetSites = async ({ context, routerManager, handleAllRouting }) => {
|
|
79
80
|
const sites = await states.site.getSites();
|
|
80
81
|
for (let i = 0; i < sites.length; i++) {
|
|
81
82
|
const site = sites[i];
|
|
@@ -87,7 +88,7 @@ const resetSites = async ({ context, routerManager, takeRoutingSnapshot }) => {
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
const hash = await
|
|
91
|
+
const hash = await handleAllRouting({ message: 'reset routing sites for test' });
|
|
91
92
|
logger.info('reset routing sites', { hash });
|
|
92
93
|
};
|
|
93
94
|
|
|
@@ -153,7 +154,7 @@ module.exports = async ({
|
|
|
153
154
|
context,
|
|
154
155
|
blockletManager,
|
|
155
156
|
routerManager,
|
|
156
|
-
|
|
157
|
+
handleAllRouting,
|
|
157
158
|
teamManager,
|
|
158
159
|
certManager,
|
|
159
160
|
}) => {
|
|
@@ -192,7 +193,7 @@ module.exports = async ({
|
|
|
192
193
|
context,
|
|
193
194
|
blockletManager,
|
|
194
195
|
routerManager,
|
|
195
|
-
|
|
196
|
+
handleAllRouting,
|
|
196
197
|
teamManager,
|
|
197
198
|
certManager,
|
|
198
199
|
});
|
package/lib/validators/router.js
CHANGED
|
@@ -38,6 +38,11 @@ class APISender extends BaseSender {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async sendNotification(url, notification) {
|
|
41
|
+
if (process.env.NODE_ENV === 'test') {
|
|
42
|
+
return {
|
|
43
|
+
text: 'ok',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
41
46
|
try {
|
|
42
47
|
const res = await axios.post(url, notification, { timeout: 10000 });
|
|
43
48
|
return { text: res.statusText, code: res.status };
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.17.7-beta-
|
|
6
|
+
"version": "1.17.7-beta-20251229-085620-84f09930",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -17,21 +17,19 @@
|
|
|
17
17
|
"author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
|
|
18
18
|
"license": "Apache-2.0",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@abtnode/analytics": "1.17.7-beta-
|
|
21
|
-
"@abtnode/auth": "1.17.7-beta-
|
|
22
|
-
"@abtnode/certificate-manager": "1.17.7-beta-
|
|
23
|
-
"@abtnode/constant": "1.17.7-beta-
|
|
24
|
-
"@abtnode/cron": "1.17.7-beta-
|
|
25
|
-
"@abtnode/db-cache": "1.17.7-beta-
|
|
26
|
-
"@abtnode/docker-utils": "1.17.7-beta-
|
|
27
|
-
"@abtnode/logger": "1.17.7-beta-
|
|
28
|
-
"@abtnode/models": "1.17.7-beta-
|
|
29
|
-
"@abtnode/queue": "1.17.7-beta-
|
|
30
|
-
"@abtnode/rbac": "1.17.7-beta-
|
|
31
|
-
"@abtnode/router-provider": "1.17.7-beta-
|
|
32
|
-
"@abtnode/
|
|
33
|
-
"@abtnode/timemachine": "1.17.7-beta-20251227-001958-ea2ba3f5",
|
|
34
|
-
"@abtnode/util": "1.17.7-beta-20251227-001958-ea2ba3f5",
|
|
20
|
+
"@abtnode/analytics": "1.17.7-beta-20251229-085620-84f09930",
|
|
21
|
+
"@abtnode/auth": "1.17.7-beta-20251229-085620-84f09930",
|
|
22
|
+
"@abtnode/certificate-manager": "1.17.7-beta-20251229-085620-84f09930",
|
|
23
|
+
"@abtnode/constant": "1.17.7-beta-20251229-085620-84f09930",
|
|
24
|
+
"@abtnode/cron": "1.17.7-beta-20251229-085620-84f09930",
|
|
25
|
+
"@abtnode/db-cache": "1.17.7-beta-20251229-085620-84f09930",
|
|
26
|
+
"@abtnode/docker-utils": "1.17.7-beta-20251229-085620-84f09930",
|
|
27
|
+
"@abtnode/logger": "1.17.7-beta-20251229-085620-84f09930",
|
|
28
|
+
"@abtnode/models": "1.17.7-beta-20251229-085620-84f09930",
|
|
29
|
+
"@abtnode/queue": "1.17.7-beta-20251229-085620-84f09930",
|
|
30
|
+
"@abtnode/rbac": "1.17.7-beta-20251229-085620-84f09930",
|
|
31
|
+
"@abtnode/router-provider": "1.17.7-beta-20251229-085620-84f09930",
|
|
32
|
+
"@abtnode/util": "1.17.7-beta-20251229-085620-84f09930",
|
|
35
33
|
"@aigne/aigne-hub": "^0.10.15",
|
|
36
34
|
"@arcblock/did": "^1.27.16",
|
|
37
35
|
"@arcblock/did-connect-js": "^1.27.16",
|
|
@@ -43,16 +41,16 @@
|
|
|
43
41
|
"@arcblock/pm2-events": "^0.0.5",
|
|
44
42
|
"@arcblock/validator": "^1.27.16",
|
|
45
43
|
"@arcblock/vc": "^1.27.16",
|
|
46
|
-
"@blocklet/constant": "1.17.7-beta-
|
|
44
|
+
"@blocklet/constant": "1.17.7-beta-20251229-085620-84f09930",
|
|
47
45
|
"@blocklet/did-space-js": "^1.2.12",
|
|
48
|
-
"@blocklet/env": "1.17.7-beta-
|
|
46
|
+
"@blocklet/env": "1.17.7-beta-20251229-085620-84f09930",
|
|
49
47
|
"@blocklet/error": "^0.3.5",
|
|
50
|
-
"@blocklet/meta": "1.17.7-beta-
|
|
51
|
-
"@blocklet/resolver": "1.17.7-beta-
|
|
52
|
-
"@blocklet/sdk": "1.17.7-beta-
|
|
53
|
-
"@blocklet/server-js": "1.17.7-beta-
|
|
54
|
-
"@blocklet/store": "1.17.7-beta-
|
|
55
|
-
"@blocklet/theme": "^3.3.
|
|
48
|
+
"@blocklet/meta": "1.17.7-beta-20251229-085620-84f09930",
|
|
49
|
+
"@blocklet/resolver": "1.17.7-beta-20251229-085620-84f09930",
|
|
50
|
+
"@blocklet/sdk": "1.17.7-beta-20251229-085620-84f09930",
|
|
51
|
+
"@blocklet/server-js": "1.17.7-beta-20251229-085620-84f09930",
|
|
52
|
+
"@blocklet/store": "1.17.7-beta-20251229-085620-84f09930",
|
|
53
|
+
"@blocklet/theme": "^3.3.1",
|
|
56
54
|
"@fidm/x509": "^1.2.1",
|
|
57
55
|
"@ocap/mcrypto": "^1.27.16",
|
|
58
56
|
"@ocap/util": "^1.27.16",
|
|
@@ -116,5 +114,5 @@
|
|
|
116
114
|
"express": "^4.18.2",
|
|
117
115
|
"unzipper": "^0.10.11"
|
|
118
116
|
},
|
|
119
|
-
"gitHead": "
|
|
117
|
+
"gitHead": "fe2ffc3cf431bbaa89ac802bed793aa1188da4c3"
|
|
120
118
|
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-await-in-loop */
|
|
2
|
-
/* eslint-disable no-underscore-dangle */
|
|
3
|
-
module.exports = async ({ node, printInfo }) => {
|
|
4
|
-
printInfo('Try to update routing snapshot to 1.0.36...');
|
|
5
|
-
const hash = await node.takeRoutingSnapshot(
|
|
6
|
-
{ message: 'Migrate routing snapshot to pretty format', dryRun: false },
|
|
7
|
-
{}
|
|
8
|
-
);
|
|
9
|
-
printInfo('take routing snapshot on pretty format', { hash });
|
|
10
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-await-in-loop */
|
|
2
|
-
/* eslint-disable no-underscore-dangle */
|
|
3
|
-
module.exports = async ({ node, printInfo }) => {
|
|
4
|
-
printInfo('Try to update routing snapshot to 1.1.9...');
|
|
5
|
-
const hash = await node.takeRoutingSnapshot({ message: 'Migrate routing snapshot to 1.1.9', dryRun: false }, {});
|
|
6
|
-
printInfo('take routing snapshot on new keys', { hash });
|
|
7
|
-
};
|