@abtnode/core 1.17.7-beta-20251225-073259-cb6ecf68 → 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 +150 -36
- 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 +18 -27
- package/lib/migrations/1.17.7-beta-2025122601-settings-authentication.js +19 -0
- 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/audit-log.js +6 -3
- 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 +71 -37
- 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 +35 -37
- 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/router/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const get = require('lodash/get');
|
|
2
2
|
const uniq = require('lodash/uniq');
|
|
3
|
-
const
|
|
3
|
+
const debounce = require('lodash/debounce');
|
|
4
4
|
const pick = require('lodash/pick');
|
|
5
5
|
const isEqual = require('lodash/isEqual');
|
|
6
6
|
const cloneDeep = require('@abtnode/util/lib/deep-clone');
|
|
@@ -27,30 +27,16 @@ const IP = require('../util/ip');
|
|
|
27
27
|
|
|
28
28
|
const isServiceFeDevelopment = process.env.ABT_NODE_SERVICE_FE_PORT;
|
|
29
29
|
|
|
30
|
-
const mergeAllowedOrigins = (domain, allowedOrigins) => {
|
|
31
|
-
const origins = Array.isArray(allowedOrigins) ? allowedOrigins : [domain];
|
|
32
|
-
if (origins.includes(domain) === false) {
|
|
33
|
-
origins.push(domain);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// skip site if domain is BLOCKLET_SITE_GROUP
|
|
37
|
-
const res = origins.filter((x) => !x.endsWith(BLOCKLET_SITE_GROUP_SUFFIX));
|
|
38
|
-
|
|
39
|
-
return res;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
30
|
const expandSites = (sites = []) => {
|
|
43
31
|
const result = [];
|
|
44
32
|
|
|
45
33
|
sites.forEach((site) => {
|
|
46
|
-
site.corsAllowedOrigins = mergeAllowedOrigins(site.domain, site.corsAllowedOrigins);
|
|
47
34
|
(site.domainAliases || []).forEach((domainAlias) => {
|
|
48
35
|
const domain = typeof domainAlias === 'object' ? domainAlias.value : domainAlias;
|
|
49
36
|
const tmpSite = cloneDeep(site);
|
|
50
37
|
delete tmpSite.domainAliases;
|
|
51
38
|
tmpSite.serviceType = isBlockletSite(site.domain) ? 'blocklet' : 'daemon';
|
|
52
39
|
tmpSite.domain = domain;
|
|
53
|
-
tmpSite.corsAllowedOrigins = mergeAllowedOrigins(tmpSite.domain, site.corsAllowedOrigins);
|
|
54
40
|
result.push(tmpSite);
|
|
55
41
|
});
|
|
56
42
|
|
|
@@ -98,7 +84,7 @@ const getRoutingTable = ({ sites, nodeInfo }) => {
|
|
|
98
84
|
const enableIpServer = nodeInfo.routing.enableIpServer ?? false;
|
|
99
85
|
|
|
100
86
|
// eslint-disable-next-line no-use-before-define
|
|
101
|
-
let routingTable = Router.formatSites(sites);
|
|
87
|
+
let routingTable = Router.formatSites(sites, nodeInfo);
|
|
102
88
|
routingTable = expandSites(routingTable);
|
|
103
89
|
routingTable = filterSites({ sites: routingTable, enableDefaultServer, enableIpServer });
|
|
104
90
|
|
|
@@ -119,28 +105,122 @@ class Router {
|
|
|
119
105
|
/**
|
|
120
106
|
* Router
|
|
121
107
|
* @constructor
|
|
122
|
-
* @param {object}
|
|
108
|
+
* @param {object} options
|
|
109
|
+
* @param {object} options.provider - Router provider instance
|
|
110
|
+
* @param {function} options.getAllRoutingParams - Function to get all routing params (for full reload)
|
|
111
|
+
* @param {function} [options.getBlockletRoutingParams] - Function to get single blocklet's routing params (for lightweight updates)
|
|
112
|
+
* @param {function} [options.getSystemRoutingParams] - Function to get global/system routing params (for global-only updates)
|
|
123
113
|
*/
|
|
124
|
-
constructor({ provider,
|
|
114
|
+
constructor({ provider, getAllRoutingParams, getBlockletRoutingParams, getSystemRoutingParams }) {
|
|
125
115
|
if (!provider) {
|
|
126
116
|
throw new Error('Must provide valid router when create new router');
|
|
127
117
|
}
|
|
128
118
|
|
|
129
|
-
if (typeof
|
|
130
|
-
throw new Error('Must provide a valid
|
|
119
|
+
if (typeof getAllRoutingParams !== 'function') {
|
|
120
|
+
throw new Error('Must provide a valid getAllRoutingParams function when create new router');
|
|
131
121
|
}
|
|
132
122
|
|
|
133
123
|
this.provider = provider;
|
|
134
|
-
this.
|
|
124
|
+
this.getAllRoutingParams = getAllRoutingParams;
|
|
125
|
+
this.getBlockletRoutingParams = getBlockletRoutingParams; // Optional - for lightweight single blocklet updates
|
|
126
|
+
this.getSystemRoutingParams = getSystemRoutingParams; // Optional - for global-only updates (O(1))
|
|
135
127
|
this.routingTable = [];
|
|
136
128
|
|
|
137
|
-
|
|
138
|
-
this.
|
|
129
|
+
// Batching for rapid changes
|
|
130
|
+
this.pendingChanges = {
|
|
131
|
+
global: false,
|
|
132
|
+
blocklets: new Set(),
|
|
133
|
+
blockletsRemoved: new Set(),
|
|
134
|
+
};
|
|
135
|
+
this._processingBatch = false;
|
|
136
|
+
|
|
137
|
+
// Debounced batch processor - waits 1000ms for more changes, max 5s
|
|
138
|
+
this.debouncedProcessBatch = debounce(() => this._processBatch(), 1000, { maxWait: 5000 });
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Update routing table by fetching params and calling provider.update()
|
|
143
|
+
* This is a convenience method that combines _getUpdateParams() and provider.update()
|
|
144
|
+
*/
|
|
141
145
|
async updateRoutingTable() {
|
|
142
|
-
logger.info('update routing table');
|
|
146
|
+
logger.info('router: update routing table');
|
|
147
|
+
|
|
148
|
+
const params = await this._getUpdateParams();
|
|
149
|
+
if (!params) {
|
|
150
|
+
logger.error('router: failed to get update params in updateRoutingTable');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
logger.info('router: update routing table params ready');
|
|
154
|
+
|
|
155
|
+
await this.provider.update(params);
|
|
156
|
+
logger.info('router: update routing table complete');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async update() {
|
|
160
|
+
logger.info('router: update');
|
|
161
|
+
await this.updateRoutingTable();
|
|
162
|
+
await this.provider.reload();
|
|
163
|
+
logger.info('router: reload provider success');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async start() {
|
|
167
|
+
logger.info('router: start');
|
|
168
|
+
await this.updateRoutingTable();
|
|
169
|
+
return this.provider.start();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async restart() {
|
|
173
|
+
logger.info('router: restart');
|
|
174
|
+
await this.updateRoutingTable();
|
|
175
|
+
return this.provider.restart();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async reload() {
|
|
179
|
+
logger.info('router: reload');
|
|
180
|
+
await this.updateRoutingTable();
|
|
181
|
+
return this.provider.reload();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
stop() {
|
|
185
|
+
logger.info('router: stop');
|
|
186
|
+
return this.provider.stop();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async validateConfig() {
|
|
190
|
+
logger.info('router: validateConfig');
|
|
191
|
+
await this.provider.validateConfig();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async rotateLogs() {
|
|
195
|
+
logger.info('router: rotate logs');
|
|
196
|
+
await this.provider.rotateLogs();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
getLogFilesForToday() {
|
|
200
|
+
return this.provider.getLogFilesForToday();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
getLogDir() {
|
|
204
|
+
return this.provider.getLogDir();
|
|
205
|
+
}
|
|
143
206
|
|
|
207
|
+
searchCache(pattern, group) {
|
|
208
|
+
return this.provider.searchCache(pattern, group);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
clearCache(group) {
|
|
212
|
+
return this.provider.clearCache(group);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
supportsModSecurity() {
|
|
216
|
+
return !!this.provider.capabilities?.modsecurity;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get update parameters for the provider
|
|
221
|
+
* @returns {Promise<object>} Parameters for provider.update()
|
|
222
|
+
*/
|
|
223
|
+
async _getUpdateParams(fn = 'getAllRoutingParams') {
|
|
144
224
|
const {
|
|
145
225
|
sites,
|
|
146
226
|
certificates,
|
|
@@ -148,10 +228,11 @@ class Router {
|
|
|
148
228
|
services = [],
|
|
149
229
|
nodeInfo = {},
|
|
150
230
|
wafDisabledBlocklets = [],
|
|
151
|
-
} = (await this
|
|
231
|
+
} = (await this[fn]()) || {};
|
|
232
|
+
|
|
152
233
|
if (!Array.isArray(sites)) {
|
|
153
|
-
logger.error('sites is not an array', { sites });
|
|
154
|
-
return;
|
|
234
|
+
logger.error('router:_getUpdateParams: sites is not an array', { fn, sites });
|
|
235
|
+
return null;
|
|
155
236
|
}
|
|
156
237
|
|
|
157
238
|
this.routingTable = getRoutingTable({ sites, nodeInfo });
|
|
@@ -179,7 +260,6 @@ class Router {
|
|
|
179
260
|
if (blockPolicy.enabled) {
|
|
180
261
|
blockPolicy.blacklist = await expandBlacklist(blockPolicy.blacklist);
|
|
181
262
|
|
|
182
|
-
// remove current internal ip from blacklist to avoid blocking self
|
|
183
263
|
const result = await IP.get({ timeout: 2000 });
|
|
184
264
|
if (result?.internal) {
|
|
185
265
|
blockPolicy.blacklist = blockPolicy.blacklist.filter((x) => x !== result.internal);
|
|
@@ -188,10 +268,7 @@ class Router {
|
|
|
188
268
|
blockPolicy.blacklist = blockPolicy.blacklist.filter((x) => x !== result.external);
|
|
189
269
|
}
|
|
190
270
|
|
|
191
|
-
// Append blocked ips from database
|
|
192
271
|
const blockedIps = await getActiveBlacklist();
|
|
193
|
-
logger.info('router auto blocked ips', blockedIps);
|
|
194
|
-
|
|
195
272
|
blockPolicy.blacklist = uniq([...blockPolicy.blacklist, ...blockedIps]);
|
|
196
273
|
}
|
|
197
274
|
|
|
@@ -209,15 +286,12 @@ class Router {
|
|
|
209
286
|
outboundAnomalyScoreThreshold: 10,
|
|
210
287
|
};
|
|
211
288
|
|
|
212
|
-
|
|
213
|
-
snapshotHash: nodeInfo?.routing?.snapshotHash,
|
|
214
|
-
});
|
|
215
|
-
await this.provider.update({
|
|
289
|
+
return {
|
|
216
290
|
routingTable: this.routingTable,
|
|
217
291
|
certificates,
|
|
218
292
|
commonHeaders: headers,
|
|
219
293
|
services,
|
|
220
|
-
nodeInfo: pick(nodeInfo, ['name', 'version', 'port', 'mode', 'enableWelcomePage', 'routing']),
|
|
294
|
+
nodeInfo: pick(nodeInfo, ['did', 'name', 'version', 'port', 'mode', 'enableWelcomePage', 'routing']),
|
|
221
295
|
requestLimit,
|
|
222
296
|
blockPolicy,
|
|
223
297
|
proxyPolicy,
|
|
@@ -226,87 +300,190 @@ class Router {
|
|
|
226
300
|
wafDisabledBlocklets,
|
|
227
301
|
enableDefaultServer: nodeInfo.routing.enableDefaultServer ?? false,
|
|
228
302
|
enableIpServer: nodeInfo.routing.enableIpServer ?? false,
|
|
229
|
-
}
|
|
230
|
-
logger.info('router: update routing table success', {
|
|
231
|
-
snapshotHash: nodeInfo?.routing?.snapshotHash,
|
|
232
|
-
});
|
|
303
|
+
};
|
|
233
304
|
}
|
|
234
305
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Queue a change for batched processing
|
|
308
|
+
* @param {string} changeType - 'global', 'blocklet', or 'blocklet-remove'
|
|
309
|
+
* @param {string} [did] - The blocklet DID (for blocklet changes)
|
|
310
|
+
*/
|
|
311
|
+
queueChange(changeType, did = undefined) {
|
|
312
|
+
if (changeType === 'global') {
|
|
313
|
+
this.pendingChanges.global = true;
|
|
314
|
+
} else if (changeType === 'blocklet' && did) {
|
|
315
|
+
this.pendingChanges.blocklets.add(did);
|
|
316
|
+
// If we're updating a blocklet, remove it from the remove list
|
|
317
|
+
this.pendingChanges.blockletsRemoved.delete(did);
|
|
318
|
+
} else if (changeType === 'blocklet-remove' && did) {
|
|
319
|
+
this.pendingChanges.blockletsRemoved.add(did);
|
|
320
|
+
// If we're removing a blocklet, remove it from the update list
|
|
321
|
+
this.pendingChanges.blocklets.delete(did);
|
|
322
|
+
}
|
|
241
323
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
324
|
+
logger.info('router: queued change', {
|
|
325
|
+
changeType,
|
|
326
|
+
did,
|
|
327
|
+
global: this.pendingChanges.global,
|
|
328
|
+
blockletCount: this.pendingChanges.blocklets.size,
|
|
329
|
+
removeCount: this.pendingChanges.blockletsRemoved.size,
|
|
330
|
+
});
|
|
247
331
|
|
|
248
|
-
|
|
249
|
-
logger.info('router: restart');
|
|
250
|
-
await this.updateRoutingTable();
|
|
251
|
-
return this.provider.restart();
|
|
332
|
+
this.debouncedProcessBatch();
|
|
252
333
|
}
|
|
253
334
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
335
|
+
/**
|
|
336
|
+
* Process batched changes with tiered approach:
|
|
337
|
+
* - Global-only changes: O(1) using getSystemRoutingParams
|
|
338
|
+
* - Blocklet changes: O(1) per blocklet using getBlockletRoutingParams
|
|
339
|
+
* - Global + blocklet changes: O(1) global + O(k) blocklets where k = number of changed blocklets
|
|
340
|
+
*/
|
|
341
|
+
async _processBatch() {
|
|
342
|
+
if (this._processingBatch) {
|
|
343
|
+
logger.info('router: already processing batch, will retry');
|
|
344
|
+
this.debouncedProcessBatch();
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
259
347
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
348
|
+
const { global: globalChanged, blocklets, blockletsRemoved } = this.pendingChanges;
|
|
349
|
+
const hasBlockletChanges = blocklets.size > 0 || blockletsRemoved.size > 0;
|
|
350
|
+
const hasChanges = globalChanged || hasBlockletChanges;
|
|
264
351
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
352
|
+
if (!hasChanges) {
|
|
353
|
+
logger.debug('router: no pending changes to process');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
269
356
|
|
|
270
|
-
|
|
271
|
-
logger.info('router: rotate logs');
|
|
272
|
-
await this.provider.rotateLogs();
|
|
273
|
-
}
|
|
357
|
+
this._processingBatch = true;
|
|
274
358
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
359
|
+
// Clear pending changes before processing
|
|
360
|
+
const blockletsToUpdate = [...blocklets];
|
|
361
|
+
const blockletsToRemove = [...blockletsRemoved];
|
|
362
|
+
this.pendingChanges = {
|
|
363
|
+
global: false,
|
|
364
|
+
blocklets: new Set(),
|
|
365
|
+
blockletsRemoved: new Set(),
|
|
366
|
+
};
|
|
278
367
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
368
|
+
logger.info('router: processing batched changes', {
|
|
369
|
+
globalChanged,
|
|
370
|
+
blockletsToUpdate: blockletsToUpdate.length,
|
|
371
|
+
blockletsToRemove: blockletsToRemove.length,
|
|
372
|
+
});
|
|
282
373
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
374
|
+
try {
|
|
375
|
+
let needsReload = false;
|
|
286
376
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
377
|
+
// Get global params once (O(1)) - needed for both global and blocklet updates
|
|
378
|
+
let globalParams = null;
|
|
379
|
+
if (typeof this.getSystemRoutingParams === 'function') {
|
|
380
|
+
globalParams = await this._getUpdateParams('getSystemRoutingParams');
|
|
381
|
+
}
|
|
290
382
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
383
|
+
// Case 1: Global changed with no blocklet changes - use lightweight global update
|
|
384
|
+
if (globalChanged) {
|
|
385
|
+
if (globalParams) {
|
|
386
|
+
// Use global params for system sites + services + policies
|
|
387
|
+
await this.provider.update({ ...globalParams, skipBlockletSites: true });
|
|
388
|
+
needsReload = true;
|
|
389
|
+
logger.info('router: batch processed global-only changes');
|
|
390
|
+
} else {
|
|
391
|
+
logger.error('router: global-only changes found, but globalParams is not available');
|
|
392
|
+
}
|
|
393
|
+
}
|
|
295
394
|
|
|
296
|
-
|
|
297
|
-
|
|
395
|
+
// Case 2: Process blocklet updates (O(1) per blocklet)
|
|
396
|
+
if (blockletsToUpdate.length > 0 && typeof this.getBlockletRoutingParams === 'function') {
|
|
397
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
398
|
+
for (const did of blockletsToUpdate) {
|
|
399
|
+
try {
|
|
400
|
+
// eslint-disable-next-line no-await-in-loop
|
|
401
|
+
const rawParams = await this.getBlockletRoutingParams(did);
|
|
402
|
+
if (rawParams && rawParams.sites && rawParams.sites.length > 0) {
|
|
403
|
+
const { sites, certificates, headers = {}, nodeInfo = {}, wafDisabledBlocklets = [] } = rawParams;
|
|
404
|
+
const blockletParams = {
|
|
405
|
+
routingTable: getRoutingTable({ sites, nodeInfo }),
|
|
406
|
+
certificates,
|
|
407
|
+
commonHeaders: headers,
|
|
408
|
+
nodeInfo: pick(nodeInfo, ['did', 'name', 'version', 'port', 'mode', 'enableWelcomePage', 'routing']),
|
|
409
|
+
wafDisabledBlocklets,
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
if (typeof this.provider.updateSingleBlocklet === 'function') {
|
|
413
|
+
// eslint-disable-next-line no-await-in-loop
|
|
414
|
+
await this.provider.updateSingleBlocklet(did, blockletParams);
|
|
415
|
+
logger.info('router: batch processed blocklet changes', { did });
|
|
416
|
+
needsReload = true;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
} catch (error) {
|
|
420
|
+
logger.warn('router: failed to update blocklet in batch, skipping', { did, error: error.message });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Case 4: Process blocklet removals
|
|
426
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
427
|
+
for (const did of blockletsToRemove) {
|
|
428
|
+
if (typeof this.provider._removeBlockletConfig === 'function') {
|
|
429
|
+
// eslint-disable-next-line no-await-in-loop
|
|
430
|
+
await this.provider._removeBlockletConfig(did);
|
|
431
|
+
logger.info('router: batch processed blocklet removal', { did });
|
|
432
|
+
needsReload = true;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Single reload for all changes
|
|
437
|
+
if (needsReload) {
|
|
438
|
+
const status = await this.provider.getStatus();
|
|
439
|
+
logger.info('router: batch needs reload', { status });
|
|
440
|
+
if (!status.running) {
|
|
441
|
+
await this.provider.start();
|
|
442
|
+
} else {
|
|
443
|
+
await this.provider.reload();
|
|
444
|
+
}
|
|
445
|
+
}
|
|
298
446
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
447
|
+
logger.info('router: batch processing complete', {
|
|
448
|
+
globalChanged,
|
|
449
|
+
blockletsUpdated: blockletsToUpdate.length,
|
|
450
|
+
blockletsRemoved: blockletsToRemove.length,
|
|
451
|
+
});
|
|
452
|
+
} catch (error) {
|
|
453
|
+
logger.error('router: batch processing failed', { error: error.message });
|
|
454
|
+
// Try full reload as fallback
|
|
455
|
+
try {
|
|
456
|
+
await this.reload();
|
|
457
|
+
} catch (reloadError) {
|
|
458
|
+
logger.error('router: fallback reload also failed', { error: reloadError.message });
|
|
459
|
+
}
|
|
460
|
+
} finally {
|
|
461
|
+
this._processingBatch = false;
|
|
305
462
|
}
|
|
306
463
|
}
|
|
307
|
-
|
|
308
|
-
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Full regeneration - O(N) complexity
|
|
467
|
+
* Use for startup, manual rebuild, or when complete regeneration is needed
|
|
468
|
+
* @param {Object} options
|
|
469
|
+
* @param {string} [options.message] - Log message describing the reason for regeneration
|
|
470
|
+
*/
|
|
471
|
+
async regenerateAll({ message = '' } = {}) {
|
|
472
|
+
logger.info('router: full regeneration', { message });
|
|
473
|
+
const params = await this._getUpdateParams('getAllRoutingParams');
|
|
474
|
+
if (!params) {
|
|
475
|
+
logger.error('router: failed to get update params for full regeneration');
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
await this.provider.update(params);
|
|
480
|
+
await this.provider.reload();
|
|
481
|
+
logger.info('router: full regeneration complete', { message });
|
|
309
482
|
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
Router.formatSites = (sites = [], info) => {
|
|
486
|
+
const result = cloneDeep(sites);
|
|
310
487
|
|
|
311
488
|
result.forEach((site) => {
|
|
312
489
|
const rules = Array.isArray(site.rules) ? cloneDeep(site.rules) : [];
|
|
@@ -324,7 +501,7 @@ Router.formatSites = (sites = []) => {
|
|
|
324
501
|
},
|
|
325
502
|
to: {
|
|
326
503
|
type: ROUTING_RULE_TYPES.DAEMON,
|
|
327
|
-
port:
|
|
504
|
+
port: info.port,
|
|
328
505
|
did: site.blockletDid,
|
|
329
506
|
},
|
|
330
507
|
});
|
|
@@ -334,7 +511,7 @@ Router.formatSites = (sites = []) => {
|
|
|
334
511
|
site.rules.push({
|
|
335
512
|
dynamic: true,
|
|
336
513
|
from: { root: true, pathPrefix: '/', groupPathPrefix: '/', pathSuffix: '/favicon.ico' },
|
|
337
|
-
to: { type: ROUTING_RULE_TYPES.SERVICE, port:
|
|
514
|
+
to: { type: ROUTING_RULE_TYPES.SERVICE, port: info.port, did: site.blockletDid },
|
|
338
515
|
});
|
|
339
516
|
}
|
|
340
517
|
|
|
@@ -362,7 +539,7 @@ Router.formatSites = (sites = []) => {
|
|
|
362
539
|
},
|
|
363
540
|
to: {
|
|
364
541
|
type: ROUTING_RULE_TYPES.DAEMON,
|
|
365
|
-
port:
|
|
542
|
+
port: info.port,
|
|
366
543
|
did: rule.to.did,
|
|
367
544
|
componentId: rule.to.componentId,
|
|
368
545
|
pageGroup: rule.to.pageGroup,
|
|
@@ -374,7 +551,6 @@ Router.formatSites = (sites = []) => {
|
|
|
374
551
|
if (isBlockletSite(site.domain) && Object.keys(grouped).length === 0) {
|
|
375
552
|
grouped['/'] = {
|
|
376
553
|
id: '',
|
|
377
|
-
groupId: '',
|
|
378
554
|
to: {
|
|
379
555
|
did: site.blockletDid,
|
|
380
556
|
componentId: site.blockletDid,
|
|
@@ -397,7 +573,7 @@ Router.formatSites = (sites = []) => {
|
|
|
397
573
|
from: rootFrom,
|
|
398
574
|
to: {
|
|
399
575
|
type: ROUTING_RULE_TYPES.DAEMON,
|
|
400
|
-
port:
|
|
576
|
+
port: info.port,
|
|
401
577
|
pageGroup: rule.to.pageGroup,
|
|
402
578
|
did: rule.to.did,
|
|
403
579
|
},
|
|
@@ -411,7 +587,7 @@ Router.formatSites = (sites = []) => {
|
|
|
411
587
|
pathPrefix: BLOCKLET_PROXY_PATH_PREFIX,
|
|
412
588
|
},
|
|
413
589
|
to: {
|
|
414
|
-
port:
|
|
590
|
+
port: info.port,
|
|
415
591
|
type: ROUTING_RULE_TYPES.DAEMON,
|
|
416
592
|
target: BLOCKLET_PROXY_PATH_PREFIX,
|
|
417
593
|
cacheGroup: !isServiceFeDevelopment && site.mode === BLOCKLET_MODES.PRODUCTION ? 'blockletProxy' : '',
|
package/lib/router/manager.js
CHANGED
|
@@ -23,12 +23,7 @@ const checkDomainMatch = require('@abtnode/util/lib/check-domain-match');
|
|
|
23
23
|
const { isDidDomain, isCustomDomain } = require('@abtnode/util/lib/url-evaluation');
|
|
24
24
|
const { isTopLevelDomain } = require('@abtnode/util/lib/domain');
|
|
25
25
|
const { EVENTS, WELLKNOWN_PING_PREFIX } = require('@abtnode/constant');
|
|
26
|
-
const {
|
|
27
|
-
DOMAIN_FOR_IP_SITE,
|
|
28
|
-
DOMAIN_FOR_DEFAULT_SITE,
|
|
29
|
-
ROUTING_RULE_TYPES,
|
|
30
|
-
DEFAULT_SERVICE_PATH,
|
|
31
|
-
} = require('@abtnode/constant');
|
|
26
|
+
const { DOMAIN_FOR_IP_SITE, DOMAIN_FOR_DEFAULT_SITE, ROUTING_RULE_TYPES } = require('@abtnode/constant');
|
|
32
27
|
const {
|
|
33
28
|
BLOCKLET_BUNDLE_FOLDER,
|
|
34
29
|
BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
@@ -59,15 +54,7 @@ const {
|
|
|
59
54
|
const checkDNS = require('../util/check-dns');
|
|
60
55
|
|
|
61
56
|
const checkPathPrefixInBlackList = (pathPrefix, extraBlackList = []) => {
|
|
62
|
-
const blacklist = [
|
|
63
|
-
'static',
|
|
64
|
-
'build',
|
|
65
|
-
'dist',
|
|
66
|
-
'assets',
|
|
67
|
-
'.service',
|
|
68
|
-
BLOCKLET_BUNDLE_FOLDER,
|
|
69
|
-
DEFAULT_SERVICE_PATH,
|
|
70
|
-
].concat(extraBlackList);
|
|
57
|
+
const blacklist = ['static', 'build', 'dist', 'assets', '.service', BLOCKLET_BUNDLE_FOLDER].concat(extraBlackList);
|
|
71
58
|
const prefixBlacklist = ['_abtnode_'].concat(extraBlackList);
|
|
72
59
|
if (blacklist.find((b) => normalizePathPrefix(pathPrefix) === normalizePathPrefix(b))) {
|
|
73
60
|
throw new Error(`path prefix can't be one of these values: ${blacklist.join(', ')}`);
|
|
@@ -420,7 +407,7 @@ class RouterManager extends EventEmitter {
|
|
|
420
407
|
}
|
|
421
408
|
|
|
422
409
|
async addRoutingRule(
|
|
423
|
-
{ id, rule: tempRule, skipCheckDynamicBlacklist = false, formatPathPrefix = true },
|
|
410
|
+
{ id, rule: tempRule, skipCheckDynamicBlacklist = false, formatPathPrefix = true, skipValidation = false },
|
|
424
411
|
context = {}
|
|
425
412
|
) {
|
|
426
413
|
const { rule } = await validateAddRule({ id, rule: tempRule }, context);
|
|
@@ -458,7 +445,9 @@ class RouterManager extends EventEmitter {
|
|
|
458
445
|
}
|
|
459
446
|
|
|
460
447
|
await this.validatePathPrefix(rule);
|
|
461
|
-
|
|
448
|
+
if (!skipValidation) {
|
|
449
|
+
await this.validateRouterConfig('addRoutingRule', { id, rule });
|
|
450
|
+
}
|
|
462
451
|
|
|
463
452
|
// add child blocklet rules
|
|
464
453
|
for (const x of await this.getRulesForMutation(rule)) {
|
|
@@ -482,7 +471,7 @@ class RouterManager extends EventEmitter {
|
|
|
482
471
|
}
|
|
483
472
|
|
|
484
473
|
async updateRoutingRule(
|
|
485
|
-
{ id, rule: tmpRule, skipProtectedRuleChecking = false, formatPathPrefix = true },
|
|
474
|
+
{ id, rule: tmpRule, skipProtectedRuleChecking = false, formatPathPrefix = true, skipValidation = false },
|
|
486
475
|
context = {}
|
|
487
476
|
) {
|
|
488
477
|
const { rule } = await validateEditRule({ id, rule: tmpRule }, context);
|
|
@@ -518,13 +507,12 @@ class RouterManager extends EventEmitter {
|
|
|
518
507
|
}
|
|
519
508
|
|
|
520
509
|
await this.validatePathPrefix(rule);
|
|
521
|
-
|
|
510
|
+
if (!skipValidation) {
|
|
511
|
+
await this.validateRouterConfig('updateRoutingRule', { id, rule });
|
|
512
|
+
}
|
|
522
513
|
|
|
523
514
|
// update rules
|
|
524
|
-
const newRules = [
|
|
525
|
-
...dbSite.rules.filter((x) => (x.groupId && x.groupId !== rule.id) || x.id !== rule.id), // 有些路由没有 rule.groupId
|
|
526
|
-
...(await this.getRulesForMutation(rule)),
|
|
527
|
-
];
|
|
515
|
+
const newRules = [...dbSite.rules.filter((x) => x.id !== rule.id), ...(await this.getRulesForMutation(rule))];
|
|
528
516
|
|
|
529
517
|
const updateResult = await states.site.update({ id }, { $set: { rules: newRules } });
|
|
530
518
|
logger.info('update result', { updateResult });
|
|
@@ -551,8 +539,8 @@ class RouterManager extends EventEmitter {
|
|
|
551
539
|
|
|
552
540
|
// 只要有匹配到的查询条件,不管是否删除成功都不会返回 0,所以这里没用 update 的返回值
|
|
553
541
|
const doc = await states.site.findOne({ id });
|
|
554
|
-
if (doc.rules.some((x) => x.id === ruleId
|
|
555
|
-
const newRules = doc.rules.filter((x) => x.id !== ruleId
|
|
542
|
+
if (doc.rules.some((x) => x.id === ruleId)) {
|
|
543
|
+
const newRules = doc.rules.filter((x) => x.id !== ruleId);
|
|
556
544
|
await states.site.update({ id }, { $set: { rules: newRules } });
|
|
557
545
|
}
|
|
558
546
|
|
|
@@ -840,7 +828,7 @@ class RouterManager extends EventEmitter {
|
|
|
840
828
|
});
|
|
841
829
|
const tempRouter = new Router({
|
|
842
830
|
provider,
|
|
843
|
-
|
|
831
|
+
getAllRoutingParams: async () => ({
|
|
844
832
|
sites: await ensureLatestInfo([site]),
|
|
845
833
|
certificates,
|
|
846
834
|
commonHeaders: get(info, 'routing.headers', {}),
|
|
@@ -865,7 +853,6 @@ class RouterManager extends EventEmitter {
|
|
|
865
853
|
if (!rule.id) {
|
|
866
854
|
rule.id = uuid.v4();
|
|
867
855
|
}
|
|
868
|
-
rule.groupId = rule.id;
|
|
869
856
|
rule.from.pathPrefix = normalizePathPrefix(rule.from.pathPrefix);
|
|
870
857
|
if (rule.to.type === ROUTING_RULE_TYPES.BLOCKLET) {
|
|
871
858
|
// pathPrefix of root blocklet maybe changed to another prefix than '/', so use old groupPathPrefix first
|
|
@@ -937,7 +924,6 @@ class RouterManager extends EventEmitter {
|
|
|
937
924
|
// if is root path, child rule become root rule
|
|
938
925
|
const childRule = {
|
|
939
926
|
id: occupied ? rawRule.id : uuid.v4(),
|
|
940
|
-
groupId: rawRule.id,
|
|
941
927
|
from: {
|
|
942
928
|
pathPrefix: normalizePathPrefix(pathPrefix),
|
|
943
929
|
groupPathPrefix: blockletPrefix,
|
package/lib/states/audit-log.js
CHANGED
|
@@ -351,8 +351,10 @@ const getLogContent = async (action, args, context, result, info, node) => {
|
|
|
351
351
|
return `updated following navigations:\n${args.navigations.map((x) => `- ${x.title}: ${x.link}\n`)}`;
|
|
352
352
|
case 'configTheme':
|
|
353
353
|
return `updated theme for application ${args.did}`;
|
|
354
|
-
case '
|
|
355
|
-
return `updated
|
|
354
|
+
case 'configAuthentication':
|
|
355
|
+
return `updated authentication for blocklet ${getBlockletInfo(result, info)}:\n${args.authentication}`;
|
|
356
|
+
case 'configDidConnect':
|
|
357
|
+
return `updated DID Connect for blocklet ${getBlockletInfo(result, info)}:\n${args.didConnect}`;
|
|
356
358
|
case 'joinFederatedLogin':
|
|
357
359
|
return `blocklet ${getBlockletInfo(result, info)} join federated login to ${args.appUrl}`;
|
|
358
360
|
case 'quitFederatedLogin':
|
|
@@ -802,7 +804,8 @@ const getLogCategory = (action) => {
|
|
|
802
804
|
case 'upgradeComponents':
|
|
803
805
|
case 'configNavigations':
|
|
804
806
|
case 'configTheme':
|
|
805
|
-
case '
|
|
807
|
+
case 'configAuthentication':
|
|
808
|
+
case 'configDidConnect':
|
|
806
809
|
case 'configNotification':
|
|
807
810
|
case 'updateComponentTitle':
|
|
808
811
|
case 'updateComponentMountPoint':
|