@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/router/helper.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
/* eslint-disable no-use-before-define */
|
|
1
2
|
/* eslint-disable no-restricted-syntax */
|
|
2
3
|
/* eslint-disable prefer-destructuring */
|
|
3
4
|
const fs = require('fs-extra');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const tar = require('tar');
|
|
7
|
+
const dns = require('dns').promises;
|
|
6
8
|
const UUID = require('uuid');
|
|
7
9
|
const dayjs = require('@abtnode/util/lib/dayjs');
|
|
8
|
-
const isUrl = require('is-url');
|
|
9
10
|
const get = require('lodash/get');
|
|
10
11
|
const cloneDeep = require('@abtnode/util/lib/deep-clone');
|
|
11
12
|
const groupBy = require('lodash/groupBy');
|
|
@@ -26,7 +27,8 @@ const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
|
|
|
26
27
|
const getTmpDir = require('@abtnode/util/lib/get-tmp-directory');
|
|
27
28
|
const downloadFile = require('@abtnode/util/lib/download-file');
|
|
28
29
|
const axios = require('@abtnode/util/lib/axios');
|
|
29
|
-
const { getIpDnsDomainForBlocklet } = require('@abtnode/util/lib/get-domain-for-blocklet');
|
|
30
|
+
const { getIpDnsDomainForBlocklet, getDidDomainForBlocklet } = require('@abtnode/util/lib/get-domain-for-blocklet');
|
|
31
|
+
const { hasMountPoint } = require('@blocklet/meta/lib/engine');
|
|
30
32
|
const { forEachBlockletSync } = require('@blocklet/meta/lib/util');
|
|
31
33
|
const { processLogByDate } = require('@abtnode/analytics');
|
|
32
34
|
const { DBCache, getAbtNodeRedisAndSQLiteUrl } = require('@abtnode/db-cache');
|
|
@@ -43,7 +45,6 @@ const {
|
|
|
43
45
|
NAME_FOR_WELLKNOWN_SITE,
|
|
44
46
|
ROUTING_RULE_TYPES,
|
|
45
47
|
CERTIFICATE_EXPIRES_OFFSET,
|
|
46
|
-
DEFAULT_SERVICE_PATH,
|
|
47
48
|
SLOT_FOR_IP_DNS_SITE,
|
|
48
49
|
BLOCKLET_SITE_GROUP_SUFFIX,
|
|
49
50
|
WELLKNOWN_ACME_CHALLENGE_PREFIX,
|
|
@@ -57,6 +58,8 @@ const {
|
|
|
57
58
|
DEFAULT_IP_DOMAIN,
|
|
58
59
|
WELLKNOWN_BLACKLIST_PREFIX,
|
|
59
60
|
NODE_MODES,
|
|
61
|
+
ACCESS_POLICY_PUBLIC,
|
|
62
|
+
DEFAULT_DID_DOMAIN,
|
|
60
63
|
} = require('@abtnode/constant');
|
|
61
64
|
const {
|
|
62
65
|
BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
@@ -64,17 +67,15 @@ const {
|
|
|
64
67
|
BLOCKLET_INTERFACE_PUBLIC,
|
|
65
68
|
BLOCKLET_INTERFACE_WELLKNOWN,
|
|
66
69
|
BLOCKLET_INTERFACE_TYPE_WELLKNOWN,
|
|
67
|
-
BLOCKLET_CONFIGURABLE_KEY,
|
|
68
70
|
BlockletEvents,
|
|
69
71
|
BLOCKLET_MODES,
|
|
70
72
|
} = require('@blocklet/constant');
|
|
71
|
-
const {
|
|
73
|
+
const { isWorkerInstance } = require('@abtnode/util/lib/pm2/is-instance-worker');
|
|
72
74
|
|
|
73
75
|
const pkg = require('../../package.json');
|
|
74
76
|
// eslint-disable-next-line
|
|
75
77
|
const logger = require('@abtnode/logger')(`${pkg.name}:router:helper`);
|
|
76
78
|
const {
|
|
77
|
-
getProviderFromNodeInfo,
|
|
78
79
|
getHttpsCertInfo,
|
|
79
80
|
findInterfacePortByName,
|
|
80
81
|
getWellknownSitePort,
|
|
@@ -103,6 +104,7 @@ const {
|
|
|
103
104
|
} = require('../util/blocklet');
|
|
104
105
|
const { toCamelCase } = require('../util/index');
|
|
105
106
|
const { get: getIp } = require('../util/ip');
|
|
107
|
+
const { getBlockletSecurityRules } = require('../blocklet/security/security-rule');
|
|
106
108
|
|
|
107
109
|
const isServiceFeDevelopment = process.env.ABT_NODE_SERVICE_FE_PORT;
|
|
108
110
|
|
|
@@ -217,57 +219,9 @@ const attachRuntimeDomainAliases = async ({ sites = [], context = {} }) => {
|
|
|
217
219
|
});
|
|
218
220
|
};
|
|
219
221
|
|
|
220
|
-
/**
|
|
221
|
-
* @description
|
|
222
|
-
* @param {{
|
|
223
|
-
* corsAllowedOrigins: Array<string>;
|
|
224
|
-
* }} site
|
|
225
|
-
* @param {string} rawUrl
|
|
226
|
-
* @return {void}
|
|
227
|
-
*/
|
|
228
|
-
const addCorsToSite = (site, rawUrl) => {
|
|
229
|
-
if (!site || !rawUrl) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
try {
|
|
233
|
-
const url = new URL(rawUrl);
|
|
234
|
-
if (!Array.isArray(site.corsAllowedOrigins)) {
|
|
235
|
-
site.corsAllowedOrigins = [];
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (!site.corsAllowedOrigins.includes(url.hostname)) {
|
|
239
|
-
site.corsAllowedOrigins.push(url.hostname);
|
|
240
|
-
}
|
|
241
|
-
} catch (err) {
|
|
242
|
-
console.error('addCorsToSite', err);
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
|
|
246
222
|
const isBasicSite = (domain) =>
|
|
247
223
|
[DOMAIN_FOR_INTERNAL_SITE, DOMAIN_FOR_IP_SITE, DOMAIN_FOR_DEFAULT_SITE, DOMAIN_FOR_IP_SITE_REGEXP].includes(domain);
|
|
248
224
|
|
|
249
|
-
/**
|
|
250
|
-
* add rule of directly proxy to abtnode-service for default site
|
|
251
|
-
*/
|
|
252
|
-
const ensureServiceRule = async (sites) => {
|
|
253
|
-
const info = await states.node.read();
|
|
254
|
-
return sites.map((site) => {
|
|
255
|
-
if (
|
|
256
|
-
[DOMAIN_FOR_IP_SITE, DOMAIN_FOR_IP_SITE_REGEXP].includes(site.domain) &&
|
|
257
|
-
!site.rules.some((r) => r.from.pathPrefix === DEFAULT_SERVICE_PATH)
|
|
258
|
-
) {
|
|
259
|
-
site.rules.push({
|
|
260
|
-
from: { pathPrefix: DEFAULT_SERVICE_PATH },
|
|
261
|
-
to: {
|
|
262
|
-
port: process.env.ABT_NODE_SERVICE_PORT,
|
|
263
|
-
type: ROUTING_RULE_TYPES.SERVICE,
|
|
264
|
-
did: info.did,
|
|
265
|
-
},
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
return site;
|
|
269
|
-
});
|
|
270
|
-
};
|
|
271
225
|
const ensureRootRule = (sites) => {
|
|
272
226
|
return sites.map((site) => {
|
|
273
227
|
if (!isBasicSite(site.domain) && !site.rules.some((x) => x.from.pathPrefix === '/')) {
|
|
@@ -282,8 +236,7 @@ const ensureRootRule = (sites) => {
|
|
|
282
236
|
});
|
|
283
237
|
};
|
|
284
238
|
|
|
285
|
-
const ensureLatestNodeInfo =
|
|
286
|
-
const info = await states.node.read();
|
|
239
|
+
const ensureLatestNodeInfo = (sites = [], info) => {
|
|
287
240
|
return sites.map((site) => {
|
|
288
241
|
site.rules = site.rules.map((rule) => {
|
|
289
242
|
if (rule.to.did === info.did && rule.to.type === ROUTING_RULE_TYPES.DAEMON) {
|
|
@@ -300,14 +253,6 @@ const ensureLatestNodeInfo = async (sites = [], { withDefaultCors = true } = {})
|
|
|
300
253
|
// We use an regular expression to match ip domain so that it is adaptive
|
|
301
254
|
// @ref https://stackoverflow.com/questions/9454764/nginx-server-name-wildcard-or-catch-all
|
|
302
255
|
site.domain = DOMAIN_FOR_IP_SITE_REGEXP;
|
|
303
|
-
|
|
304
|
-
if (withDefaultCors) {
|
|
305
|
-
// Allow CORS from "Install on ABT Node"
|
|
306
|
-
addCorsToSite(site, info.registerUrl);
|
|
307
|
-
|
|
308
|
-
// Allow CORS from "Web Wallet"
|
|
309
|
-
addCorsToSite(site, info.webWalletUrl);
|
|
310
|
-
}
|
|
311
256
|
}
|
|
312
257
|
|
|
313
258
|
return site;
|
|
@@ -317,9 +262,8 @@ const ensureLatestNodeInfo = async (sites = [], { withDefaultCors = true } = {})
|
|
|
317
262
|
/**
|
|
318
263
|
* set rule.to.target by interface.path and interface.prefix
|
|
319
264
|
*/
|
|
320
|
-
const ensureLatestInterfaceInfo = async (sites = []) => {
|
|
321
|
-
|
|
322
|
-
const interfaces = await states.blocklet.groupAllInterfaces();
|
|
265
|
+
const ensureLatestInterfaceInfo = async (sites = [], blocklets = []) => {
|
|
266
|
+
const interfaces = await states.blocklet.groupAllInterfaces(blocklets);
|
|
323
267
|
return sites.map((site) => {
|
|
324
268
|
if (!Array.isArray(site.rules)) {
|
|
325
269
|
return site;
|
|
@@ -370,6 +314,7 @@ const ensureWellknownRule = async (sites = []) => {
|
|
|
370
314
|
port: wellknownPort,
|
|
371
315
|
type: ROUTING_RULE_TYPES.GENERAL_PROXY,
|
|
372
316
|
interfaceName: BLOCKLET_INTERFACE_WELLKNOWN,
|
|
317
|
+
did: site.blockletDid,
|
|
373
318
|
},
|
|
374
319
|
isProtected: true,
|
|
375
320
|
});
|
|
@@ -391,7 +336,6 @@ const ensureWellknownRule = async (sites = []) => {
|
|
|
391
336
|
grouped['/'] = [
|
|
392
337
|
{
|
|
393
338
|
id: '',
|
|
394
|
-
groupId: '',
|
|
395
339
|
to: {
|
|
396
340
|
did: site.blockletDid,
|
|
397
341
|
componentId: site.blockletDid,
|
|
@@ -409,7 +353,6 @@ const ensureWellknownRule = async (sites = []) => {
|
|
|
409
353
|
if (!site.rules.some((x) => x.from.pathPrefix === servicePathPrefix)) {
|
|
410
354
|
site.rules.push({
|
|
411
355
|
id: rule.id,
|
|
412
|
-
groupId: rule.groupId,
|
|
413
356
|
from: {
|
|
414
357
|
pathPrefix: servicePathPrefix,
|
|
415
358
|
groupPathPrefix,
|
|
@@ -430,7 +373,6 @@ const ensureWellknownRule = async (sites = []) => {
|
|
|
430
373
|
if (!site.rules.some((x) => x.from.pathPrefix === avatarPathPrefix)) {
|
|
431
374
|
site.rules.push({
|
|
432
375
|
id: rule.id,
|
|
433
|
-
groupId: rule.groupId,
|
|
434
376
|
from: {
|
|
435
377
|
pathPrefix: avatarPathPrefix,
|
|
436
378
|
groupPathPrefix,
|
|
@@ -453,10 +395,8 @@ const ensureWellknownRule = async (sites = []) => {
|
|
|
453
395
|
return sites;
|
|
454
396
|
};
|
|
455
397
|
|
|
456
|
-
const ensureBlockletDid =
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
return (sites || []).map((site) => {
|
|
398
|
+
const ensureBlockletDid = (sites = [], info) => {
|
|
399
|
+
return sites.map((site) => {
|
|
460
400
|
if (site.domain === DOMAIN_FOR_INTERNAL_SITE) {
|
|
461
401
|
return site;
|
|
462
402
|
}
|
|
@@ -472,39 +412,6 @@ const ensureBlockletDid = async (sites) => {
|
|
|
472
412
|
});
|
|
473
413
|
};
|
|
474
414
|
|
|
475
|
-
const ensureCorsForWebWallet = async (sites) => {
|
|
476
|
-
const info = await states.node.read();
|
|
477
|
-
for (const site of sites) {
|
|
478
|
-
if (!isBasicSite(site.domain)) {
|
|
479
|
-
// Allow CORS from "Web Wallet"
|
|
480
|
-
addCorsToSite(site, info.webWalletUrl);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
return sites;
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* @description
|
|
488
|
-
* @param {Array<any>} [sites=[]]
|
|
489
|
-
* @param {Array<import('@blocklet/server-js').BlockletState>} blocklets
|
|
490
|
-
* @return {Promise<any>}
|
|
491
|
-
*/
|
|
492
|
-
const ensureCorsForDidSpace = (sites = [], blocklets) => {
|
|
493
|
-
return sites.map((site) => {
|
|
494
|
-
const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
|
|
495
|
-
if (blocklet) {
|
|
496
|
-
const endpoint = blocklet.environments.find(
|
|
497
|
-
(x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_BACKUP_ENDPOINT
|
|
498
|
-
);
|
|
499
|
-
if (endpoint && isUrl(endpoint.value)) {
|
|
500
|
-
addCorsToSite(site, endpoint.value);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
return site;
|
|
505
|
-
});
|
|
506
|
-
};
|
|
507
|
-
|
|
508
415
|
const filterSitesForRemovedBlocklets = (sites = [], blocklets) => {
|
|
509
416
|
return sites.filter((site) => {
|
|
510
417
|
if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
|
|
@@ -589,8 +496,6 @@ const ensureBlockletWellknownRules = (sites, blocklets) => {
|
|
|
589
496
|
.filter(Boolean);
|
|
590
497
|
};
|
|
591
498
|
|
|
592
|
-
// Expand component rules to blocklet rules
|
|
593
|
-
const isComponentRule = (x) => x.to.type === ROUTING_RULE_TYPES.COMPONENT;
|
|
594
499
|
const expandComponentRules = (sites = [], blocklets) => {
|
|
595
500
|
return sites
|
|
596
501
|
.map((site) => {
|
|
@@ -603,50 +508,43 @@ const expandComponentRules = (sites = [], blocklets) => {
|
|
|
603
508
|
}
|
|
604
509
|
|
|
605
510
|
const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
|
|
606
|
-
const components = blocklet.children.filter((x) => x.
|
|
607
|
-
const expandedRules =
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
} else {
|
|
634
|
-
newRule.from.pathPrefix = joinURL(baseRule.from.pathPrefix, x.mountPoint);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
expandedRules.push(newRule);
|
|
638
|
-
});
|
|
511
|
+
const components = blocklet.children.filter((x) => x.mode === BLOCKLET_MODES.PRODUCTION);
|
|
512
|
+
const expandedRules = components
|
|
513
|
+
.filter((x) => hasMountPoint(x.meta))
|
|
514
|
+
.map((x) => ({
|
|
515
|
+
id: UUID.v4(),
|
|
516
|
+
from: {
|
|
517
|
+
pathPrefix: x.mountPoint,
|
|
518
|
+
groupPathPrefix: '/',
|
|
519
|
+
},
|
|
520
|
+
to: {
|
|
521
|
+
type: ROUTING_RULE_TYPES.BLOCKLET,
|
|
522
|
+
componentId: getComponentId(x, [blocklet]),
|
|
523
|
+
interfaceName: BLOCKLET_INTERFACE_PUBLIC,
|
|
524
|
+
port: findInterfacePortByName(x, BLOCKLET_INTERFACE_PUBLIC),
|
|
525
|
+
did: blocklet.meta.did,
|
|
526
|
+
target: '/',
|
|
527
|
+
pageGroup: '',
|
|
528
|
+
},
|
|
529
|
+
}));
|
|
530
|
+
|
|
531
|
+
// logger.info('expandComponentRules.before.rules', { siteId: site.id, rules: site.rules, expandedRules });
|
|
532
|
+
site.rules = site.rules.filter((x) => x.isProtected).concat(expandedRules);
|
|
533
|
+
site.rules.forEach((x) => {
|
|
534
|
+
if (x.to.type === ROUTING_RULE_TYPES.COMPONENT) {
|
|
535
|
+
x.to.type = ROUTING_RULE_TYPES.BLOCKLET;
|
|
536
|
+
x.to.componentId = [blocklet.meta.did, x.to.componentId].join('/');
|
|
537
|
+
}
|
|
639
538
|
});
|
|
539
|
+
// logger.info('expandComponentRules.after.rules', { siteId: site.id, rules: site.rules });
|
|
640
540
|
|
|
641
|
-
site.rules = site.rules.filter((x) => !isComponentRule(x)).concat(expandedRules);
|
|
642
541
|
site.componentExpanded = true;
|
|
643
|
-
|
|
644
542
|
return site;
|
|
645
543
|
})
|
|
646
544
|
.filter(Boolean);
|
|
647
545
|
};
|
|
648
546
|
|
|
649
|
-
const ensureBlockletCache = (sites = [], blocklets) => {
|
|
547
|
+
const ensureBlockletCache = (sites = [], blocklets = []) => {
|
|
650
548
|
return sites
|
|
651
549
|
.map((site) => {
|
|
652
550
|
if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
|
|
@@ -696,7 +594,7 @@ const ensureBlockletCache = (sites = [], blocklets) => {
|
|
|
696
594
|
.filter(Boolean);
|
|
697
595
|
};
|
|
698
596
|
|
|
699
|
-
const ensureBlockletProxyBehavior = (sites, blocklets) => {
|
|
597
|
+
const ensureBlockletProxyBehavior = (sites, blocklets = []) => {
|
|
700
598
|
return sites
|
|
701
599
|
.map((site) => {
|
|
702
600
|
if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
|
|
@@ -727,21 +625,130 @@ const ensureBlockletProxyBehavior = (sites, blocklets) => {
|
|
|
727
625
|
.filter(Boolean);
|
|
728
626
|
};
|
|
729
627
|
|
|
730
|
-
|
|
731
|
-
|
|
628
|
+
/**
|
|
629
|
+
* Get static root path for a component
|
|
630
|
+
* @param {object} component - Blocklet component
|
|
631
|
+
* @returns {string|null} - Absolute path to static files root, or null if not available
|
|
632
|
+
*/
|
|
633
|
+
const getStaticRoot = (component) => {
|
|
634
|
+
const appDir = component.environments.find((e) => e.key === 'BLOCKLET_APP_DIR')?.value;
|
|
635
|
+
if (!appDir) {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const main = component.meta?.main;
|
|
640
|
+
return main ? path.join(appDir, main) : appDir;
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Check if a component can be served directly by Nginx
|
|
645
|
+
* Conditions:
|
|
646
|
+
* 1. Engine-based blocklet using 'blocklet' interpreter (static-server engine)
|
|
647
|
+
* 2. Engine source is the built-in static-server (not a custom engine)
|
|
648
|
+
* 3. Has a default security rule (pathPattern: '*') with ACCESS_POLICY_PUBLIC
|
|
649
|
+
*
|
|
650
|
+
* @param {object} component - Blocklet component
|
|
651
|
+
* @param {Array} securityRules - Security rules for the blocklet
|
|
652
|
+
* @returns {boolean} - True if can serve static directly
|
|
653
|
+
*/
|
|
654
|
+
const canServeStaticDirectly = (component, securityRules) => {
|
|
655
|
+
if (hasStartEngine(component.meta)) {
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (!securityRules || securityRules.length === 0) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const componentRules = securityRules.filter((x) => x.componentDid === component.meta.did);
|
|
664
|
+
if (componentRules.length) {
|
|
665
|
+
return componentRules.every((x) => x.accessPolicy.id === ACCESS_POLICY_PUBLIC);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const fallbackRules = securityRules.filter((x) => !x.componentDid);
|
|
669
|
+
if (fallbackRules.length) {
|
|
670
|
+
return fallbackRules.every((x) => x.accessPolicy.id === ACCESS_POLICY_PUBLIC);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return false;
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Ensure static serving info is added to routing rules for eligible blocklets
|
|
678
|
+
* This function adds serveStatic and staticRoot fields to rules that:
|
|
679
|
+
* 1. Are engine-based static blocklets
|
|
680
|
+
* 2. Have public access policy for all paths
|
|
681
|
+
*
|
|
682
|
+
* @param {Array} sites - Routing sites
|
|
683
|
+
* @param {Array} blocklets - All blocklets
|
|
684
|
+
* @param {object} teamManager - Team manager for querying security rules
|
|
685
|
+
* @returns {Promise<Array>} - Sites with static serving info added
|
|
686
|
+
*/
|
|
687
|
+
const ensureBlockletStaticServing = async (sites = [], blocklets = [], teamManager = null) => {
|
|
688
|
+
if (!teamManager) {
|
|
689
|
+
return sites;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const result = await Promise.all(
|
|
693
|
+
sites.map(async (site) => {
|
|
694
|
+
if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
|
|
695
|
+
return site;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
|
|
699
|
+
if (!blocklet) {
|
|
700
|
+
return site;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Get security rules for this blocklet
|
|
704
|
+
const { securityRules } = await getBlockletSecurityRules(
|
|
705
|
+
{ teamManager },
|
|
706
|
+
{ did: blocklet.meta.did, formatted: true }
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
// Process each rule
|
|
710
|
+
site.rules
|
|
711
|
+
.filter((x) => x.to.type === ROUTING_RULE_TYPES.BLOCKLET && x.to.interfaceName === BLOCKLET_INTERFACE_PUBLIC)
|
|
712
|
+
.forEach((rule) => {
|
|
713
|
+
const component = findComponentById(blocklet, rule.to.componentId);
|
|
714
|
+
if (!component) {
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Check if this component can be served directly
|
|
719
|
+
if (canServeStaticDirectly(component, securityRules)) {
|
|
720
|
+
const staticRoot = getStaticRoot(component);
|
|
721
|
+
logger.info('ensureBlockletStaticServing.canServeStaticDirectly', {
|
|
722
|
+
blockletDid: blocklet.meta.did,
|
|
723
|
+
componentDid: component.meta.did,
|
|
724
|
+
staticRoot,
|
|
725
|
+
});
|
|
726
|
+
if (staticRoot) {
|
|
727
|
+
rule.to.staticRoot = staticRoot;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
return site;
|
|
733
|
+
})
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
return result.filter(Boolean);
|
|
737
|
+
};
|
|
732
738
|
|
|
739
|
+
const ensureLatestInfo = async (sites = [], blocklets = [], teamManager = null, { nodeInfo = null } = {}) => {
|
|
740
|
+
const info = nodeInfo ?? (await states.node.read());
|
|
733
741
|
// CAUTION: following steps are very important, please do not change the order
|
|
734
|
-
let result =
|
|
735
|
-
result =
|
|
736
|
-
result =
|
|
737
|
-
result =
|
|
738
|
-
result =
|
|
742
|
+
let result = ensureLatestNodeInfo(sites, info);
|
|
743
|
+
result = ensureBlockletDid(result, info);
|
|
744
|
+
result = filterSitesForRemovedBlocklets(result, blocklets);
|
|
745
|
+
result = ensureBlockletProxyBehavior(result, blocklets);
|
|
746
|
+
result = ensureBlockletCache(result, blocklets);
|
|
739
747
|
result = await ensureWellknownRule(result);
|
|
740
|
-
result = await
|
|
741
|
-
result =
|
|
742
|
-
result =
|
|
743
|
-
result = await
|
|
744
|
-
result = await expandComponentRules(result, blocklets);
|
|
748
|
+
result = await ensureLatestInterfaceInfo(result, blocklets);
|
|
749
|
+
result = ensureBlockletWellknownRules(result, blocklets);
|
|
750
|
+
result = expandComponentRules(result, blocklets);
|
|
751
|
+
result = await ensureBlockletStaticServing(result, blocklets, teamManager);
|
|
745
752
|
|
|
746
753
|
return result;
|
|
747
754
|
};
|
|
@@ -763,15 +770,17 @@ const getDownloadCertBaseUrl = (info) =>
|
|
|
763
770
|
/**
|
|
764
771
|
* 根据 DID 获取域名
|
|
765
772
|
*/
|
|
766
|
-
const getDomainsByDid = async (did) => {
|
|
773
|
+
const getDomainsByDid = async (did, teamManager) => {
|
|
767
774
|
if (!did) {
|
|
768
775
|
return [];
|
|
769
776
|
}
|
|
770
777
|
|
|
771
778
|
try {
|
|
772
779
|
const sites = await states.site.getSitesByBlocklet(did);
|
|
780
|
+
const blocklet = await states.blocklet.getBlocklet(did);
|
|
781
|
+
const blocklets = blocklet ? [blocklet] : [];
|
|
773
782
|
const domainAliases = await attachRuntimeDomainAliases({
|
|
774
|
-
sites: await ensureLatestInfo(sites,
|
|
783
|
+
sites: await ensureLatestInfo(sites, blocklets, teamManager),
|
|
775
784
|
context: {},
|
|
776
785
|
});
|
|
777
786
|
|
|
@@ -786,7 +795,6 @@ const getDomainsByDid = async (did) => {
|
|
|
786
795
|
|
|
787
796
|
module.exports = function getRouterHelpers({
|
|
788
797
|
dataDirs,
|
|
789
|
-
routingSnapshot,
|
|
790
798
|
routerManager,
|
|
791
799
|
blockletManager,
|
|
792
800
|
certManager,
|
|
@@ -1049,6 +1057,7 @@ module.exports = function getRouterHelpers({
|
|
|
1049
1057
|
const newSiteRule = {
|
|
1050
1058
|
id: site.id,
|
|
1051
1059
|
rule,
|
|
1060
|
+
skipValidation: true, // Skip nginx validation for system rules - validated at end
|
|
1052
1061
|
};
|
|
1053
1062
|
|
|
1054
1063
|
const existingRule = findExistingRule(get(rule, 'from.pathPrefix'));
|
|
@@ -1067,9 +1076,13 @@ module.exports = function getRouterHelpers({
|
|
|
1067
1076
|
return false;
|
|
1068
1077
|
};
|
|
1069
1078
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1079
|
+
/**
|
|
1080
|
+
* Add wellknown site with routing rules
|
|
1081
|
+
* @param {object|null} site - Pre-fetched wellknown site or null if not exists
|
|
1082
|
+
* @param {object} context
|
|
1083
|
+
* @returns {Promise<boolean>} - true if routing changed
|
|
1084
|
+
*/
|
|
1085
|
+
const addWellknownSite = async (site, context) => {
|
|
1073
1086
|
try {
|
|
1074
1087
|
const info = await nodeState.read();
|
|
1075
1088
|
const proxyTarget = {
|
|
@@ -1161,12 +1174,19 @@ module.exports = function getRouterHelpers({
|
|
|
1161
1174
|
* Add system routing sites for the dashboard
|
|
1162
1175
|
* Which should contain: default site, ip site, wellknown site
|
|
1163
1176
|
*
|
|
1177
|
+
* Optimized to use O(1) targeted queries instead of loading all sites.
|
|
1178
|
+
* With thousands of blocklets, this avoids loading all sites into memory.
|
|
1179
|
+
*
|
|
1164
1180
|
* @returns {boolean} if routing changed
|
|
1165
1181
|
*/
|
|
1166
1182
|
const ensureDashboardRouting = async (context = {}) => {
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1183
|
+
// eslint-disable-next-line
|
|
1184
|
+
let [info, dashboardSite, defaultSite, wellknownSite] = await Promise.all([
|
|
1185
|
+
nodeState.read(),
|
|
1186
|
+
siteState.findOne({ domain: DOMAIN_FOR_IP_SITE }),
|
|
1187
|
+
siteState.findOne({ domain: DOMAIN_FOR_DEFAULT_SITE }),
|
|
1188
|
+
siteState.findOne({ name: NAME_FOR_WELLKNOWN_SITE }),
|
|
1189
|
+
]);
|
|
1170
1190
|
const updatedResult = [];
|
|
1171
1191
|
if (!dashboardSite) {
|
|
1172
1192
|
try {
|
|
@@ -1183,6 +1203,7 @@ module.exports = function getRouterHelpers({
|
|
|
1183
1203
|
],
|
|
1184
1204
|
},
|
|
1185
1205
|
skipCheckDynamicBlacklist: true,
|
|
1206
|
+
skipValidation: true, // Skip nginx validation for system sites
|
|
1186
1207
|
},
|
|
1187
1208
|
context
|
|
1188
1209
|
);
|
|
@@ -1221,8 +1242,7 @@ module.exports = function getRouterHelpers({
|
|
|
1221
1242
|
logger.error('add dashboard analytics rule failed', { error });
|
|
1222
1243
|
}
|
|
1223
1244
|
|
|
1224
|
-
|
|
1225
|
-
if (!defaultRule) {
|
|
1245
|
+
if (!defaultSite) {
|
|
1226
1246
|
try {
|
|
1227
1247
|
const result = await routerManager.addRoutingSite(
|
|
1228
1248
|
{
|
|
@@ -1231,6 +1251,7 @@ module.exports = function getRouterHelpers({
|
|
|
1231
1251
|
rules: [],
|
|
1232
1252
|
},
|
|
1233
1253
|
skipCheckDynamicBlacklist: true,
|
|
1254
|
+
skipValidation: true, // Skip nginx validation for system sites
|
|
1234
1255
|
},
|
|
1235
1256
|
context
|
|
1236
1257
|
);
|
|
@@ -1241,28 +1262,17 @@ module.exports = function getRouterHelpers({
|
|
|
1241
1262
|
}
|
|
1242
1263
|
}
|
|
1243
1264
|
|
|
1244
|
-
const wellknownRes = await addWellknownSite(
|
|
1265
|
+
const wellknownRes = await addWellknownSite(wellknownSite, context);
|
|
1245
1266
|
if (wellknownRes) {
|
|
1246
1267
|
updatedResult.push(wellknownRes);
|
|
1247
1268
|
}
|
|
1248
1269
|
|
|
1249
|
-
|
|
1250
|
-
// eslint-disable-next-line no-use-before-define
|
|
1251
|
-
const hash = await takeRoutingSnapshot({ message: 'ensure dashboard routing rules', dryRun: false }, context);
|
|
1252
|
-
logger.info('take routing snapshot on ensure dashboard routing rules', { updatedResult, hash });
|
|
1253
|
-
return true;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
return false;
|
|
1270
|
+
return updatedResult.length > 0;
|
|
1257
1271
|
};
|
|
1258
1272
|
|
|
1259
|
-
async function updateSiteDomainAliases(
|
|
1273
|
+
async function updateSiteDomainAliases(site, blocklet, nodeInfo) {
|
|
1260
1274
|
try {
|
|
1261
|
-
|
|
1262
|
-
* - 恢复 Blocklet
|
|
1263
|
-
*/
|
|
1264
|
-
// 因为更新路由是非常重要的操作,所以为了确保更新路由的稳定性,在更新域名时使用 try catch 来捕获异常
|
|
1265
|
-
const domainAliases = cloneDeep(existSite.domainAliases || []);
|
|
1275
|
+
const domainAliases = cloneDeep(site.domainAliases || []);
|
|
1266
1276
|
const didDomainList = getBlockletDidDomainList(blocklet, nodeInfo);
|
|
1267
1277
|
didDomainList.forEach((item) => {
|
|
1268
1278
|
if (!domainAliases.some((alias) => alias.value === item.value)) {
|
|
@@ -1270,27 +1280,28 @@ module.exports = function getRouterHelpers({
|
|
|
1270
1280
|
}
|
|
1271
1281
|
});
|
|
1272
1282
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1283
|
+
// let didDomain in front of ipEchoDnsDomain
|
|
1284
|
+
domainAliases.sort((a, b) => b.value.length - a.value.length);
|
|
1285
|
+
if (isEqual(site.domainAliases, domainAliases)) {
|
|
1286
|
+
logger.info('site domain aliases is up to date', {
|
|
1287
|
+
siteId: site.id,
|
|
1288
|
+
existedDomainAliases: site.domainAliases,
|
|
1276
1289
|
domainAliases,
|
|
1277
1290
|
didDomainList,
|
|
1278
1291
|
});
|
|
1279
1292
|
} else {
|
|
1280
|
-
await states.site.updateDomainAliasList(
|
|
1281
|
-
logger.info('update
|
|
1293
|
+
await states.site.updateDomainAliasList(site.id, domainAliases);
|
|
1294
|
+
logger.info('update site domain aliases', { site, domainAliases, didDomainList });
|
|
1295
|
+
return true;
|
|
1282
1296
|
}
|
|
1283
1297
|
} catch (error) {
|
|
1284
|
-
logger.error('update
|
|
1298
|
+
logger.error('update site domain aliases failed', { error, site });
|
|
1285
1299
|
}
|
|
1300
|
+
|
|
1301
|
+
return false;
|
|
1286
1302
|
}
|
|
1287
1303
|
|
|
1288
|
-
|
|
1289
|
-
* Add system sites for blocklet
|
|
1290
|
-
*
|
|
1291
|
-
* @returns {boolean} if routing state db changed
|
|
1292
|
-
*/
|
|
1293
|
-
const _ensureBlockletSites = async (blocklet, nodeInfo, context = {}) => {
|
|
1304
|
+
const _ensureBlockletRouting = async (blocklet, nodeInfo, context = {}) => {
|
|
1294
1305
|
const webInterface = (blocklet.meta.interfaces || []).find((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
|
|
1295
1306
|
if (!webInterface) {
|
|
1296
1307
|
return false;
|
|
@@ -1304,36 +1315,21 @@ module.exports = function getRouterHelpers({
|
|
|
1304
1315
|
};
|
|
1305
1316
|
|
|
1306
1317
|
const domainGroup = getBlockletDomainGroupName(blocklet.meta.did);
|
|
1307
|
-
|
|
1308
|
-
const pathPrefix = getPrefix(webInterface.prefix);
|
|
1309
1318
|
const rule = {
|
|
1310
|
-
from: { pathPrefix },
|
|
1319
|
+
from: { pathPrefix: getPrefix(webInterface.prefix) },
|
|
1311
1320
|
to: {
|
|
1312
1321
|
port: findInterfacePortByName(blocklet, webInterface.name),
|
|
1313
1322
|
did: blocklet.meta.did,
|
|
1314
1323
|
type: ROUTING_RULE_TYPES.BLOCKLET,
|
|
1315
1324
|
interfaceName: webInterface.name, // root blocklet interface
|
|
1316
1325
|
},
|
|
1317
|
-
isProtected: true,
|
|
1318
1326
|
};
|
|
1319
1327
|
|
|
1320
1328
|
const existSite = await states.site.findOne({ domain: domainGroup });
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
states,
|
|
1326
|
-
})
|
|
1327
|
-
.then(() => {
|
|
1328
|
-
logger.info('updated blocklet dns document', {
|
|
1329
|
-
did: blocklet.appPid,
|
|
1330
|
-
});
|
|
1331
|
-
})
|
|
1332
|
-
.catch((err) => {
|
|
1333
|
-
logger.error('update blocklet dns document failed', { did: blocklet.meta.did, error: err });
|
|
1334
|
-
});
|
|
1335
|
-
|
|
1336
|
-
if (!existSite) {
|
|
1329
|
+
if (existSite) {
|
|
1330
|
+
await updateSiteDomainAliases(existSite, blocklet, nodeInfo);
|
|
1331
|
+
logger.info('site already exists', { did: blocklet.meta.did, site: existSite });
|
|
1332
|
+
} else {
|
|
1337
1333
|
const domainAliases = getBlockletDidDomainList(blocklet, nodeInfo);
|
|
1338
1334
|
// let didDomain in front of ipEchoDnsDomain
|
|
1339
1335
|
domainAliases.push({ value: getIpDnsDomainForBlocklet(blocklet), isProtected: true });
|
|
@@ -1351,7 +1347,7 @@ module.exports = function getRouterHelpers({
|
|
|
1351
1347
|
});
|
|
1352
1348
|
rules.push(...rulesInPack);
|
|
1353
1349
|
|
|
1354
|
-
await routerManager.addRoutingSite(
|
|
1350
|
+
const created = await routerManager.addRoutingSite(
|
|
1355
1351
|
{
|
|
1356
1352
|
site: {
|
|
1357
1353
|
domain: domainGroup,
|
|
@@ -1364,7 +1360,12 @@ module.exports = function getRouterHelpers({
|
|
|
1364
1360
|
},
|
|
1365
1361
|
context
|
|
1366
1362
|
);
|
|
1367
|
-
logger.info('
|
|
1363
|
+
logger.info('create routing site for blocklet', {
|
|
1364
|
+
did: blocklet.meta.did,
|
|
1365
|
+
siteId: created.id,
|
|
1366
|
+
rules,
|
|
1367
|
+
domainAliases,
|
|
1368
|
+
});
|
|
1368
1369
|
|
|
1369
1370
|
const bindDomainCap = await states.blockletExtras.getSettings(blocklet.meta.did, 'bindDomainCap');
|
|
1370
1371
|
const nftDid = await states.blockletExtras.getSettings(blocklet.meta.did, 'domainNftDid');
|
|
@@ -1436,35 +1437,35 @@ module.exports = function getRouterHelpers({
|
|
|
1436
1437
|
chainHost: context.domainChainHost,
|
|
1437
1438
|
});
|
|
1438
1439
|
}
|
|
1439
|
-
|
|
1440
|
-
return true;
|
|
1441
1440
|
}
|
|
1442
1441
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1442
|
+
updateBlockletDocument({
|
|
1443
|
+
did: blocklet.meta.did,
|
|
1444
|
+
nodeInfo,
|
|
1445
|
+
teamManager,
|
|
1446
|
+
states,
|
|
1447
|
+
})
|
|
1448
|
+
.then(() => {
|
|
1449
|
+
logger.info('updated did document succeed on add blocklet', { did: blocklet.meta.did });
|
|
1446
1450
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
rule,
|
|
1451
|
+
// Trigger DNS lookup to warm up DNS cache (non-blocking)
|
|
1452
|
+
// This helps reduce DNS resolution time when user accesses the blocklet after install
|
|
1453
|
+
const domain = getDidDomainForBlocklet({
|
|
1454
|
+
did: blocklet.meta.did,
|
|
1455
|
+
didDomain: nodeInfo.didDomain || DEFAULT_DID_DOMAIN,
|
|
1456
|
+
});
|
|
1457
|
+
dns
|
|
1458
|
+
.resolve(domain)
|
|
1459
|
+
.then((result) => {
|
|
1460
|
+
logger.info('dns cache warm-up completed', { did: blocklet.meta.did, domain, result });
|
|
1461
|
+
})
|
|
1462
|
+
.catch((error) => {
|
|
1463
|
+
logger.warn('dns cache warm-up failed', { did: blocklet.meta.did, domain, error: error.message });
|
|
1464
|
+
});
|
|
1465
|
+
})
|
|
1466
|
+
.catch((err) => {
|
|
1467
|
+
logger.error('update did document failed on add blocklet', { did: blocklet.meta.did, error: err });
|
|
1465
1468
|
});
|
|
1466
|
-
logger.info('add routing rule for site', { site: domainGroup });
|
|
1467
|
-
}
|
|
1468
1469
|
|
|
1469
1470
|
return true;
|
|
1470
1471
|
};
|
|
@@ -1513,12 +1514,7 @@ module.exports = function getRouterHelpers({
|
|
|
1513
1514
|
|
|
1514
1515
|
try {
|
|
1515
1516
|
const nodeInfo = await nodeState.read();
|
|
1516
|
-
|
|
1517
|
-
if (!hasWebInterface) {
|
|
1518
|
-
return false;
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
return await _ensureBlockletSites(blocklet, nodeInfo, context);
|
|
1517
|
+
return await _ensureBlockletRouting(blocklet, nodeInfo, context);
|
|
1522
1518
|
} finally {
|
|
1523
1519
|
await ensureBlockletRoutingLock.releaseLock(lockName);
|
|
1524
1520
|
}
|
|
@@ -1634,22 +1630,49 @@ module.exports = function getRouterHelpers({
|
|
|
1634
1630
|
return ruleChanged || siteChanged;
|
|
1635
1631
|
};
|
|
1636
1632
|
|
|
1637
|
-
async function
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
}
|
|
1633
|
+
async function readAllRoutingSites() {
|
|
1634
|
+
const sites = await siteState.getSites();
|
|
1635
|
+
const blocklets = await states.blocklet.getBlocklets();
|
|
1636
|
+
return {
|
|
1637
|
+
sites: await ensureLatestInfo(sites, blocklets, teamManager),
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1643
1640
|
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1641
|
+
/**
|
|
1642
|
+
* Lightweight version of readAllRoutingSites for a single blocklet
|
|
1643
|
+
* Only queries the specific blocklet's data from database
|
|
1644
|
+
* @param {string} blockletDid - The blocklet DID
|
|
1645
|
+
* @returns {Promise<Array>}
|
|
1646
|
+
*/
|
|
1647
|
+
async function readBlockletRoutingSite(blockletDid) {
|
|
1648
|
+
const [site, blocklet] = await Promise.all([
|
|
1649
|
+
siteState.findOneByBlocklet(blockletDid),
|
|
1650
|
+
states.blocklet.getBlocklet(blockletDid),
|
|
1651
|
+
]);
|
|
1652
|
+
|
|
1653
|
+
if (!site || !blocklet) {
|
|
1654
|
+
logger.warn('readBlockletRoutingSite: site or blocklet not found', {
|
|
1655
|
+
blockletDid,
|
|
1656
|
+
hasSite: !!site,
|
|
1657
|
+
hasBlocklet: !!blocklet,
|
|
1658
|
+
});
|
|
1659
|
+
return [];
|
|
1647
1660
|
}
|
|
1648
1661
|
|
|
1649
|
-
|
|
1650
|
-
|
|
1662
|
+
return ensureLatestInfo([site], [blocklet], teamManager);
|
|
1663
|
+
}
|
|
1651
1664
|
|
|
1652
|
-
|
|
1665
|
+
/**
|
|
1666
|
+
* Lightweight version of readAllRoutingSites for system sites only (no blocklet sites)
|
|
1667
|
+
* O(1) complexity - only queries system sites from database
|
|
1668
|
+
* System sites: dashboard, wellknown, IP site, default site
|
|
1669
|
+
* @returns {Promise<{sites: Array}>}
|
|
1670
|
+
*/
|
|
1671
|
+
async function readSystemRoutingSites() {
|
|
1672
|
+
// getSystemSites() returns sites where domain NOT LIKE '%@blocklet-site-group'
|
|
1673
|
+
const systemSites = await siteState.getSystemSites();
|
|
1674
|
+
const sites = await ensureLatestInfo(systemSites, [], teamManager);
|
|
1675
|
+
return { sites };
|
|
1653
1676
|
}
|
|
1654
1677
|
|
|
1655
1678
|
async function resetSiteByDid(did, { refreshRouterProvider = true } = {}) {
|
|
@@ -1658,16 +1681,13 @@ module.exports = function getRouterHelpers({
|
|
|
1658
1681
|
await ensureBlockletRouting(blocklet);
|
|
1659
1682
|
if (refreshRouterProvider) {
|
|
1660
1683
|
// eslint-disable-next-line no-use-before-define
|
|
1661
|
-
|
|
1662
|
-
logger.info('reset blocklet routing rules', { did, hash });
|
|
1684
|
+
await handleBlockletRouting({ did, message: 'reset blocklet routing rules' });
|
|
1663
1685
|
}
|
|
1664
1686
|
}
|
|
1665
1687
|
|
|
1666
|
-
const providers = {}; // we need to keep reference for different router instances
|
|
1667
|
-
|
|
1668
1688
|
const startAccessLogWatcher = (info) => {
|
|
1669
1689
|
const providerName = get(info, 'routing.provider', null);
|
|
1670
|
-
if (!providerName || !providers[providerName] ||
|
|
1690
|
+
if (!providerName || !providers[providerName] || isWorkerInstance()) {
|
|
1671
1691
|
return;
|
|
1672
1692
|
}
|
|
1673
1693
|
|
|
@@ -1693,7 +1713,7 @@ module.exports = function getRouterHelpers({
|
|
|
1693
1713
|
|
|
1694
1714
|
const startErrorLogWatcher = async (info, shouldScheduleReload = false) => {
|
|
1695
1715
|
const providerName = get(info, 'routing.provider', null);
|
|
1696
|
-
if (!providerName || !providers[providerName] ||
|
|
1716
|
+
if (!providerName || !providers[providerName] || isWorkerInstance()) {
|
|
1697
1717
|
return;
|
|
1698
1718
|
}
|
|
1699
1719
|
|
|
@@ -1724,19 +1744,20 @@ module.exports = function getRouterHelpers({
|
|
|
1724
1744
|
logger.debug('router error detected', { logEntries, grouped, blocked, timestamp: dayjs().unix() });
|
|
1725
1745
|
|
|
1726
1746
|
if (blocked.length) {
|
|
1727
|
-
|
|
1728
|
-
|
|
1747
|
+
// Queue global change to update blacklist in nginx config
|
|
1748
|
+
providers[providerName].queueChange('global');
|
|
1749
|
+
// Schedule reload after block expiration to remove IPs from blacklist
|
|
1729
1750
|
uniq(blocked).forEach((timeout) => {
|
|
1730
1751
|
setTimeout(() => {
|
|
1731
1752
|
logger.info('router reload on block expire', { timeout, timestamp: dayjs().unix() });
|
|
1732
|
-
providers[providerName].
|
|
1753
|
+
providers[providerName].queueChange('global');
|
|
1733
1754
|
}, timeout + 1000);
|
|
1734
1755
|
});
|
|
1735
1756
|
}
|
|
1736
1757
|
});
|
|
1737
1758
|
}
|
|
1738
1759
|
|
|
1739
|
-
// Schedule router reload for active blacklist
|
|
1760
|
+
// Schedule router reload for active blacklist expiration
|
|
1740
1761
|
if (daemon && shouldScheduleReload) {
|
|
1741
1762
|
const blacklist = await blocker.getActiveBlacklist(false);
|
|
1742
1763
|
if (blacklist.length) {
|
|
@@ -1746,58 +1767,77 @@ module.exports = function getRouterHelpers({
|
|
|
1746
1767
|
const timeout = x.expiresAt - now + 1;
|
|
1747
1768
|
setTimeout(() => {
|
|
1748
1769
|
logger.info('router reload on block expire', { ip: x.key });
|
|
1749
|
-
providers[providerName].
|
|
1770
|
+
providers[providerName].queueChange('global');
|
|
1750
1771
|
}, timeout * 1000);
|
|
1751
1772
|
});
|
|
1752
1773
|
}
|
|
1753
1774
|
}
|
|
1754
1775
|
};
|
|
1755
1776
|
|
|
1756
|
-
const
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1777
|
+
const providers = {}; // we need to keep reference for different router instances
|
|
1778
|
+
const providerInitPromises = {}; // track ongoing initializations for race condition handling
|
|
1779
|
+
|
|
1780
|
+
/**
|
|
1781
|
+
* Ensure routing provider is initialized (idempotent, race-condition safe)
|
|
1782
|
+
* Multiple concurrent calls will await the same initialization promise
|
|
1783
|
+
* @returns {Promise<{ provider: Router, nodeInfo: object, providerName: string }>}
|
|
1784
|
+
*/
|
|
1785
|
+
const ensureRoutingProvider = async () => {
|
|
1786
|
+
const nodeInfo = await nodeState.read();
|
|
1787
|
+
const providerName = get(nodeInfo, 'routing.provider');
|
|
1762
1788
|
const httpsEnabled = get(nodeInfo, 'routing.https', true);
|
|
1763
|
-
logger.debug('handle routing', { providerName, httpsEnabled });
|
|
1764
1789
|
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
throw new Error(`${providerName} pre-check failed, ${checkResult.error}`);
|
|
1790
|
+
// Already initialized - fast path
|
|
1791
|
+
if (providers[providerName]) {
|
|
1792
|
+
return { provider: providers[providerName], nodeInfo, providerName };
|
|
1769
1793
|
}
|
|
1770
1794
|
|
|
1771
|
-
|
|
1795
|
+
// Already initializing - await the same promise
|
|
1796
|
+
if (providerInitPromises[providerName]) {
|
|
1797
|
+
await providerInitPromises[providerName];
|
|
1798
|
+
return { provider: providers[providerName], nodeInfo, providerName };
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// Start initialization - store promise so concurrent calls can await it
|
|
1802
|
+
providerInitPromises[providerName] = (async () => {
|
|
1803
|
+
const startedAt = Date.now();
|
|
1804
|
+
logger.info('ensureRoutingProvider: initializing', { providerName, httpsEnabled });
|
|
1805
|
+
|
|
1806
|
+
const Provider = getProvider(providerName);
|
|
1807
|
+
const checkResult = await Provider.check({ configDir: dataDirs.router });
|
|
1808
|
+
if (!checkResult.available) {
|
|
1809
|
+
throw new Error(`routing provider ${providerName} pre-check failed, ${checkResult.error}`);
|
|
1810
|
+
}
|
|
1772
1811
|
|
|
1773
|
-
if (providers[providerName]) {
|
|
1774
|
-
await providers[providerName].reload();
|
|
1775
|
-
} else {
|
|
1776
1812
|
providers[providerName] = new Router({
|
|
1777
1813
|
provider: createProviderInstance({ nodeInfo, routerDataDir: dataDirs.router }),
|
|
1778
|
-
|
|
1814
|
+
getAllRoutingParams: async () => {
|
|
1779
1815
|
try {
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1816
|
+
// Parallelize all independent async operations
|
|
1817
|
+
const [info, { sites }, services, certificates, wafDisabledList] = await Promise.all([
|
|
1818
|
+
nodeState._read(),
|
|
1819
|
+
readAllRoutingSites(),
|
|
1820
|
+
blockletState.getServices(),
|
|
1821
|
+
httpsEnabled ? certManager.getAllNormal() : Promise.resolve([]),
|
|
1822
|
+
states.blockletExtras.getWafDisabledBlocklets(),
|
|
1823
|
+
]);
|
|
1824
|
+
|
|
1825
|
+
// Fetch site info for WAF disabled blocklets in parallel
|
|
1826
|
+
const wafDisabledBlocklets = await Promise.all(
|
|
1827
|
+
wafDisabledList.map((x) =>
|
|
1828
|
+
states.site.findOneByBlocklet(x.did).then((result) => ({ did: x.did, site: result }))
|
|
1829
|
+
)
|
|
1830
|
+
);
|
|
1831
|
+
|
|
1832
|
+
logger.info('router:getAllRoutingParams read routing params', { services });
|
|
1789
1833
|
|
|
1790
1834
|
return {
|
|
1791
|
-
sites,
|
|
1835
|
+
sites: await ensureRootRule(sites),
|
|
1792
1836
|
certificates,
|
|
1793
1837
|
headers: get(nodeInfo, 'routing.headers', {}),
|
|
1794
|
-
services
|
|
1838
|
+
services,
|
|
1795
1839
|
nodeInfo: info,
|
|
1796
|
-
wafDisabledBlocklets
|
|
1797
|
-
(await states.blockletExtras.getWafDisabledBlocklets()).map((x) =>
|
|
1798
|
-
states.site.findOneByBlocklet(x.did).then((result) => ({ did: x.did, site: result }))
|
|
1799
|
-
)
|
|
1800
|
-
),
|
|
1840
|
+
wafDisabledBlocklets,
|
|
1801
1841
|
};
|
|
1802
1842
|
} catch (err) {
|
|
1803
1843
|
logger.error('Read routing rules failed', { error: err });
|
|
@@ -1812,8 +1852,98 @@ module.exports = function getRouterHelpers({
|
|
|
1812
1852
|
return {};
|
|
1813
1853
|
}
|
|
1814
1854
|
},
|
|
1855
|
+
// Lightweight getter for single blocklet updates - O(1) database queries
|
|
1856
|
+
getBlockletRoutingParams: async (blockletDid) => {
|
|
1857
|
+
try {
|
|
1858
|
+
// Parallelize all independent async operations
|
|
1859
|
+
const [info, sites, certificates] = await Promise.all([
|
|
1860
|
+
nodeState._read(),
|
|
1861
|
+
readBlockletRoutingSite(blockletDid),
|
|
1862
|
+
httpsEnabled ? certManager.getAllNormal() : Promise.resolve([]),
|
|
1863
|
+
]);
|
|
1864
|
+
|
|
1865
|
+
if (!sites || sites.length === 0) {
|
|
1866
|
+
logger.warn('router: getBlockletRoutingParams empty', { blockletDid });
|
|
1867
|
+
return null;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
logger.info('router: getBlockletRoutingParams success', { blockletDid, sites });
|
|
1871
|
+
|
|
1872
|
+
return {
|
|
1873
|
+
sites,
|
|
1874
|
+
certificates,
|
|
1875
|
+
headers: get(info, 'routing.headers', {}),
|
|
1876
|
+
nodeInfo: info,
|
|
1877
|
+
wafDisabledBlocklets: [],
|
|
1878
|
+
};
|
|
1879
|
+
} catch (err) {
|
|
1880
|
+
logger.error('router: getBlockletRoutingParams failed', { blockletDid, error: err });
|
|
1881
|
+
return null;
|
|
1882
|
+
}
|
|
1883
|
+
},
|
|
1884
|
+
// Lightweight getter for global/system routing params - O(1) database queries
|
|
1885
|
+
// Returns only system sites (dashboard, wellknown, IP, default) + services + global policies
|
|
1886
|
+
getSystemRoutingParams: async () => {
|
|
1887
|
+
try {
|
|
1888
|
+
// Parallelize all independent async operations
|
|
1889
|
+
const [info, { sites }, services, certificates, wafDisabledList] = await Promise.all([
|
|
1890
|
+
nodeState._read(),
|
|
1891
|
+
readSystemRoutingSites(),
|
|
1892
|
+
blockletState.getServices(),
|
|
1893
|
+
httpsEnabled ? certManager.getAllNormal() : Promise.resolve([]),
|
|
1894
|
+
states.blockletExtras.getWafDisabledBlocklets(),
|
|
1895
|
+
]);
|
|
1896
|
+
|
|
1897
|
+
// Fetch site info for WAF disabled blocklets in parallel
|
|
1898
|
+
const wafDisabledBlocklets = await Promise.all(
|
|
1899
|
+
wafDisabledList.map((x) =>
|
|
1900
|
+
states.site.findOneByBlocklet(x.did).then((result) => ({ did: x.did, site: result }))
|
|
1901
|
+
)
|
|
1902
|
+
);
|
|
1903
|
+
|
|
1904
|
+
logger.info('router: getSystemRoutingParams success', {
|
|
1905
|
+
services,
|
|
1906
|
+
wafDisabledBlocklets,
|
|
1907
|
+
sites,
|
|
1908
|
+
});
|
|
1909
|
+
|
|
1910
|
+
return {
|
|
1911
|
+
sites,
|
|
1912
|
+
certificates,
|
|
1913
|
+
services,
|
|
1914
|
+
headers: get(info, 'routing.headers', {}),
|
|
1915
|
+
nodeInfo: info,
|
|
1916
|
+
wafDisabledBlocklets,
|
|
1917
|
+
};
|
|
1918
|
+
} catch (err) {
|
|
1919
|
+
logger.error('router: getSystemRoutingParams failed', { error: err });
|
|
1920
|
+
return null;
|
|
1921
|
+
}
|
|
1922
|
+
},
|
|
1815
1923
|
});
|
|
1816
1924
|
|
|
1925
|
+
// Helper to find affected blocklets by certificate
|
|
1926
|
+
const findAffectedBlockletsByCert = async (cert) => {
|
|
1927
|
+
if (!cert) return [];
|
|
1928
|
+
|
|
1929
|
+
const sites = await siteState.getSites();
|
|
1930
|
+
const affectedDids = new Set();
|
|
1931
|
+
|
|
1932
|
+
for (const site of sites) {
|
|
1933
|
+
if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) continue;
|
|
1934
|
+
|
|
1935
|
+
const domains = [site.domain, ...(site.domainAliases || []).map((x) => x.value)];
|
|
1936
|
+
const isMatch = domains.some((domain) => domain && routerManager.isCertMatchedDomain(cert, domain));
|
|
1937
|
+
|
|
1938
|
+
if (isMatch) {
|
|
1939
|
+
affectedDids.add(site.domain.replace(BLOCKLET_SITE_GROUP_SUFFIX, ''));
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
return Array.from(affectedDids);
|
|
1944
|
+
};
|
|
1945
|
+
|
|
1946
|
+
// Set up cert event handlers - trigger targeted routing updates
|
|
1817
1947
|
[
|
|
1818
1948
|
BlockletEvents.certIssued,
|
|
1819
1949
|
EVENTS.CERT_ADDED,
|
|
@@ -1821,16 +1951,85 @@ module.exports = function getRouterHelpers({
|
|
|
1821
1951
|
EVENTS.CERT_ISSUED,
|
|
1822
1952
|
EVENTS.CERT_UPDATED,
|
|
1823
1953
|
].forEach((event) => {
|
|
1824
|
-
certManager.on(event, () =>
|
|
1954
|
+
certManager.on(event, async (cert) => {
|
|
1955
|
+
if (!cert) return;
|
|
1956
|
+
|
|
1957
|
+
logger.info('cert event triggered routing update', { event, domain: cert.domain });
|
|
1958
|
+
|
|
1959
|
+
// Always update global routing for cert changes
|
|
1960
|
+
// eslint-disable-next-line no-use-before-define
|
|
1961
|
+
await handleSystemRouting({ message: `cert event: ${event}` });
|
|
1962
|
+
|
|
1963
|
+
// Find and update affected blocklets
|
|
1964
|
+
const affectedDids = await findAffectedBlockletsByCert(cert);
|
|
1965
|
+
for (const did of affectedDids) {
|
|
1966
|
+
// eslint-disable-next-line no-await-in-loop, no-use-before-define
|
|
1967
|
+
await handleBlockletRouting({ did, message: `cert event: ${event}` });
|
|
1968
|
+
}
|
|
1969
|
+
});
|
|
1825
1970
|
});
|
|
1826
1971
|
|
|
1827
|
-
await
|
|
1972
|
+
await startAccessLogWatcher(nodeInfo);
|
|
1973
|
+
await startErrorLogWatcher(nodeInfo, true);
|
|
1974
|
+
|
|
1975
|
+
logger.info('ensureRoutingProvider: initialized', { providerName, duration: Date.now() - startedAt });
|
|
1976
|
+
})();
|
|
1977
|
+
|
|
1978
|
+
try {
|
|
1979
|
+
await providerInitPromises[providerName];
|
|
1980
|
+
} finally {
|
|
1981
|
+
// Clean up promise regardless of success/failure
|
|
1982
|
+
delete providerInitPromises[providerName];
|
|
1828
1983
|
}
|
|
1829
1984
|
|
|
1830
|
-
|
|
1831
|
-
|
|
1985
|
+
return { provider: providers[providerName], nodeInfo, providerName };
|
|
1986
|
+
};
|
|
1832
1987
|
|
|
1833
|
-
|
|
1988
|
+
/**
|
|
1989
|
+
* Lightweight handler for global/system routing updates - O(1) complexity
|
|
1990
|
+
* Use this for changes that only affect global config (policies, system sites, services)
|
|
1991
|
+
* @param {Object} options
|
|
1992
|
+
* @param {string} [options.message] - Log message describing the change
|
|
1993
|
+
*/
|
|
1994
|
+
const handleSystemRouting = async ({ message = '' } = {}) => {
|
|
1995
|
+
logger.info('handleSystemRouting: triggered', { message });
|
|
1996
|
+
const { provider, providerName } = await ensureRoutingProvider();
|
|
1997
|
+
provider.queueChange('global');
|
|
1998
|
+
logger.info('handleSystemRouting: queued', { message, providerName });
|
|
1999
|
+
};
|
|
2000
|
+
|
|
2001
|
+
/**
|
|
2002
|
+
* Lightweight handler for single blocklet routing updates - O(2) complexity
|
|
2003
|
+
* Updates blocklet config + global config (for services that may have changed)
|
|
2004
|
+
* @param {Object} options
|
|
2005
|
+
* @param {string} options.did - Blocklet DID
|
|
2006
|
+
* @param {string} [options.message] - Log message describing the change
|
|
2007
|
+
* @param {boolean} [options.isRemoval] - Whether this is a blocklet removal
|
|
2008
|
+
*/
|
|
2009
|
+
const handleBlockletRouting = async ({ did, message = '', isRemoval = false }) => {
|
|
2010
|
+
logger.info('handleBlockletRouting: triggered', { did, message, isRemoval });
|
|
2011
|
+
if (!did) {
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
const { provider, providerName } = await ensureRoutingProvider();
|
|
2016
|
+
const changeType = isRemoval ? 'blocklet-remove' : 'blocklet';
|
|
2017
|
+
provider.queueChange('global');
|
|
2018
|
+
provider.queueChange(changeType, did);
|
|
2019
|
+
logger.info('handleBlockletRouting: queued', { did, message, isRemoval, providerName });
|
|
2020
|
+
};
|
|
2021
|
+
|
|
2022
|
+
/**
|
|
2023
|
+
* Full regeneration handler - O(N) complexity
|
|
2024
|
+
* Use this for startup, manual rebuild, or when complete regeneration is needed
|
|
2025
|
+
* @param {Object} options
|
|
2026
|
+
* @param {string} [options.message] - Log message describing the change
|
|
2027
|
+
*/
|
|
2028
|
+
const handleAllRouting = async ({ message = '' } = {}) => {
|
|
2029
|
+
logger.info('handleAllRouting: triggered', { message });
|
|
2030
|
+
const { provider, providerName } = await ensureRoutingProvider();
|
|
2031
|
+
await provider.regenerateAll({ message });
|
|
2032
|
+
logger.info('handleAllRouting: done', { message, providerName });
|
|
1834
2033
|
};
|
|
1835
2034
|
|
|
1836
2035
|
const rotateRouterLog = async () => {
|
|
@@ -1846,8 +2045,6 @@ module.exports = function getRouterHelpers({
|
|
|
1846
2045
|
|
|
1847
2046
|
const analyzeRouterLog = async () => {
|
|
1848
2047
|
const info = await nodeState.read();
|
|
1849
|
-
// eslint-disable-next-line no-use-before-define
|
|
1850
|
-
const sites = await getRoutingSites({});
|
|
1851
2048
|
const providerName = get(info, 'routing.provider', null);
|
|
1852
2049
|
if (!providerName || !providers[providerName]) {
|
|
1853
2050
|
logger.warn('No router provider instance found');
|
|
@@ -1855,6 +2052,8 @@ module.exports = function getRouterHelpers({
|
|
|
1855
2052
|
}
|
|
1856
2053
|
|
|
1857
2054
|
const groups = [];
|
|
2055
|
+
const sites = await attachRuntimeDomainAliases({ sites: await siteState.getSites(), context: {} });
|
|
2056
|
+
|
|
1858
2057
|
const server = sites.find((x) =>
|
|
1859
2058
|
x.rules.some((rule) => rule.to.type === ROUTING_RULE_TYPES.DAEMON && rule.to.did === info.did)
|
|
1860
2059
|
);
|
|
@@ -1935,124 +2134,10 @@ module.exports = function getRouterHelpers({
|
|
|
1935
2134
|
logger.info('refresh gateway blacklist from cron', { hash, blacklist });
|
|
1936
2135
|
};
|
|
1937
2136
|
|
|
1938
|
-
const
|
|
1939
|
-
const
|
|
1940
|
-
const
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
// Repopulate routing rules if we have changed the routing snapshot
|
|
1944
|
-
const isValidSnapshot = newSnapshotHash ? await routingSnapshot.hasSnapshot(newSnapshotHash) : false;
|
|
1945
|
-
if (isValidSnapshot) {
|
|
1946
|
-
if (!oldSnapshotHash || oldSnapshotHash !== newSnapshotHash || forceRepopulate) {
|
|
1947
|
-
const { sites, meta } = await routingSnapshot.readSnapshot(newSnapshotHash);
|
|
1948
|
-
const result = await routerManager.repopulateRouting({ sites });
|
|
1949
|
-
await routingSnapshot.restoreMetaData(meta);
|
|
1950
|
-
logger.info(`repopulate routing rules from snapshot: ${newSnapshotHash}`, { result });
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
|
|
1954
|
-
const newProvider = params.provider || getProviderFromNodeInfo(info);
|
|
1955
|
-
const result = await nodeState.updateNodeRouting({
|
|
1956
|
-
...info.routing,
|
|
1957
|
-
snapshotHash: newSnapshotHash || oldSnapshotHash || '',
|
|
1958
|
-
provider: newProvider,
|
|
1959
|
-
});
|
|
1960
|
-
await handleRouting(result);
|
|
1961
|
-
|
|
1962
|
-
if (newProvider !== info.routing.provider) {
|
|
1963
|
-
// Ensure we have system sites for daemon
|
|
1964
|
-
await ensureDashboardRouting(context);
|
|
1965
|
-
|
|
1966
|
-
// Ensure we have system rules for blocklets
|
|
1967
|
-
const blocklets = await blockletState.getBlocklets();
|
|
1968
|
-
const ensureBlocklet = async (x) => {
|
|
1969
|
-
const blocklet = await blockletManager.getBlocklet(x.meta.did);
|
|
1970
|
-
return ensureBlockletRouting(blocklet, context);
|
|
1971
|
-
};
|
|
1972
|
-
const ensureBlockletResults = await Promise.all(blocklets.map((x) => ensureBlocklet(x)));
|
|
1973
|
-
|
|
1974
|
-
// We need to take snapshot after system rules ensured
|
|
1975
|
-
if (ensureBlockletResults.filter(Boolean).length) {
|
|
1976
|
-
// eslint-disable-next-line no-use-before-define
|
|
1977
|
-
await takeRoutingSnapshot({ message: `Switch routing engine to ${newProvider}`, dryRun: false }, context);
|
|
1978
|
-
logger.info(`take routing snapshot on switch engine: ${newProvider}`, { ensureBlockletResults });
|
|
1979
|
-
}
|
|
1980
|
-
|
|
1981
|
-
const Provider = getProvider(info.routing.provider);
|
|
1982
|
-
if (Provider) {
|
|
1983
|
-
try {
|
|
1984
|
-
const providerInstance = createProviderInstance({
|
|
1985
|
-
nodeInfo: info,
|
|
1986
|
-
routerDataDir: dataDirs.router,
|
|
1987
|
-
});
|
|
1988
|
-
|
|
1989
|
-
await providerInstance.stop();
|
|
1990
|
-
logger.info('original router stopped:', { provider: info.routing.provider });
|
|
1991
|
-
} catch (err) {
|
|
1992
|
-
logger.error('original router stop failed:', { provider: info.routing.provider, error: err });
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
return result;
|
|
1998
|
-
};
|
|
1999
|
-
|
|
2000
|
-
/**
|
|
2001
|
-
* @param {string} message
|
|
2002
|
-
* @param {boolean} dryRun
|
|
2003
|
-
* @param {boolean} handleRouting if NOT dryRun and handleRouting is true, will run handleRouting() after update routing
|
|
2004
|
-
* @returns
|
|
2005
|
-
*/
|
|
2006
|
-
// eslint-disable-next-line no-unused-vars
|
|
2007
|
-
const takeRoutingSnapshot = async ({ message, dryRun = true, handleRouting: handle = true }, context = {}) => {
|
|
2008
|
-
const msg = decodeURIComponent(message);
|
|
2009
|
-
|
|
2010
|
-
if (!msg) {
|
|
2011
|
-
throw new Error('Please provide a message for this snapshot');
|
|
2012
|
-
}
|
|
2013
|
-
if (!dryRun) {
|
|
2014
|
-
if (msg.length < 5) {
|
|
2015
|
-
throw new Error('Message cannot be less than 5 characters');
|
|
2016
|
-
}
|
|
2017
|
-
if (msg.length > 150) {
|
|
2018
|
-
throw new Error('Message cannot exceed 100 characters');
|
|
2019
|
-
}
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
const hash = await routingSnapshot.takeSnapshot(msg, dryRun);
|
|
2023
|
-
if (!dryRun) {
|
|
2024
|
-
let nodeInfo = await nodeState.read();
|
|
2025
|
-
nodeInfo = await nodeState.updateNodeRouting({ ...nodeInfo.routing, snapshotHash: hash });
|
|
2026
|
-
if (handle) {
|
|
2027
|
-
await handleRouting(nodeInfo);
|
|
2028
|
-
}
|
|
2029
|
-
logger.info('takeRoutingSnapshot', {
|
|
2030
|
-
dryRun,
|
|
2031
|
-
handleRouting: handle,
|
|
2032
|
-
hash,
|
|
2033
|
-
dbSnapshotHash: nodeInfo?.routing?.snapshotHash,
|
|
2034
|
-
});
|
|
2035
|
-
}
|
|
2036
|
-
|
|
2037
|
-
return hash;
|
|
2038
|
-
};
|
|
2039
|
-
|
|
2040
|
-
const getSitesFromSnapshot = async ({ snapshotHash } = {}) => {
|
|
2041
|
-
const latestSnapshot = await routingSnapshot.getLastSnapshot(true);
|
|
2042
|
-
if (!snapshotHash || snapshotHash === latestSnapshot) {
|
|
2043
|
-
const sites = await siteState.getSites();
|
|
2044
|
-
return ensureLatestInfo(sites);
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
const isValidSnapshot = await routingSnapshot.hasSnapshot(snapshotHash);
|
|
2048
|
-
if (!isValidSnapshot) {
|
|
2049
|
-
const sites = await siteState.getSites();
|
|
2050
|
-
return ensureLatestInfo(sites);
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2053
|
-
const { sites = [] } = await routingSnapshot.readSnapshot(snapshotHash);
|
|
2054
|
-
|
|
2055
|
-
return ensureLatestInfo(sites);
|
|
2137
|
+
const getSitesFromState = async (scope = 'all') => {
|
|
2138
|
+
const sites = scope === 'all' ? await siteState.getSites() : await siteState.getSystemSites();
|
|
2139
|
+
const blocklets = scope === 'all' ? await states.blocklet.getBlocklets() : [];
|
|
2140
|
+
return ensureLatestInfo(sites, blocklets, teamManager);
|
|
2056
2141
|
};
|
|
2057
2142
|
|
|
2058
2143
|
/**
|
|
@@ -2061,30 +2146,22 @@ module.exports = function getRouterHelpers({
|
|
|
2061
2146
|
* @param {*} context
|
|
2062
2147
|
* @param {string} withInterfaceUrls
|
|
2063
2148
|
*/
|
|
2064
|
-
const getRoutingSites = async (params, context = {}, { withInterfaceUrls = true,
|
|
2065
|
-
const sites = await
|
|
2149
|
+
const getRoutingSites = async (params, context = {}, { withInterfaceUrls = true, scope = 'all' } = {}) => {
|
|
2150
|
+
const sites = await getSitesFromState(scope);
|
|
2066
2151
|
|
|
2067
2152
|
if (!withInterfaceUrls) {
|
|
2068
2153
|
return sites;
|
|
2069
2154
|
}
|
|
2070
2155
|
|
|
2071
2156
|
return attachRuntimeDomainAliases({
|
|
2072
|
-
sites
|
|
2073
|
-
context,
|
|
2074
|
-
});
|
|
2075
|
-
};
|
|
2076
|
-
|
|
2077
|
-
const getSnapshotSites = async ({ hash }, context = {}, { withDefaultCors = true } = {}) => {
|
|
2078
|
-
const sites = await routingSnapshot.readSnapshotSites(hash);
|
|
2079
|
-
return attachRuntimeDomainAliases({
|
|
2080
|
-
sites: await ensureLatestInfo(sites, { withDefaultCors }),
|
|
2157
|
+
sites,
|
|
2081
2158
|
context,
|
|
2082
2159
|
});
|
|
2083
2160
|
};
|
|
2084
2161
|
|
|
2085
2162
|
const getCertificates = async () => {
|
|
2086
2163
|
const certificates = await certManager.getAll();
|
|
2087
|
-
const sites = await
|
|
2164
|
+
const sites = await attachRuntimeDomainAliases({ sites: await siteState.getSites(), context: {} });
|
|
2088
2165
|
|
|
2089
2166
|
const isMatch = (cert, domain) =>
|
|
2090
2167
|
domain !== DOMAIN_FOR_DEFAULT_SITE && domain && routerManager.isCertMatchedDomain(cert, domain);
|
|
@@ -2105,18 +2182,14 @@ module.exports = function getRouterHelpers({
|
|
|
2105
2182
|
};
|
|
2106
2183
|
|
|
2107
2184
|
/**
|
|
2108
|
-
* proxy to routerManager and
|
|
2185
|
+
* proxy to routerManager and trigger handleBlockletRouting after operation
|
|
2109
2186
|
*/
|
|
2110
2187
|
const _proxyToRouterManager =
|
|
2111
2188
|
(fnName) =>
|
|
2112
2189
|
async (...args) => {
|
|
2113
2190
|
const res = await routerManager[fnName](...args);
|
|
2114
|
-
|
|
2115
|
-
takeRoutingSnapshot({ message: fnName, dryRun: false }).catch((error) => {
|
|
2116
|
-
logger.error('failed to takeRoutingSnapshot', { error });
|
|
2117
|
-
});
|
|
2118
|
-
|
|
2119
2191
|
const { teamDid: did } = args[0] || {};
|
|
2192
|
+
await handleBlockletRouting({ did, message: fnName });
|
|
2120
2193
|
if (did) {
|
|
2121
2194
|
routerManager.emit(BlockletEvents.updated, { meta: { did } });
|
|
2122
2195
|
}
|
|
@@ -2182,13 +2255,12 @@ module.exports = function getRouterHelpers({
|
|
|
2182
2255
|
ensureBlockletRouting,
|
|
2183
2256
|
ensureBlockletRoutingForUpgrade,
|
|
2184
2257
|
removeBlockletRouting,
|
|
2185
|
-
|
|
2258
|
+
handleSystemRouting, // O(1) - for global config changes only
|
|
2259
|
+
handleBlockletRouting, // O(2) - for single blocklet + global (services)
|
|
2260
|
+
handleAllRouting, // O(N) - for full regeneration
|
|
2186
2261
|
resetSiteByDid,
|
|
2187
|
-
updateNodeRouting,
|
|
2188
|
-
takeRoutingSnapshot,
|
|
2189
2262
|
getRoutingSites,
|
|
2190
|
-
|
|
2191
|
-
getSitesFromSnapshot,
|
|
2263
|
+
getSitesFromState,
|
|
2192
2264
|
getCertificates,
|
|
2193
2265
|
ensureWildcardCerts,
|
|
2194
2266
|
ensureServerlessCerts,
|
|
@@ -2267,6 +2339,7 @@ module.exports.ensureLatestInfo = ensureLatestInfo;
|
|
|
2267
2339
|
module.exports.ensureWellknownRule = ensureWellknownRule;
|
|
2268
2340
|
module.exports.ensureBlockletWellknownRules = ensureBlockletWellknownRules;
|
|
2269
2341
|
module.exports.ensureBlockletProxyBehavior = ensureBlockletProxyBehavior;
|
|
2342
|
+
module.exports.ensureBlockletStaticServing = ensureBlockletStaticServing;
|
|
2270
2343
|
module.exports.expandComponentRules = expandComponentRules;
|
|
2271
2344
|
module.exports.getDomainsByDid = getDomainsByDid;
|
|
2272
2345
|
module.exports.ensureBlockletHasMultipleInterfaces = ensureBlockletHasMultipleInterfaces;
|