@abtnode/core 1.15.17 → 1.16.0-beta-8ee536d7

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.
Files changed (119) hide show
  1. package/lib/api/node.js +67 -69
  2. package/lib/api/team.js +386 -55
  3. package/lib/blocklet/downloader/blocklet-downloader.js +226 -0
  4. package/lib/blocklet/downloader/bundle-downloader.js +272 -0
  5. package/lib/blocklet/downloader/constants.js +3 -0
  6. package/lib/blocklet/downloader/resolve-download.js +199 -0
  7. package/lib/blocklet/extras.js +83 -26
  8. package/lib/blocklet/hooks.js +18 -65
  9. package/lib/blocklet/manager/base.js +10 -16
  10. package/lib/blocklet/manager/disk.js +1680 -1566
  11. package/lib/blocklet/manager/helper/install-application-from-backup.js +177 -0
  12. package/lib/blocklet/manager/helper/install-application-from-dev.js +94 -0
  13. package/lib/blocklet/manager/helper/install-application-from-general.js +188 -0
  14. package/lib/blocklet/manager/helper/install-component-from-dev.js +84 -0
  15. package/lib/blocklet/manager/helper/install-component-from-upload.js +181 -0
  16. package/lib/blocklet/manager/helper/install-component-from-url.js +173 -0
  17. package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +450 -0
  18. package/lib/blocklet/manager/helper/rollback-cache.js +41 -0
  19. package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
  20. package/lib/blocklet/migration.js +30 -52
  21. package/lib/blocklet/storage/backup/audit-log.js +27 -0
  22. package/lib/blocklet/storage/backup/base.js +62 -0
  23. package/lib/blocklet/storage/backup/blocklet-extras.js +92 -0
  24. package/lib/blocklet/storage/backup/blocklet.js +70 -0
  25. package/lib/blocklet/storage/backup/blocklets.js +74 -0
  26. package/lib/blocklet/storage/backup/data.js +19 -0
  27. package/lib/blocklet/storage/backup/logs.js +24 -0
  28. package/lib/blocklet/storage/backup/routing-rule.js +19 -0
  29. package/lib/blocklet/storage/backup/spaces.js +240 -0
  30. package/lib/blocklet/storage/restore/base.js +67 -0
  31. package/lib/blocklet/storage/restore/blocklet-extras.js +86 -0
  32. package/lib/blocklet/storage/restore/blocklet.js +56 -0
  33. package/lib/blocklet/storage/restore/blocklets.js +43 -0
  34. package/lib/blocklet/storage/restore/logs.js +21 -0
  35. package/lib/blocklet/storage/restore/spaces.js +156 -0
  36. package/lib/blocklet/storage/utils/hash.js +51 -0
  37. package/lib/blocklet/storage/utils/zip.js +43 -0
  38. package/lib/cert.js +206 -0
  39. package/lib/event.js +237 -64
  40. package/lib/index.js +191 -83
  41. package/lib/migrations/1.0.21-update-config.js +1 -1
  42. package/lib/migrations/1.0.22-max-memory.js +1 -1
  43. package/lib/migrations/1.0.25.js +1 -1
  44. package/lib/migrations/1.0.32-update-config.js +1 -1
  45. package/lib/migrations/1.0.33-blocklets.js +1 -1
  46. package/lib/migrations/1.5.20-registry.js +15 -0
  47. package/lib/migrations/1.6.17-blocklet-children.js +48 -0
  48. package/lib/migrations/1.6.21-rename-ip-echo-domain.js +35 -0
  49. package/lib/migrations/1.6.4-security.js +59 -0
  50. package/lib/migrations/1.6.5-security.js +60 -0
  51. package/lib/migrations/1.6.9-update-node-info-and-certificate.js +38 -0
  52. package/lib/migrations/1.7.1-blocklet-setup.js +18 -0
  53. package/lib/migrations/1.7.12-blocklet-meta.js +51 -0
  54. package/lib/migrations/1.7.15-blocklet-bundle-source.js +42 -0
  55. package/lib/migrations/1.7.20-blocklet-component.js +41 -0
  56. package/lib/migrations/1.8.33-blocklet-mem-limit.js +20 -0
  57. package/lib/migrations/README.md +1 -1
  58. package/lib/migrations/index.js +6 -2
  59. package/lib/monitor/blocklet-runtime-monitor.js +200 -0
  60. package/lib/monitor/get-history-list.js +37 -0
  61. package/lib/monitor/node-runtime-monitor.js +228 -0
  62. package/lib/router/helper.js +576 -500
  63. package/lib/router/index.js +85 -21
  64. package/lib/router/manager.js +146 -187
  65. package/lib/states/README.md +36 -1
  66. package/lib/states/access-key.js +39 -17
  67. package/lib/states/audit-log.js +462 -0
  68. package/lib/states/base.js +4 -213
  69. package/lib/states/blocklet-extras.js +195 -138
  70. package/lib/states/blocklet.js +371 -110
  71. package/lib/states/cache.js +8 -6
  72. package/lib/states/challenge.js +5 -5
  73. package/lib/states/index.js +19 -36
  74. package/lib/states/migration.js +4 -4
  75. package/lib/states/node.js +135 -46
  76. package/lib/states/notification.js +22 -35
  77. package/lib/states/session.js +17 -9
  78. package/lib/states/site.js +50 -25
  79. package/lib/states/user.js +74 -20
  80. package/lib/states/webhook.js +10 -6
  81. package/lib/team/manager.js +124 -7
  82. package/lib/util/blocklet.js +1223 -246
  83. package/lib/util/chain.js +1 -1
  84. package/lib/util/default-node-config.js +5 -23
  85. package/lib/util/disk-monitor.js +13 -10
  86. package/lib/util/domain-status.js +84 -15
  87. package/lib/util/get-accessible-external-node-ip.js +2 -2
  88. package/lib/util/get-domain-for-blocklet.js +13 -0
  89. package/lib/util/get-meta-from-url.js +33 -0
  90. package/lib/util/index.js +207 -272
  91. package/lib/util/ip.js +6 -0
  92. package/lib/util/maintain.js +233 -0
  93. package/lib/util/public-to-store.js +85 -0
  94. package/lib/util/ready.js +1 -1
  95. package/lib/util/requirement.js +28 -9
  96. package/lib/util/reset-node.js +22 -7
  97. package/lib/util/router.js +13 -0
  98. package/lib/util/rpc.js +16 -0
  99. package/lib/util/store.js +179 -0
  100. package/lib/util/sysinfo.js +44 -0
  101. package/lib/util/ua.js +54 -0
  102. package/lib/validators/blocklet-extra.js +24 -0
  103. package/lib/validators/node.js +25 -12
  104. package/lib/validators/permission.js +16 -1
  105. package/lib/validators/role.js +17 -3
  106. package/lib/validators/router.js +40 -20
  107. package/lib/validators/trusted-passport.js +1 -0
  108. package/lib/validators/util.js +22 -5
  109. package/lib/webhook/index.js +45 -35
  110. package/lib/webhook/sender/index.js +5 -0
  111. package/lib/webhook/sender/slack/index.js +1 -1
  112. package/lib/webhook/sender/wallet/index.js +48 -0
  113. package/package.json +54 -36
  114. package/lib/blocklet/registry.js +0 -205
  115. package/lib/states/https-cert.js +0 -67
  116. package/lib/util/get-ip-dns-domain-for-blocklet.js +0 -19
  117. package/lib/util/service.js +0 -66
  118. package/lib/util/upgrade.js +0 -178
  119. /package/lib/{queue.js → util/queue.js} +0 -0
@@ -3,63 +3,78 @@
3
3
  const fs = require('fs-extra');
4
4
  const path = require('path');
5
5
  const tar = require('tar');
6
+ const isUrl = require('is-url');
6
7
  const get = require('lodash/get');
7
8
  const cloneDeep = require('lodash/cloneDeep');
9
+ const isEqual = require('lodash/isEqual');
8
10
  const joinUrl = require('url-join');
11
+ const { replaceSlotToIp, findComponentById, findWebInterface } = require('@blocklet/meta/lib/util');
9
12
  const { getProvider } = require('@abtnode/router-provider');
10
13
  const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
11
- const getTmpDirectory = require('@abtnode/util/lib/get-tmp-directory');
14
+ const getTmpDir = require('@abtnode/util/lib/get-tmp-directory');
12
15
  const downloadFile = require('@abtnode/util/lib/download-file');
16
+ const { updateBlockletDocument } = require('@abtnode/util/lib/did-document');
17
+ const getBlockletInfo = require('@blocklet/meta/lib/info');
18
+ const { forEachBlocklet } = require('@blocklet/meta/lib/util');
13
19
  const {
14
20
  DOMAIN_FOR_DEFAULT_SITE,
15
21
  DOMAIN_FOR_IP_SITE_REGEXP,
16
- ROUTER_PROVIDER_NONE,
17
22
  DOMAIN_FOR_INTERNAL_SITE,
18
23
  WELLKNOWN_PATH_PREFIX,
19
- WELLKNOWN_AUTH_PATH_PREFIX,
24
+ WELLKNOWN_SERVICE_PATH_PREFIX,
25
+ USER_AVATAR_PATH_PREFIX,
20
26
  DOMAIN_FOR_IP_SITE,
21
27
  NAME_FOR_WELLKNOWN_SITE,
22
28
  DEFAULT_HTTP_PORT,
23
29
  DEFAULT_HTTPS_PORT,
24
- DAY_IN_MS,
25
- NODE_MODES,
26
30
  ROUTING_RULE_TYPES,
27
31
  CERTIFICATE_EXPIRES_OFFSET,
28
- CERTIFICATE_EXPIRES_WARNING_OFFSET,
29
- DEFAULT_DAEMON_PORT,
30
32
  DEFAULT_SERVICE_PATH,
31
33
  SLOT_FOR_IP_DNS_SITE,
32
34
  BLOCKLET_SITE_GROUP_SUFFIX,
35
+ WELLKNOWN_ACME_CHALLENGE_PREFIX,
36
+ WELLKNOWN_DID_RESOLVER_PREFIX,
37
+ WELLKNOWN_PING_PREFIX,
38
+ LOG_RETAIN_IN_DAYS,
39
+ EVENTS,
33
40
  } = require('@abtnode/constant');
34
41
  const {
35
42
  BLOCKLET_DYNAMIC_PATH_PREFIX,
36
43
  BLOCKLET_INTERFACE_TYPE_WEB,
44
+ BLOCKLET_INTERFACE_PUBLIC,
37
45
  BLOCKLET_INTERFACE_WELLKNOWN,
38
46
  BLOCKLET_INTERFACE_TYPE_WELLKNOWN,
39
- } = require('@blocklet/meta/lib/constants');
47
+ BLOCKLET_CONFIGURABLE_KEY,
48
+ BlockletEvents,
49
+ BLOCKLET_MODES,
50
+ } = require('@blocklet/constant');
40
51
 
41
52
  // eslint-disable-next-line global-require
42
53
  const logger = require('@abtnode/logger')(`${require('../../package.json').name}:router:helper`);
43
54
  const {
44
55
  getProviderFromNodeInfo,
45
- trimSlash,
46
- getInterfaceUrl,
47
- getBlockletHost,
48
56
  getHttpsCertInfo,
49
57
  findInterfacePortByName,
50
58
  getWellknownSitePort,
59
+ getServerDidDomain,
60
+ isGatewayCacheEnabled,
51
61
  } = require('../util');
52
- const { getServicesFromBlockletInterface } = require('../util/service');
53
- const getIpDnsDomainForBlocklet = require('../util/get-ip-dns-domain-for-blocklet');
62
+ const { getIpDnsDomainForBlocklet, getDidDomainForBlocklet } = require('../util/get-domain-for-blocklet');
54
63
  const { getFromCache: getAccessibleExternalNodeIp } = require('../util/get-accessible-external-node-ip');
55
64
 
56
65
  const Router = require('./index');
57
66
  const states = require('../states');
67
+ const { getBlockletDomainGroupName, getDidFromDomainGroupName } = require('../util/router');
68
+ const { getBlockletKnownAs } = require('../util/blocklet');
58
69
 
59
70
  /**
60
71
  * replace 888-888-888-888 with accessible ip for domain
61
72
  */
62
73
  const attachRuntimeDomainAliases = async ({ sites = [], context = {}, node }) => {
74
+ if (!sites) {
75
+ return [];
76
+ }
77
+
63
78
  let ip;
64
79
  const ipRegex = /\d+[-.]\d+[-.]\d+[-.]\d+/;
65
80
  const match = ipRegex.exec(context.hostname);
@@ -79,7 +94,7 @@ const attachRuntimeDomainAliases = async ({ sites = [], context = {}, node }) =>
79
94
  return domain;
80
95
  }
81
96
  if (domain.value.includes(SLOT_FOR_IP_DNS_SITE) && ip) {
82
- domain.value = domain.value.replace(SLOT_FOR_IP_DNS_SITE, ip.replace(/\./g, '-'));
97
+ domain.value = replaceSlotToIp(domain.value, ip);
83
98
  }
84
99
  return domain;
85
100
  });
@@ -96,89 +111,6 @@ const attachRuntimeDomainAliases = async ({ sites = [], context = {}, node }) =>
96
111
  });
97
112
  };
98
113
 
99
- const attachInterfaceUrls = async ({ sites = [], context, node }) => {
100
- if (!sites) {
101
- return [];
102
- }
103
-
104
- attachRuntimeDomainAliases({ sites, context, node });
105
-
106
- const getUrl = (rule, domain = '') => {
107
- const host = getBlockletHost({ domain, context });
108
- const prefix = trimSlash(rule.from.pathPrefix);
109
- if (prefix) {
110
- return `http://${host}/${prefix}/`;
111
- }
112
-
113
- return `http://${host}/`;
114
- };
115
-
116
- const blocklets = await states.blocklet.getBlocklets();
117
- const getInterfaces = (site) =>
118
- (site.rules || []).map((tmpRule) => {
119
- const rule = { ...tmpRule };
120
-
121
- const ruleId = tmpRule.id;
122
- const baseUrl = getUrl(rule, site.domain);
123
-
124
- const interfaces = [];
125
- if (rule.to.type === ROUTING_RULE_TYPES.BLOCKLET) {
126
- const blocklet = blocklets.find((b) => b.meta.did === rule.to.did);
127
-
128
- if (!blocklet) {
129
- logger.warn(`can not attach interface urls for non-existing blocklet ${rule.to.did}`);
130
- rule.interfaces = interfaces;
131
- return rule;
132
- }
133
-
134
- if (!getBlockletHost({ domain: site.domain, context })) {
135
- logger.warn('blocklet host does not exist');
136
- rule.interfaces = interfaces;
137
- return rule;
138
- }
139
-
140
- (blocklet.meta.interfaces || []).forEach((x) => {
141
- if (x.port && !x.port.external) {
142
- interfaces.push({
143
- ruleId,
144
- type: x.type,
145
- name: x.name,
146
- url: getInterfaceUrl({ baseUrl, url: '/', version: blocklet.meta.version }),
147
- });
148
- }
149
- });
150
- } else if (rule.to.type === ROUTING_RULE_TYPES.REDIRECT || rule.to.type === ROUTING_RULE_TYPES.NONE) {
151
- interfaces.push({
152
- ruleId,
153
- type: 'web',
154
- name: ROUTING_RULE_TYPES.REDIRECT,
155
- url: baseUrl,
156
- });
157
- } else {
158
- interfaces.push({
159
- ruleId,
160
- type: 'web',
161
- name: 'default',
162
- url: getUrl(rule),
163
- });
164
- }
165
-
166
- rule.interfaces = interfaces;
167
- return rule;
168
- });
169
-
170
- if (!Array.isArray(sites)) {
171
- sites.rules = getInterfaces(sites);
172
- return sites;
173
- }
174
-
175
- return sites.map((site) => {
176
- site.rules = getInterfaces(site);
177
-
178
- return site;
179
- });
180
- };
181
-
182
114
  const addCorsToSite = (site, rawUrl) => {
183
115
  if (!site || !rawUrl) {
184
116
  return;
@@ -193,7 +125,7 @@ const addCorsToSite = (site, rawUrl) => {
193
125
  site.corsAllowedOrigins.push(url.hostname);
194
126
  }
195
127
  } catch (err) {
196
- // Do nothing
128
+ console.error('addCorsToSite', err);
197
129
  }
198
130
  };
199
131
 
@@ -222,6 +154,19 @@ const ensureServiceRule = async (sites) => {
222
154
  return site;
223
155
  });
224
156
  };
157
+ const ensureRootRule = async (sites) => {
158
+ return sites.map((site) => {
159
+ if (!isBasicSite(site.domain) && !site.rules.some((x) => x.from.pathPrefix === '/')) {
160
+ site.rules.push({
161
+ from: { pathPrefix: '/' },
162
+ to: {
163
+ type: ROUTING_RULE_TYPES.NONE,
164
+ },
165
+ });
166
+ }
167
+ return site;
168
+ });
169
+ };
225
170
 
226
171
  const ensureLatestNodeInfo = async (sites = [], { withDefaultCors = true } = {}) => {
227
172
  const info = await states.node.read();
@@ -243,6 +188,9 @@ const ensureLatestNodeInfo = async (sites = [], { withDefaultCors = true } = {})
243
188
  site.domain = DOMAIN_FOR_IP_SITE_REGEXP;
244
189
 
245
190
  if (withDefaultCors) {
191
+ // Allow CORS from "Install on ABT Node"
192
+ addCorsToSite(site, info.registerUrl);
193
+
246
194
  // Allow CORS from "Web Wallet"
247
195
  addCorsToSite(site, info.webWalletUrl);
248
196
  }
@@ -256,6 +204,7 @@ const ensureLatestNodeInfo = async (sites = [], { withDefaultCors = true } = {})
256
204
  * set rule.to.target by interface.path and interface.prefix
257
205
  */
258
206
  const ensureLatestInterfaceInfo = async (sites = []) => {
207
+ // FIXME @linchen groupAllInterfaces() may have performance issue
259
208
  const interfaces = await states.blocklet.groupAllInterfaces();
260
209
  return sites.map((site) => {
261
210
  if (!Array.isArray(site.rules)) {
@@ -263,16 +212,22 @@ const ensureLatestInterfaceInfo = async (sites = []) => {
263
212
  }
264
213
 
265
214
  site.rules = site.rules.map((rule) => {
266
- // If a rule already has a target and target is WELLKNOWN_AUTH_PATH_PREFIX, just return
215
+ if (rule.dynamic) {
216
+ return rule;
217
+ }
218
+ // If a rule already has a target and target is WELLKNOWN_SERVICE_PATH_PREFIX, just return
267
219
  // Indicates that the rule id generated by the system for auth service
268
- if (rule.isProtected && rule.to.target === WELLKNOWN_AUTH_PATH_PREFIX) {
220
+ if (rule.isProtected && rule.to.target === WELLKNOWN_SERVICE_PATH_PREFIX) {
221
+ return rule;
222
+ }
223
+ if (rule.isProtected && rule.to.target === joinUrl(WELLKNOWN_SERVICE_PATH_PREFIX, USER_AVATAR_PATH_PREFIX)) {
269
224
  return rule;
270
225
  }
271
226
 
272
- const { did, interfaceName } = rule.to;
273
- if (interfaces[did] && interfaces[did][interfaceName]) {
227
+ const { componentId, interfaceName } = rule.to;
228
+ if (interfaces[componentId] && interfaces[componentId][interfaceName]) {
274
229
  // eslint-disable-next-line no-shadow
275
- const { path, prefix } = interfaces[did][interfaceName];
230
+ const { path, prefix } = interfaces[componentId][interfaceName];
276
231
  if (prefix === BLOCKLET_DYNAMIC_PATH_PREFIX) {
277
232
  rule.to.target = path;
278
233
  } else {
@@ -294,8 +249,8 @@ const ensureWellknownRule = async (sites) => {
294
249
  for (const site of tempSites) {
295
250
  // 不向 default site & ip site & wellknown site 添加 wellknown rule
296
251
  if (![DOMAIN_FOR_INTERNAL_SITE, DOMAIN_FOR_IP_SITE, DOMAIN_FOR_DEFAULT_SITE].includes(site.domain)) {
252
+ // add /.well-known for blocklet
297
253
  const isExists = site.rules.find((x) => x.from.pathPrefix === WELLKNOWN_PATH_PREFIX);
298
-
299
254
  if (!isExists) {
300
255
  site.rules.push({
301
256
  from: { pathPrefix: WELLKNOWN_PATH_PREFIX },
@@ -308,40 +263,72 @@ const ensureWellknownRule = async (sites) => {
308
263
  });
309
264
  }
310
265
 
311
- // add /.well-known/auth for blocklet
312
- const rules = cloneDeep(site.rules);
313
- rules.forEach((rule) => {
314
- if (
315
- rule.to.type !== ROUTING_RULE_TYPES.BLOCKLET || // is not blocklet
316
- rule.to.did !== rule.to.realDid // is a component endpoint
317
- ) {
318
- return;
319
- }
266
+ // add /.well-known/service for blocklet
267
+ const blockletRules = site.rules
268
+ .filter((x) => x.to.type === ROUTING_RULE_TYPES.BLOCKLET)
269
+ // 可能存在挂载的 blocklet 不是自己, 是其他 blocklet
270
+ // 这里默认 pathPrefix 最短的是自己 ( 通常自己在 /, 其他 blocklet 在 /xxxx )
271
+ // 挂载其他 blocklet 的使用方式不推荐, 将来可能会被废弃
272
+ .sort((a, b) => (a.from.pathPrefix.length > b.from.pathPrefix.length ? 1 : -1));
273
+ if (blockletRules.length) {
274
+ // get pathPrefix for blocklet-service
275
+ const rootBlockletRule = blockletRules.find((x) => x.to.did === x.to.componentId);
276
+
277
+ const servicePathPrefix = joinUrl(
278
+ // rootBlockletRule?.from?.pathPrefix is for backwards compatibility
279
+ // rootBlockletRule?.from?.groupPathPrefix 在旧的场景中可能不为 '/' (blocklet 只能挂载在 relative prefix 下)
280
+ rootBlockletRule?.from?.groupPathPrefix || rootBlockletRule?.from?.pathPrefix || '/',
281
+ WELLKNOWN_SERVICE_PATH_PREFIX
282
+ );
320
283
 
321
- // already exists
322
- if (rule.from.pathPrefix.endsWith(WELLKNOWN_AUTH_PATH_PREFIX)) {
323
- return;
284
+ // requests for /.well-known/service will stay in blocklet-service and never proxy back to blocklet
285
+ // so any rule is ok to be cloned
286
+ if (!site.rules.some((x) => x.from.pathPrefix === servicePathPrefix)) {
287
+ const rule = cloneDeep(rootBlockletRule || blockletRules[0]);
288
+ rule.from.pathPrefix = servicePathPrefix;
289
+ rule.to.target = servicePathPrefix;
290
+ rule.isProtected = true;
291
+ rule.dynamic = true; // mark as dynamic to avoid redundant generated rules
292
+ site.rules.push(rule);
324
293
  }
325
294
 
326
- const pathPrefix = joinUrl(rule.from.pathPrefix, WELLKNOWN_AUTH_PATH_PREFIX);
327
-
328
- // already exists
329
- if (site.rules.some((x) => x.from.pathPrefix === pathPrefix)) {
330
- return;
295
+ // Cache user avatar in nginx
296
+ const avatarPathPrefix = joinUrl(servicePathPrefix, USER_AVATAR_PATH_PREFIX);
297
+ if (!site.rules.some((x) => x.from.pathPrefix === avatarPathPrefix)) {
298
+ const rule = cloneDeep(rootBlockletRule || blockletRules[0]);
299
+ rule.from.pathPrefix = avatarPathPrefix;
300
+ rule.to.cacheGroup = 'blockletProxy';
301
+ rule.to.target = avatarPathPrefix;
302
+ rule.isProtected = true;
303
+ rule.dynamic = true; // mark as dynamic to avoid redundant generated rules
304
+ site.rules.push(rule);
331
305
  }
332
-
333
- rule.from.pathPrefix = pathPrefix;
334
- // let service gateway keep WELLKNOWN_AUTH_PATH_PREFIX prefix
335
- rule.to.target = WELLKNOWN_AUTH_PATH_PREFIX;
336
- rule.isProtected = true;
337
- site.rules.push(rule);
338
- });
306
+ }
339
307
  }
340
308
  }
341
309
 
342
310
  return tempSites;
343
311
  };
344
312
 
313
+ const ensureBlockletDid = async (sites) => {
314
+ const info = await states.node.read();
315
+
316
+ return (sites || []).map((site) => {
317
+ if (site.domain === DOMAIN_FOR_INTERNAL_SITE) {
318
+ return site;
319
+ }
320
+
321
+ if ([DOMAIN_FOR_IP_SITE, DOMAIN_FOR_DEFAULT_SITE, DOMAIN_FOR_IP_SITE_REGEXP].includes(site.domain)) {
322
+ site.blockletDid = info.did;
323
+ return site;
324
+ }
325
+
326
+ site.blockletDid = getDidFromDomainGroupName(site.domain);
327
+
328
+ return site;
329
+ });
330
+ };
331
+
345
332
  const ensureCorsForWebWallet = async (sites) => {
346
333
  const info = await states.node.read();
347
334
  for (const site of sites) {
@@ -353,48 +340,97 @@ const ensureCorsForWebWallet = async (sites) => {
353
340
  return sites;
354
341
  };
355
342
 
356
- const ensureLatestInfo = async (sites = [], { withDefaultCors = true } = {}) => {
357
- let result = await ensureLatestNodeInfo(sites, { withDefaultCors });
358
- result = await ensureWellknownRule(result);
359
- result = await ensureCorsForWebWallet(result);
360
- return ensureLatestInterfaceInfo(result);
343
+ const ensureCorsForDidSpace = async (sites = [], blocklets) => {
344
+ return sites.map((site) => {
345
+ const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
346
+ if (blocklet) {
347
+ const endpoint = blocklet.environments.find(
348
+ (x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT
349
+ );
350
+ if (endpoint && isUrl(endpoint.value)) {
351
+ addCorsToSite(site, endpoint.value);
352
+ }
353
+ }
354
+
355
+ return site;
356
+ });
361
357
  };
362
358
 
363
- const ensureAuthService = async (sites = [], blockletManager) => {
364
- const blocklets = await blockletManager.list({ includeRuntimeInfo: false });
365
- const blockletMap = blocklets.reduce((o, x) => {
366
- o[x.meta.did] = x;
367
- return o;
368
- }, {});
359
+ const filterSitesForRemovedBlocklets = async (sites = [], blocklets) => {
360
+ return sites.filter((site) => {
361
+ if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
362
+ return true;
363
+ }
369
364
 
370
- sites.forEach((site) => {
371
- site.rules.forEach((rule) => {
372
- if (rule.to.type !== ROUTING_RULE_TYPES.BLOCKLET || !blockletMap[rule.to.did]) {
373
- return;
374
- }
365
+ const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
366
+ return !!blocklet;
367
+ });
368
+ };
375
369
 
376
- // find blocklet
377
- let blocklet = blockletMap[rule.to.did];
378
- if (rule.to.realDid && rule.to.did !== rule.to.realDid) {
379
- blocklet = (blocklet.children || []).find((x) => x.meta.did === rule.to.realDid);
380
- }
381
- if (!blocklet) {
382
- return;
370
+ const ensureBlockletCache = async (sites = [], blocklets) => {
371
+ return sites
372
+ .map((site) => {
373
+ if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
374
+ return site;
383
375
  }
384
376
 
385
- // find interface
386
- // we should only use rule.to.realInterfaceName, rule.to.interfaceName is for backward compatible
387
- const interfaceName = rule.to.realInterfaceName || rule.to.interfaceName;
388
- const { interfaces } = blocklet.meta;
389
- const _interface = interfaces.find((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB && x.name === interfaceName);
390
- if (_interface) {
391
- rule.services = rule.services || [];
392
- rule.services.unshift(...getServicesFromBlockletInterface(_interface, { logError: logger.error }));
377
+ if (site.cacheableGenerated) {
378
+ return site;
393
379
  }
394
- });
395
- });
396
380
 
397
- return sites;
381
+ // For each rule, get component, check cacheable, clone and push new rule
382
+ const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
383
+ const cacheRules = [];
384
+ site.rules
385
+ .filter(
386
+ (x) =>
387
+ x.to.type === ROUTING_RULE_TYPES.BLOCKLET &&
388
+ x.to.interfaceName === BLOCKLET_INTERFACE_PUBLIC &&
389
+ x.from.pathPrefix.startsWith(WELLKNOWN_SERVICE_PATH_PREFIX) === false
390
+ )
391
+ .forEach((rule) => {
392
+ const component = findComponentById(blocklet, rule.to.componentId);
393
+ if (!component) {
394
+ return;
395
+ }
396
+
397
+ if (component.mode !== BLOCKLET_MODES.PRODUCTION) {
398
+ return;
399
+ }
400
+ const cacheable = get(findWebInterface(component), 'cacheable', []);
401
+ cacheable.forEach((cachePrefix) => {
402
+ const clone = cloneDeep(rule);
403
+ clone.from.pathPrefix = joinUrl(rule.from.pathPrefix, cachePrefix);
404
+ clone.to.cacheGroup = 'blockletProxy';
405
+ clone.to.targetPrefix = cachePrefix;
406
+ clone.dynamic = true; // mark as dynamic to avoid redundant generated rules
407
+ cacheRules.push(clone);
408
+ });
409
+ });
410
+
411
+ site.rules = site.rules.concat(cacheRules);
412
+ site.mode = blocklet.mode;
413
+ site.cacheableGenerated = true;
414
+
415
+ return site;
416
+ })
417
+ .filter(Boolean);
418
+ };
419
+
420
+ const ensureLatestInfo = async (sites = [], { withDefaultCors = true } = {}) => {
421
+ const blocklets = await states.blocklet.getBlocklets();
422
+
423
+ // CAUTION: following steps are very important, please do not change the order
424
+ let result = await ensureLatestNodeInfo(sites, { withDefaultCors });
425
+ result = await ensureBlockletDid(result);
426
+ result = await filterSitesForRemovedBlocklets(sites, blocklets);
427
+ result = await ensureBlockletCache(result, blocklets);
428
+ result = await ensureWellknownRule(result);
429
+ result = await ensureCorsForWebWallet(result);
430
+ result = await ensureCorsForDidSpace(result, blocklets);
431
+ result = await ensureLatestInterfaceInfo(result);
432
+
433
+ return result;
398
434
  };
399
435
 
400
436
  const decompressCertificates = async (source, dest) => {
@@ -403,55 +439,29 @@ const decompressCertificates = async (source, dest) => {
403
439
  return dest;
404
440
  };
405
441
 
406
- module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager }) {
442
+ const joinCertDownUrl = (baseUrl, name) => joinUrl(baseUrl, '/certs', name);
443
+
444
+ const getIpEchoCertDownloadUrl = (baseUrl) => joinCertDownUrl(baseUrl, 'ip-abtnet-io.tar.gz');
445
+ const getDidDomainCertDownloadUrl = (baseUrl) => joinCertDownUrl(baseUrl, 'did-abtnet-io.tar.gz');
446
+ const getDownloadCertBaseUrl = (info) =>
447
+ process.env.ABT_NODE_WILDCARD_CERT_HOST || get(info, 'routing.wildcardCertHost', '');
448
+
449
+ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager, certManager }) {
407
450
  const nodeState = states.node;
408
451
  const blockletState = states.blocklet;
409
452
  const siteState = states.site;
410
- const httpsCertState = states.certificate;
411
453
  const notification = states.notification;
412
454
 
413
455
  // site level duplication detection
414
456
  const hasRuleByPrefix = (site, value) => site.rules.find((x) => x.isProtected && get(x, 'from.pathPrefix') === value);
415
457
 
416
- const updateDashboardCertificate = async ({ checkExpire = true }) => {
417
- const info = await nodeState.read();
418
- const provider = getProviderFromNodeInfo(info);
419
- if (provider === ROUTER_PROVIDER_NONE) {
420
- return;
421
- }
422
-
423
- const https = get(info, 'routing.https', true);
424
- const dashboardDomain = get(info, 'routing.dashboardDomain', '');
425
- const certDownloadAddress = get(info, 'routing.dashboardCertDownloadAddress', '');
426
- if (!https || !dashboardDomain || !certDownloadAddress) {
427
- return;
428
- }
429
-
430
- if (checkExpire) {
431
- try {
432
- const cert = await routerManager.findCertificateByDomain(dashboardDomain);
433
- if (!cert) {
434
- return;
435
- }
436
-
437
- const now = Date.now();
438
- const certInfo = getHttpsCertInfo(cert.certificate);
439
- if (certInfo.validTo - now >= CERTIFICATE_EXPIRES_OFFSET) {
440
- logger.info('skip dashboard certificate update before not expired');
441
- return;
442
- }
443
- } catch (err) {
444
- logger.error('failed to check dashboard certificate expiration', { error: err });
445
- return;
446
- }
447
- }
448
-
449
- const destFolder = getTmpDirectory(path.join(`certificate-${Date.now()}`));
458
+ const downloadCert = async ({ domain, url }) => {
459
+ const destFolder = getTmpDir(path.join(`certificate-${Date.now()}`));
450
460
  try {
451
461
  const filename = path.join(destFolder, 'certificate.tar.gz');
452
462
  fs.ensureDirSync(destFolder);
453
463
 
454
- await downloadFile(certDownloadAddress, filename);
464
+ await downloadFile(url, filename);
455
465
  await decompressCertificates(filename, destFolder);
456
466
 
457
467
  const certificateFilePath = path.join(destFolder, 'cert.pem');
@@ -468,58 +478,186 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
468
478
  const certificate = fs.readFileSync(certificateFilePath).toString();
469
479
  const privateKey = fs.readFileSync(privateKeyFilePath).toString();
470
480
 
471
- await routerManager.updateNginxHttpsCert({ domain: dashboardDomain, privateKey, certificate, isProtected: true });
472
- logger.info('dashboard certificate updated');
481
+ await certManager.upsertByDomain({
482
+ domain,
483
+ privateKey,
484
+ certificate,
485
+ isProtected: true,
486
+ });
487
+
488
+ logger.info('dashboard certificate updated', { domain });
473
489
  } catch (error) {
474
- logger.error('Update dashboard certificate failed', { error });
490
+ logger.error('update dashboard certificate failed', { error });
491
+ throw error;
475
492
  } finally {
476
493
  fs.removeSync(destFolder);
477
494
  }
478
495
  };
479
496
 
497
+ const updateCert = async (domain, url) => {
498
+ try {
499
+ const cert = await certManager.getByDomain(domain);
500
+ if (!cert) {
501
+ return;
502
+ }
503
+
504
+ const now = Date.now();
505
+ const certInfo = getHttpsCertInfo(cert.certificate);
506
+
507
+ if (certInfo.validTo - now >= CERTIFICATE_EXPIRES_OFFSET) {
508
+ logger.info('skip dashboard certificate update before not expired', { domain, url });
509
+ return;
510
+ }
511
+
512
+ await downloadCert({
513
+ domain,
514
+ url,
515
+ });
516
+ } catch (err) {
517
+ logger.error('failed to check dashboard certificate expiration', { error: err, domain, url });
518
+ }
519
+ };
520
+
521
+ const updateDashboardCertificates = async () => {
522
+ const info = await nodeState.read();
523
+ const https = get(info, 'routing.https', true);
524
+ const ipWildcardDomain = get(info, 'routing.ipWildcardDomain', '');
525
+ const didDomain = info.didDomain;
526
+ const certDownloadAddress = getDownloadCertBaseUrl(info);
527
+ if (!https || !certDownloadAddress) {
528
+ return;
529
+ }
530
+
531
+ await updateCert(ipWildcardDomain, getIpEchoCertDownloadUrl(certDownloadAddress));
532
+ await updateCert(`*.${didDomain}`, getDidDomainCertDownloadUrl(certDownloadAddress));
533
+ };
534
+
535
+ const ensureWildcardCerts = async () => {
536
+ const info = await nodeState.read();
537
+ const didDomain = info.didDomain;
538
+ const ipWildcardDomain = get(info, 'routing.ipWildcardDomain', '');
539
+
540
+ const ensureDomainCert = async (domain, url) => {
541
+ const cert = await certManager.getByDomain(domain);
542
+ if (!cert || get(cert, 'meta.validTo') <= Date.now()) {
543
+ await downloadCert({
544
+ domain,
545
+ url,
546
+ });
547
+ }
548
+ };
549
+
550
+ const certBaseUrl = getDownloadCertBaseUrl(info);
551
+ await Promise.all([
552
+ ensureDomainCert(ipWildcardDomain, getIpEchoCertDownloadUrl(certBaseUrl)),
553
+ ensureDomainCert(`*.${didDomain}`, getDidDomainCertDownloadUrl(certBaseUrl)),
554
+ ]);
555
+ };
556
+
557
+ const upsertSiteRule = async ({ site, rule }, context) => {
558
+ const findExistingRule = (prefix) => site.rules.find((r) => r.from.pathPrefix === normalizePathPrefix(prefix));
559
+
560
+ const newSiteRule = {
561
+ id: site.id,
562
+ rule,
563
+ };
564
+
565
+ const existingRule = findExistingRule(get(rule, 'from.pathPrefix'));
566
+ if (!existingRule) {
567
+ await routerManager.addRoutingRule(newSiteRule, context);
568
+ return true;
569
+ }
570
+
571
+ if (!isEqual(existingRule.to, rule.to)) {
572
+ newSiteRule.rule.id = existingRule.id;
573
+ newSiteRule.skipProtectedRuleChecking = true;
574
+ await routerManager.updateRoutingRule(newSiteRule, context);
575
+ return true;
576
+ }
577
+
578
+ return false;
579
+ };
580
+
480
581
  const addWellknownSite = async (sites, context) => {
481
582
  const site = (sites || []).find((x) => x.name === NAME_FOR_WELLKNOWN_SITE);
482
583
 
483
584
  try {
484
- const pathPrefix = joinUrl(WELLKNOWN_PATH_PREFIX, '/did.json');
585
+ const info = await nodeState.read();
586
+ const proxyTarget = {
587
+ port: info.port,
588
+ type: ROUTING_RULE_TYPES.GENERAL_PROXY,
589
+ interfaceName: BLOCKLET_INTERFACE_WELLKNOWN,
590
+ };
591
+
485
592
  const didResolverWellknownRule = {
486
- from: { pathPrefix },
593
+ isProtected: true,
594
+ from: { pathPrefix: WELLKNOWN_DID_RESOLVER_PREFIX },
595
+ to: proxyTarget,
596
+ };
597
+
598
+ const acmeChallengeWellknownRule = {
599
+ isProtected: true,
600
+ from: { pathPrefix: WELLKNOWN_ACME_CHALLENGE_PREFIX },
601
+ to: proxyTarget,
602
+ };
603
+
604
+ const pingWellknownRule = {
605
+ isProtected: true,
606
+ from: { pathPrefix: WELLKNOWN_PING_PREFIX },
487
607
  to: {
488
- port: DEFAULT_DAEMON_PORT,
489
- type: ROUTING_RULE_TYPES.GENERAL_PROXY,
490
- interfaceName: BLOCKLET_INTERFACE_WELLKNOWN,
608
+ type: ROUTING_RULE_TYPES.DIRECT_RESPONSE,
609
+ response: {
610
+ status: 200,
611
+ contentType: 'application/javascript',
612
+ body: "'pong'",
613
+ },
614
+ port: info.port,
491
615
  },
492
616
  };
493
617
 
494
618
  if (site) {
495
- if (site.rules.find((r) => r.from.pathPrefix === normalizePathPrefix(pathPrefix))) {
496
- return false;
497
- }
498
-
499
- await routerManager.addRoutingRule(
619
+ const didResolverRuleUpdateRes = await upsertSiteRule(
500
620
  {
501
- id: site.id,
621
+ site,
502
622
  rule: didResolverWellknownRule,
503
623
  },
504
624
  context
505
625
  );
506
- } else {
507
- await routerManager.addRoutingSite(
626
+
627
+ const acmeRuleUpdateRes = await upsertSiteRule(
508
628
  {
509
- site: {
510
- domain: DOMAIN_FOR_INTERNAL_SITE,
511
- port: await getWellknownSitePort(),
512
- name: NAME_FOR_WELLKNOWN_SITE,
513
- rules: [didResolverWellknownRule],
514
- isProtected: true,
515
- },
516
- skipCheckDynamicBlacklist: true,
517
- skipValidation: true,
629
+ site,
630
+ rule: acmeChallengeWellknownRule,
518
631
  },
519
632
  context
520
633
  );
634
+
635
+ const pingRuleRes = await upsertSiteRule(
636
+ {
637
+ site,
638
+ rule: pingWellknownRule,
639
+ },
640
+ context
641
+ );
642
+
643
+ return didResolverRuleUpdateRes || acmeRuleUpdateRes || pingRuleRes;
521
644
  }
522
645
 
646
+ await routerManager.addRoutingSite(
647
+ {
648
+ site: {
649
+ domain: DOMAIN_FOR_INTERNAL_SITE,
650
+ port: await getWellknownSitePort(),
651
+ name: NAME_FOR_WELLKNOWN_SITE,
652
+ rules: [didResolverWellknownRule, acmeChallengeWellknownRule, pingWellknownRule],
653
+ isProtected: true,
654
+ },
655
+ skipCheckDynamicBlacklist: true,
656
+ skipValidation: true,
657
+ },
658
+ context
659
+ );
660
+
523
661
  return true;
524
662
  } catch (err) {
525
663
  console.error('add well-known site failed:', err);
@@ -533,14 +671,8 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
533
671
  *
534
672
  * @returns {boolean} if routing changed
535
673
  */
536
- const ensureDashboardRouting = async (context = {}, output) => {
674
+ const ensureDashboardRouting = async (context = {}) => {
537
675
  const info = await nodeState.read();
538
-
539
- const provider = getProviderFromNodeInfo(info);
540
- if (provider === ROUTER_PROVIDER_NONE) {
541
- return false;
542
- }
543
-
544
676
  const sites = await siteState.getSites();
545
677
  let dashboardSite = (sites || []).find((x) => x.domain === DOMAIN_FOR_IP_SITE);
546
678
  const updatedResult = [];
@@ -569,28 +701,26 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
569
701
  }
570
702
  }
571
703
 
572
- const isExistsInAlias = (domainAlias) =>
573
- (dashboardSite.domainAliases || []).find((item) => {
574
- if (typeof item === 'object') {
575
- return domainAlias === item.value;
576
- }
704
+ const ipWildcardDomain = get(info, 'routing.ipWildcardDomain', '');
577
705
 
578
- return domainAlias === item;
579
- });
706
+ const domainAliases = (dashboardSite.domainAliases || []).filter(
707
+ (item) => !item.value.endsWith(ipWildcardDomain) && !item.value.endsWith(info.didDomain)
708
+ );
580
709
 
581
- const dashboardDomain = get(info, 'routing.dashboardDomain', '');
582
- if (dashboardDomain && !isExistsInAlias(dashboardDomain)) {
583
- try {
584
- const result = await siteState.update(
585
- { _id: dashboardSite.id },
586
- { $push: { domainAliases: { value: dashboardDomain, isProtected: true } } }
587
- );
710
+ const didDomain = getServerDidDomain(info);
711
+ const dashboardAliasDomains = [
712
+ { value: ipWildcardDomain, isProtected: true },
713
+ { value: didDomain, isProtected: true },
714
+ ];
715
+ domainAliases.push(...dashboardAliasDomains);
588
716
 
589
- updatedResult.push(result);
590
- } catch (error) {
591
- logger.error('add dashboard domain rule failed', { error });
592
- console.error('Add dashboard domain rule failed:', error);
593
- }
717
+ try {
718
+ const result = await siteState.update({ _id: dashboardSite.id }, { $set: { domainAliases } });
719
+
720
+ updatedResult.push(result);
721
+ } catch (error) {
722
+ logger.error('add dashboard domain rule failed', { error });
723
+ console.error('Add dashboard domain rule failed:', error);
594
724
  }
595
725
 
596
726
  const defaultRule = sites.find((x) => x.domain === DOMAIN_FOR_DEFAULT_SITE);
@@ -618,18 +748,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
618
748
  updatedResult.push(wellknownRes);
619
749
  }
620
750
 
621
- // Download dashboard certificates if not exists
622
- const certDownloadAddress = get(info, 'routing.dashboardCertDownloadAddress', '');
623
- if (dashboardDomain && certDownloadAddress) {
624
- const cert = await routerManager.findCertificateByDomain(dashboardDomain);
625
- if (!cert) {
626
- await updateDashboardCertificate({ checkExpire: false });
627
- if (typeof output === 'function') {
628
- output('Dashboard HTTPS certificate was downloaded successfully!');
629
- }
630
- }
631
- }
632
-
633
751
  if (updatedResult.length) {
634
752
  const hash = await takeRoutingSnapshot({ message: 'ensure dashboard routing rules', dryRun: false }, context);
635
753
  logger.info('take routing snapshot on ensure dashboard routing rules', { updatedResult, hash });
@@ -657,8 +775,8 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
657
775
  return `/${str}`.replace(/^\/+/, '/');
658
776
  };
659
777
 
660
- const domainGroup = `${blocklet.meta.did}${BLOCKLET_SITE_GROUP_SUFFIX}`;
661
- const domain = getIpDnsDomainForBlocklet(blocklet, webInterface);
778
+ const domainGroup = getBlockletDomainGroupName(blocklet.meta.did);
779
+
662
780
  const pathPrefix = getPrefix(webInterface.prefix);
663
781
  const rule = {
664
782
  from: { pathPrefix },
@@ -672,20 +790,45 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
672
790
  };
673
791
 
674
792
  const existSite = await states.site.findOne({ domain: domainGroup });
793
+ const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
794
+ updateBlockletDocument({
795
+ wallet,
796
+ appPid: blocklet.appPid,
797
+ alsoKnownAs: getBlockletKnownAs(blocklet),
798
+ daemonDidDomain: getServerDidDomain(nodeInfo),
799
+ didRegistryUrl: nodeInfo.didRegistry,
800
+ domain: nodeInfo.didDomain,
801
+ })
802
+ .then(() => {
803
+ logger.info(`updated blocklet ${blocklet.appDid} dns`);
804
+ })
805
+ .catch((err) => {
806
+ logger.error(`update blocklet ${blocklet.appDid} dns failed`, { error: err });
807
+ });
808
+
675
809
  if (!existSite) {
810
+ const domainAliases = [];
811
+
812
+ const didDomain = getDidDomainForBlocklet({ appPid: blocklet.appPid, didDomain: nodeInfo.didDomain });
813
+ const ipEchoDnsDomain = getIpDnsDomainForBlocklet(blocklet);
814
+
815
+ // let didDomain in front of ipEchoDnsDomain
816
+ domainAliases.push({ value: didDomain, isProtected: true }, { value: ipEchoDnsDomain, isProtected: true });
817
+
676
818
  await routerManager.addRoutingSite(
677
819
  {
678
820
  site: {
679
821
  domain: domainGroup,
680
- domainAliases: [{ value: domain, isProtected: true }],
822
+ domainAliases,
681
823
  isProtected: true,
682
824
  rules: [rule],
683
825
  },
684
826
  skipCheckDynamicBlacklist: true,
827
+ skipValidation: true,
685
828
  },
686
829
  context
687
830
  );
688
- logger.info('add routing site', { site: domain });
831
+ logger.info('add routing site', { site: domainGroup });
689
832
 
690
833
  return true;
691
834
  }
@@ -700,13 +843,13 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
700
843
  },
701
844
  skipProtectedRuleChecking: true,
702
845
  });
703
- logger.info('update routing rule for site', { site: domain });
846
+ logger.info('update routing rule for site', { site: domainGroup });
704
847
  } else {
705
848
  await routerManager.addRoutingRule({
706
849
  id: existSite.id,
707
850
  rule,
708
851
  });
709
- logger.info('add routing rule for site', { site: domain });
852
+ logger.info('add routing rule for site', { site: domainGroup });
710
853
  }
711
854
 
712
855
  return true;
@@ -723,130 +866,79 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
723
866
  return false;
724
867
  }
725
868
 
726
- const tasks = (blocklet.meta.interfaces || [])
727
- .filter((x) => x.type === BLOCKLET_INTERFACE_TYPE_WELLKNOWN)
728
- .map(async (x) => {
729
- const pathPrefix = normalizePathPrefix(x.prefix);
730
- if (!pathPrefix.startsWith(WELLKNOWN_PATH_PREFIX)) {
731
- throw new Error(`Wellknown path prefix must start with: ${WELLKNOWN_PATH_PREFIX}`);
732
- }
869
+ /**
870
+ * 1. component blocklet 不允许有相同多的 wellknown
871
+ * 1. wellknown 可以访问的路由:
872
+ * /.well-known/xxx
873
+ * /{wellknown owner blocklet}/.well-known/xxx
874
+ */
733
875
 
734
- const port = findInterfacePortByName(blocklet, x.name);
735
- const existedRule = hasRuleByPrefix(wellknownSite, pathPrefix);
876
+ const isWellknownInterface = (x) => x.type === BLOCKLET_INTERFACE_TYPE_WELLKNOWN;
736
877
 
737
- const rule = {
738
- from: { pathPrefix },
739
- to: {
740
- did: blocklet.meta.did,
741
- port,
742
- type: ROUTING_RULE_TYPES.GENERAL_PROXY,
743
- interfaceName: x.name,
744
- },
745
- isProtected: true,
746
- };
878
+ const handler = async (tmpBlocklet, tmpInterface, mountPoint) => {
879
+ let pathPrefix = normalizePathPrefix(tmpInterface.prefix);
880
+ if (!pathPrefix.startsWith(WELLKNOWN_PATH_PREFIX)) {
881
+ throw new Error(`Wellknown path prefix must start with: ${WELLKNOWN_PATH_PREFIX}`);
882
+ }
747
883
 
748
- if (!existedRule) {
749
- await routerManager.addRoutingRule({ id: wellknownSite.id, rule, skipCheckDynamicBlacklist: true }, context);
750
- return true;
751
- }
884
+ const tmpMountPoint = mountPoint || tmpBlocklet.mountPoint;
885
+ if (tmpMountPoint) {
886
+ pathPrefix = joinUrl(tmpMountPoint, pathPrefix);
887
+ }
752
888
 
753
- // 兼容代码,旧的 rule 没有 to.did 字段
754
- if (port !== existedRule.to.port || existedRule.to.did !== blocklet.meta.did) {
755
- existedRule.to = rule.to;
889
+ const port = findInterfacePortByName(tmpBlocklet, tmpInterface.name);
890
+ const existedRule = hasRuleByPrefix(wellknownSite, pathPrefix);
756
891
 
757
- await routerManager.updateRoutingRule(
758
- { id: wellknownSite.id, rule: existedRule, skipProtectedRuleChecking: true },
759
- context
760
- );
892
+ const rule = {
893
+ from: { pathPrefix },
894
+ to: {
895
+ did: tmpBlocklet.meta.did,
896
+ port,
897
+ type: ROUTING_RULE_TYPES.GENERAL_PROXY,
898
+ interfaceName: tmpInterface.name,
899
+ },
900
+ isProtected: true,
901
+ };
761
902
 
762
- return true;
763
- }
903
+ if (!existedRule) {
904
+ await routerManager.addRoutingRule({ id: wellknownSite.id, rule, skipCheckDynamicBlacklist: true }, context);
905
+ return true;
906
+ }
764
907
 
765
- return false;
766
- });
908
+ // 兼容代码,旧的 rule 没有 to.did 字段
909
+ if (port !== existedRule.to.port || existedRule.to.did !== tmpBlocklet.meta.did) {
910
+ existedRule.to = rule.to;
767
911
 
768
- const changes = await Promise.all(tasks);
769
- return changes.some(Boolean);
770
- };
912
+ await routerManager.updateRoutingRule(
913
+ { id: wellknownSite.id, rule: existedRule, skipProtectedRuleChecking: true },
914
+ context
915
+ );
771
916
 
772
- /**
773
- * Add system routing rules for blocklet in dashboard site
774
- *
775
- * @returns {boolean} if routing changed
776
- */
777
- const _ensureBlockletRulesForDashboardSite = async (blocklet, sites, nodeInfo, context = {}) => {
778
- // blocklet level duplication detection
779
- const findRulesByInterface = (site, value) =>
780
- site.rules.filter((x) => x.isProtected && x.to.did === blocklet.meta.did && get(x, 'to.interfaceName') === value);
781
-
782
- const ipSite = sites.find((x) => x.domain === DOMAIN_FOR_IP_SITE);
783
- if (!ipSite) {
784
- logger.warn(`Routing rule for dashboard not found, abort on ensure rules for blocklet ${blocklet.meta.did}`);
785
- return false;
786
- }
917
+ return true;
918
+ }
787
919
 
788
- // If we have rule for legacy path prefix, just return
789
- const pathPrefixLegacy = normalizePathPrefix(`/${nodeInfo.routing.adminPath}/${blocklet.meta.name}`);
790
- if (hasRuleByPrefix(ipSite, pathPrefixLegacy)) {
791
920
  return false;
792
- }
793
-
794
- const removedRuleIds = [];
795
- const changes = await Promise.all(
796
- blocklet.meta.interfaces.map(async (x) => {
797
- let changed = false; // if state db was mutated
798
- let pathPrefix = '';
799
- if (x.prefix === BLOCKLET_DYNAMIC_PATH_PREFIX) {
800
- // pathPrefix for dynamic path: `/{adminPath}/{blockletName}/{interfaceName}`
801
- pathPrefix = normalizePathPrefix(`/${pathPrefixLegacy}/${x.name}`);
802
- } else {
803
- // pathPrefix for fixed path: `/{interfacePrefix}`
804
- pathPrefix = normalizePathPrefix(x.prefix);
805
- }
806
-
807
- // Remove if we have rule with same interface
808
- const exists = findRulesByInterface(ipSite, x.name);
809
- if (exists.length) {
810
- // Note: we need to keep a list of removed rules since we might remove multiple rules in the loop
811
- exists.forEach((e) => removedRuleIds.push(e.id));
812
- await states.site.update(
813
- { _id: ipSite.id },
814
- { $set: { rules: ipSite.rules.filter((r) => removedRuleIds.includes(r.id) === false) } }
815
- );
816
- changed = true;
817
- } else if (hasRuleByPrefix(ipSite, pathPrefix)) {
818
- // Skip same pathPrefix rules
819
- return changed;
820
- }
921
+ };
821
922
 
822
- // 不在 dashboard site 中添加 path 和 prefix 都是根路径(/) 的路由
823
- if (x.path === '/' && x.prefix === '/') {
824
- return changed;
825
- }
923
+ const tasks = [];
826
924
 
827
- await routerManager.addRoutingRule(
828
- {
829
- id: ipSite.id,
830
- rule: {
831
- from: { pathPrefix },
832
- to: {
833
- type: ROUTING_RULE_TYPES.BLOCKLET,
834
- port: findInterfacePortByName(blocklet, x.name),
835
- did: blocklet.meta.did, // root blocklet did
836
- interfaceName: x.name, // root blocklet interface
837
- },
838
- isProtected: true,
839
- },
840
- skipCheckDynamicBlacklist: true,
841
- },
842
- context
843
- );
844
- changed = true;
925
+ forEachBlocklet(
926
+ blocklet,
927
+ (b) => {
928
+ (b.meta.interfaces || []).forEach((item) => {
929
+ if (isWellknownInterface(item)) {
930
+ tasks.push(handler(b, item));
845
931
 
846
- return changed;
847
- })
932
+ if (!b.mountPoint || b.mountPoint !== '/') {
933
+ tasks.push(handler(b, item, '/')); // 在站点的根路由下挂载一个
934
+ }
935
+ }
936
+ });
937
+ },
938
+ { sync: true }
848
939
  );
849
940
 
941
+ const changes = await Promise.all(tasks);
850
942
  return changes.some(Boolean);
851
943
  };
852
944
 
@@ -873,12 +965,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
873
965
  */
874
966
  const ensureBlockletRouting = async (blocklet, context = {}) => {
875
967
  const nodeInfo = await nodeState.read();
876
-
877
- const provider = getProviderFromNodeInfo(nodeInfo);
878
- if (provider === ROUTER_PROVIDER_NONE) {
879
- return false;
880
- }
881
-
882
968
  const hasWebInterface = (blocklet.meta.interfaces || []).some((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB);
883
969
  if (!hasWebInterface) {
884
970
  return false;
@@ -891,14 +977,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
891
977
  _ensureBlockletSites(blocklet, sites, nodeInfo, context),
892
978
  ];
893
979
 
894
- if (nodeInfo.mode === NODE_MODES.DEBUG || ['e2e', 'test'].includes(process.env.NODE_ENV)) {
895
- logger.info('Add blocklet endpoint in dashboard site', {
896
- nodeMode: nodeInfo.mode,
897
- NODE_ENV: process.env.NODE_ENV,
898
- });
899
- tasks.push(_ensureBlockletRulesForDashboardSite(blocklet, sites, nodeInfo, context));
900
- }
901
-
902
980
  const changes = await Promise.all(tasks);
903
981
 
904
982
  return changes.some(Boolean);
@@ -978,12 +1056,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
978
1056
  * @returns {boolean} if routing changed
979
1057
  */
980
1058
  const ensureBlockletRoutingForUpgrade = async (blocklet, context = {}) => {
981
- const nodeInfo = await nodeState.read();
982
- const provider = getProviderFromNodeInfo(nodeInfo);
983
- if (provider === ROUTER_PROVIDER_NONE) {
984
- return false;
985
- }
986
-
987
1059
  await routerManager.deleteRoutingRulesItemByDid(
988
1060
  { did: blocklet.meta.did, ruleFilter: (x) => x.isProtected },
989
1061
  context
@@ -1004,7 +1076,10 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1004
1076
  const nodeInfo = await nodeState.read();
1005
1077
 
1006
1078
  const ruleChanged = await routerManager.deleteRoutingRulesItemByDid({ did: blocklet.meta.did }, context);
1007
- const siteChanged = await _removeBlockletSites(blocklet, nodeInfo, context);
1079
+ let siteChanged;
1080
+ if (!context.keepRouting) {
1081
+ siteChanged = await _removeBlockletSites(blocklet, nodeInfo, context);
1082
+ }
1008
1083
 
1009
1084
  return ruleChanged || siteChanged;
1010
1085
  };
@@ -1028,21 +1103,21 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1028
1103
  return result;
1029
1104
  }
1030
1105
 
1031
- async function getRoutingRulesByDid(did) {
1032
- const info = await nodeState.read();
1033
- const { sites } = await readRoutingSites();
1034
- const rules = Router.flattenSitesToRules(sites, info);
1035
- return rules.filter((rule) => rule.to.did === did);
1106
+ async function resetSiteByDid(did, { refreshRouterProvider = true } = {}) {
1107
+ const blocklet = await states.blocklet.getBlocklet(did);
1108
+ await removeBlockletRouting(blocklet);
1109
+ await ensureBlockletRouting(blocklet);
1110
+ if (refreshRouterProvider) {
1111
+ const hash = await takeRoutingSnapshot({ message: `Reset blocklet ${did}`, dryRun: false });
1112
+ logger.info('reset blocklet routing rules', { did, hash });
1113
+ }
1036
1114
  }
1037
1115
 
1038
- const routers = {}; // we need to keep reference for different router instances
1116
+ const providers = {}; // we need to keep reference for different router instances
1039
1117
  const handleRouting = async (nodeInfo) => {
1040
1118
  const providerName = get(nodeInfo, 'routing.provider', null);
1041
1119
  const httpsEnabled = get(nodeInfo, 'routing.https', true);
1042
1120
  logger.debug('handle routing', { providerName, httpsEnabled });
1043
- if (providerName === ROUTER_PROVIDER_NONE) {
1044
- return;
1045
- }
1046
1121
 
1047
1122
  const Provider = getProvider(providerName);
1048
1123
  const checkResult = await Provider.check({ configDir: dataDirs.router });
@@ -1050,27 +1125,25 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1050
1125
  throw new Error(`${providerName} pre-check failed, ${checkResult.error}`);
1051
1126
  }
1052
1127
 
1053
- if (routers[providerName]) {
1054
- await routers[providerName].restart();
1128
+ if (providers[providerName]) {
1129
+ await providers[providerName].reload();
1055
1130
  } else {
1056
- routers[providerName] = new Router({
1131
+ providers[providerName] = new Router({
1057
1132
  provider: new Provider({
1058
- configDirectory: path.join(dataDirs.router, providerName),
1133
+ configDir: path.join(dataDirs.router, providerName),
1059
1134
  httpPort: nodeInfo.routing.httpPort || DEFAULT_HTTP_PORT,
1060
1135
  httpsPort: nodeInfo.routing.httpsPort || DEFAULT_HTTPS_PORT,
1061
- cacheDisabled: nodeInfo.mode === NODE_MODES.DEBUG,
1136
+ cacheEnabled: isGatewayCacheEnabled(nodeInfo),
1062
1137
  }),
1063
1138
  getRoutingParams: async () => {
1064
1139
  try {
1065
1140
  const info = await nodeState.read();
1066
1141
  let { sites } = await readRoutingSites();
1067
1142
  sites = await ensureLatestInfo(sites);
1068
- sites = await ensureAuthService(sites, blockletManager);
1069
1143
  sites = await ensureServiceRule(sites);
1144
+ sites = await ensureRootRule(sites);
1070
1145
 
1071
- const certificates = httpsEnabled
1072
- ? await httpsCertState.find({ type: providerName }, { domain: 1, certificate: 1, privateKey: 1 })
1073
- : [];
1146
+ const certificates = httpsEnabled ? await certManager.getAllNormal() : [];
1074
1147
 
1075
1148
  return {
1076
1149
  sites,
@@ -1093,11 +1166,17 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1093
1166
  },
1094
1167
  });
1095
1168
 
1096
- routerManager.on('router.certificate.add', () => routers[providerName].restart());
1097
- routerManager.on('router.certificate.updated', () => routers[providerName].restart());
1098
- routerManager.on('router.certificate.remove', () => routers[providerName].restart());
1169
+ [
1170
+ BlockletEvents.certIssued,
1171
+ EVENTS.CERT_ADDED,
1172
+ EVENTS.CERT_REMOVED,
1173
+ EVENTS.CERT_ISSUED,
1174
+ EVENTS.CERT_UPDATED,
1175
+ ].forEach((event) => {
1176
+ certManager.on(event, () => providers[providerName].reload());
1177
+ });
1099
1178
 
1100
- await routers[providerName].start();
1179
+ await providers[providerName].start();
1101
1180
  }
1102
1181
  };
1103
1182
 
@@ -1105,8 +1184,8 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1105
1184
  const info = await nodeState.read();
1106
1185
  const providerName = get(info, 'routing.provider', null);
1107
1186
 
1108
- if (providerName && routers[providerName] && typeof routers[providerName].rotateLogs === 'function') {
1109
- await routers[providerName].rotateLogs();
1187
+ if (providerName && providers[providerName] && typeof providers[providerName].rotateLogs === 'function') {
1188
+ await providers[providerName].rotateLogs({ retain: LOG_RETAIN_IN_DAYS });
1110
1189
  }
1111
1190
  };
1112
1191
 
@@ -1135,33 +1214,31 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1135
1214
  await handleRouting(result);
1136
1215
 
1137
1216
  if (newProvider !== info.routing.provider) {
1138
- if (newProvider !== ROUTER_PROVIDER_NONE) {
1139
- // Ensure we have system sites for daemon
1140
- await ensureDashboardRouting(context);
1141
-
1142
- // Ensure we have system rules for blocklets
1143
- const blocklets = await blockletState.getBlocklets();
1144
- const ensureBlocklet = async (x) => {
1145
- const blocklet = await blockletManager.ensureBlocklet(x.meta.did);
1146
- return ensureBlockletRouting(blocklet, context);
1147
- };
1148
- const ensureBlockletResults = await Promise.all(blocklets.map((x) => ensureBlocklet(x)));
1149
-
1150
- // We need to take snapshot after system rules ensured
1151
- if (ensureBlockletResults.filter(Boolean).length) {
1152
- await takeRoutingSnapshot({ message: `Switch routing engine to ${newProvider}`, dryRun: false }, context);
1153
- logger.info(`take routing snapshot on switch engine: ${newProvider}`, { ensureBlockletResults });
1154
- }
1217
+ // Ensure we have system sites for daemon
1218
+ await ensureDashboardRouting(context);
1219
+
1220
+ // Ensure we have system rules for blocklets
1221
+ const blocklets = await blockletState.getBlocklets();
1222
+ const ensureBlocklet = async (x) => {
1223
+ const blocklet = await blockletManager.getBlocklet(x.meta.did);
1224
+ return ensureBlockletRouting(blocklet, context);
1225
+ };
1226
+ const ensureBlockletResults = await Promise.all(blocklets.map((x) => ensureBlocklet(x)));
1227
+
1228
+ // We need to take snapshot after system rules ensured
1229
+ if (ensureBlockletResults.filter(Boolean).length) {
1230
+ await takeRoutingSnapshot({ message: `Switch routing engine to ${newProvider}`, dryRun: false }, context);
1231
+ logger.info(`take routing snapshot on switch engine: ${newProvider}`, { ensureBlockletResults });
1155
1232
  }
1156
1233
 
1157
1234
  const Provider = getProvider(info.routing.provider);
1158
1235
  if (Provider) {
1159
1236
  try {
1160
1237
  const providerInstance = new Provider({
1161
- configDirectory: path.join(dataDirs.router, info.routing.provider),
1238
+ configDir: path.join(dataDirs.router, info.routing.provider),
1162
1239
  httpPort: info.routing.httpPort || DEFAULT_HTTP_PORT,
1163
1240
  httpsPort: info.routing.httpsPort || DEFAULT_HTTPS_PORT,
1164
- cacheDisabled: info.mode === NODE_MODES.DEBUG,
1241
+ cacheEnabled: isGatewayCacheEnabled(info),
1165
1242
  });
1166
1243
  await providerInstance.stop();
1167
1244
  logger.info('original router stopped:', { provider: info.routing.provider });
@@ -1240,7 +1317,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1240
1317
  return sites;
1241
1318
  }
1242
1319
 
1243
- return attachInterfaceUrls({
1320
+ return attachRuntimeDomainAliases({
1244
1321
  sites: await ensureLatestInfo(sites, { withDefaultCors }),
1245
1322
  context,
1246
1323
  node: nodeState,
@@ -1249,7 +1326,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1249
1326
 
1250
1327
  const getSnapshotSites = async ({ hash }, context = {}, { withDefaultCors = true } = {}) => {
1251
1328
  const sites = await routingSnapshot.readSnapshotSites(hash);
1252
- return attachInterfaceUrls({
1329
+ return attachRuntimeDomainAliases({
1253
1330
  sites: await ensureLatestInfo(sites, { withDefaultCors }),
1254
1331
  context,
1255
1332
  node: nodeState,
@@ -1257,65 +1334,56 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1257
1334
  };
1258
1335
 
1259
1336
  const getCertificates = async () => {
1260
- const certificates = await httpsCertState.find();
1337
+ const certificates = await certManager.getAll();
1261
1338
  const sites = await getSitesFromSnapshot();
1339
+
1340
+ const isMatch = (cert, domain) =>
1341
+ domain !== DOMAIN_FOR_DEFAULT_SITE && domain && routerManager.isCertMatchedDomain(cert, domain);
1342
+
1262
1343
  return certificates.map((cert) => {
1263
1344
  cert.matchedSites = [];
1264
1345
  sites.forEach((site) => {
1265
- if (
1266
- site.domain !== DOMAIN_FOR_DEFAULT_SITE &&
1267
- site.domain &&
1268
- routerManager.isCertMatchedDomain(cert, site.domain)
1269
- ) {
1270
- cert.matchedSites.push({ id: site.id, domain: site.domain });
1271
- }
1346
+ const domains = [site.domain, ...(site.domainAliases || []).map((x) => x.value)];
1347
+ domains.forEach((domain) => {
1348
+ if (isMatch(cert, domain)) {
1349
+ cert.matchedSites.push({ id: site.id, domain });
1350
+ }
1351
+ });
1272
1352
  });
1273
1353
 
1274
1354
  return cert;
1275
1355
  });
1276
1356
  };
1277
1357
 
1278
- const checkDomain = async (domain) => {
1279
- const matchedCert = await routerManager.getMatchedCert(domain);
1358
+ /**
1359
+ * proxy to routerManager and do takeRoutingSnapshot
1360
+ */
1361
+ const _proxyToRouterManager =
1362
+ (fnName) =>
1363
+ async (...args) => {
1364
+ const res = await routerManager[fnName](...args);
1280
1365
 
1281
- return { domain, isHttps: !!matchedCert, matchedCert };
1282
- };
1366
+ takeRoutingSnapshot({ message: fnName, dryRun: false }).catch((error) => {
1367
+ logger.error('failed to takeRoutingSnapshot', { error });
1368
+ });
1283
1369
 
1284
- const checkCertificatesExpiration = async () => {
1285
- const now = Date.now();
1286
-
1287
- const certificates = await getCertificates();
1288
- for (let i = 0; i < certificates.length; i++) {
1289
- const cert = certificates[i];
1290
- const alreadyExpired = now >= cert.validTo;
1291
- const aboutToExpire = cert.validTo - now > 0 && cert.validTo - now < CERTIFICATE_EXPIRES_WARNING_OFFSET;
1292
-
1293
- if (alreadyExpired) {
1294
- logger.info('send certificate expire notification', { domain: cert.domain });
1295
- notification.create({
1296
- title: 'SSL Certificate Expired',
1297
- description: `Your SSL certificate for domain ${cert.domain} has expired, please update it in Blocklet Server`,
1298
- severity: 'error',
1299
- entityType: 'certificate',
1300
- entityId: cert._id, // eslint-disable-line no-underscore-dangle
1301
- });
1302
- } else if (aboutToExpire) {
1303
- logger.info('send certificate about-expire notification', { domain: cert.domain });
1304
- const expireInDays = Math.ceil((cert.validTo - now) / DAY_IN_MS);
1305
- notification.create({
1306
- title: 'SSL Certificate Expire Warning',
1307
- description: `Your SSL certificate for domain ${
1308
- cert.domain
1309
- } will expire in ${expireInDays} days (on ${new Date(
1310
- cert.validTo
1311
- ).toLocaleString()}), please remember to update it in Blocklet Server`,
1312
- severity: 'warning',
1313
- entityType: 'certificate',
1314
- entityId: cert._id, // eslint-disable-line no-underscore-dangle
1315
- });
1370
+ const { teamDid: did } = args[0] || {};
1371
+ if (did) {
1372
+ routerManager.emit(BlockletEvents.updated, { meta: { did } });
1316
1373
  }
1317
- }
1318
- };
1374
+
1375
+ return res;
1376
+ };
1377
+
1378
+ const addRoutingSite = _proxyToRouterManager('addRoutingSite');
1379
+ const updateRoutingSite = _proxyToRouterManager('updateRoutingSite');
1380
+ const deleteRoutingSite = _proxyToRouterManager('deleteRoutingSite');
1381
+ const addRoutingRule = _proxyToRouterManager('addRoutingRule');
1382
+ const updateRoutingRule = _proxyToRouterManager('updateRoutingRule');
1383
+ const deleteRoutingRule = _proxyToRouterManager('deleteRoutingRule');
1384
+ // FIXME: should verify domain owner before added
1385
+ const addDomainAlias = _proxyToRouterManager('addDomainAlias');
1386
+ const deleteDomainAlias = _proxyToRouterManager('deleteDomainAlias');
1319
1387
 
1320
1388
  return {
1321
1389
  ensureDashboardRouting,
@@ -1323,39 +1391,47 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1323
1391
  ensureBlockletRoutingForUpgrade,
1324
1392
  removeBlockletRouting,
1325
1393
  handleRouting,
1326
- getRoutingRulesByDid,
1394
+ resetSiteByDid,
1327
1395
  updateNodeRouting,
1328
1396
  takeRoutingSnapshot,
1329
1397
  getRoutingSites,
1330
1398
  getSnapshotSites,
1331
- getCertificates,
1332
1399
  getSitesFromSnapshot,
1333
- checkDomain,
1400
+ getCertificates,
1401
+ ensureWildcardCerts,
1402
+ addWellknownSite,
1403
+ upsertSiteRule,
1404
+ getRouterProvider: (name) => providers[name],
1334
1405
 
1335
1406
  getRoutingCrons: () => [
1336
1407
  {
1337
1408
  name: 'update-dashboard-certificate',
1338
1409
  time: '0 1 */6 * * *', // refetch on 0:00, 6:00, etc.
1339
- fn: () => updateDashboardCertificate({ checkExpire: true }),
1410
+ fn: () => updateDashboardCertificates(),
1340
1411
  options: { runOnInit: true },
1341
1412
  },
1342
- {
1343
- name: 'check-certificate-expiration',
1344
- time: '0 0 9 * * *', // check on 09:00 every day
1345
- fn: checkCertificatesExpiration,
1346
- options: { runOnInit: false },
1347
- },
1348
1413
  {
1349
1414
  name: 'rotate-log-files',
1350
- time: '5 0 0 * * *', // rotate at 05:00 every day
1415
+ time: '1 0 0 * * *', // rotate at 00:00:01 every day
1351
1416
  fn: rotateRouterLog,
1352
1417
  options: { runOnInit: false },
1353
1418
  },
1354
1419
  ],
1420
+
1421
+ addRoutingSite,
1422
+ deleteRoutingSite,
1423
+ updateRoutingSite,
1424
+ addRoutingRule,
1425
+ updateRoutingRule,
1426
+ deleteRoutingRule,
1427
+ addDomainAlias,
1428
+ deleteDomainAlias,
1429
+
1430
+ isGatewayCacheEnabled,
1355
1431
  };
1356
1432
  };
1357
1433
 
1358
- module.exports.attachInterfaceUrls = attachInterfaceUrls;
1434
+ module.exports.attachRuntimeDomainAliases = attachRuntimeDomainAliases;
1359
1435
  module.exports.ensureLatestNodeInfo = ensureLatestNodeInfo;
1360
1436
  module.exports.ensureLatestInterfaceInfo = ensureLatestInterfaceInfo;
1361
1437
  module.exports.ensureLatestInfo = ensureLatestInfo;