@abtnode/core 1.15.17 → 1.16.0-beta-b16cb035

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 +1679 -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 +572 -497
  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 +194 -138
  70. package/lib/states/blocklet.js +361 -104
  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
@@ -8,10 +8,11 @@ const path = require('path');
8
8
  const os = require('os');
9
9
  const fse = require('fs-extra');
10
10
  const get = require('lodash/get');
11
- const { Certificate, PrivateKey } = require('@fidm/x509');
11
+ const toLower = require('lodash/toLower');
12
12
  const { EventEmitter } = require('events');
13
13
  const uuid = require('uuid');
14
14
  const isUrl = require('is-url');
15
+ const joinUrl = require('url-join');
15
16
  const cloneDeep = require('lodash/cloneDeep');
16
17
  const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
17
18
  const logger = require('@abtnode/logger')('@abtnode/core:router:manager');
@@ -27,7 +28,9 @@ const {
27
28
  BLOCKLET_BUNDLE_FOLDER,
28
29
  BLOCKLET_DYNAMIC_PATH_PREFIX,
29
30
  BLOCKLET_INTERFACE_TYPE_WEB,
30
- } = require('@blocklet/meta/lib/constants');
31
+ BlockletGroup,
32
+ } = require('@blocklet/constant');
33
+ const { forEachChildSync } = require('@blocklet/meta/lib/util');
31
34
 
32
35
  const {
33
36
  validateAddSite,
@@ -37,7 +40,8 @@ const {
37
40
  validateUpdateSite,
38
41
  } = require('../validators/router');
39
42
  const { getProviderFromNodeInfo, findInterfaceByName, isCLI, findInterfacePortByName } = require('../util');
40
- const { attachInterfaceUrls, ensureLatestInfo } = require('./helper');
43
+ const { findWebInterface } = require('../util/blocklet');
44
+ const { attachRuntimeDomainAliases, ensureLatestInfo } = require('./helper');
41
45
  const Router = require('./index');
42
46
  const states = require('../states');
43
47
 
@@ -70,8 +74,9 @@ const normalizeRedirectUrl = (url) => {
70
74
  };
71
75
 
72
76
  class RouterManager extends EventEmitter {
73
- constructor() {
77
+ constructor({ certManager }) {
74
78
  super();
79
+ this.certManager = certManager;
75
80
 
76
81
  // HACK: do not emit any events from CLI
77
82
  if (isCLI()) {
@@ -109,7 +114,7 @@ class RouterManager extends EventEmitter {
109
114
  for (const rule of newSite.rules) {
110
115
  this.fixRootBlockletRule(rule);
111
116
  checkPathPrefixInBlackList(rule.from.pathPrefix, dynamicPathBlackList);
112
- rules.push(...(await this.getRules(rule)));
117
+ rules.push(...(await this.getRulesForMutation(rule)));
113
118
  }
114
119
  }
115
120
  newSite.rules = rules;
@@ -121,7 +126,7 @@ class RouterManager extends EventEmitter {
121
126
  await this.validateRouterConfig('addRoutingSite', { site: newSite });
122
127
 
123
128
  const result = await states.site.add(newSite);
124
- await attachInterfaceUrls({ sites: result, context, node: states.node });
129
+ await attachRuntimeDomainAliases({ sites: result, context, node: states.node });
125
130
 
126
131
  this.emit('router.site.created', result);
127
132
  return result;
@@ -150,20 +155,20 @@ class RouterManager extends EventEmitter {
150
155
 
151
156
  // eslint-disable-next-line no-unused-vars
152
157
  async updateRoutingSite(params, context = {}) {
153
- await validateUpdateSite(params, context);
154
- const site = await states.site.findOne({ _id: params.id });
155
- if (!site) {
158
+ const site = await validateUpdateSite(params, context);
159
+ const existed = await states.site.findOne({ _id: site.id });
160
+ if (!existed) {
156
161
  throw new Error('Can not update non-existing site');
157
162
  }
158
163
 
159
164
  const updateSet = {};
160
165
 
161
- if (params.corsAllowedOrigins) {
162
- updateSet.corsAllowedOrigins = params.corsAllowedOrigins;
166
+ if (site.corsAllowedOrigins) {
167
+ updateSet.corsAllowedOrigins = site.corsAllowedOrigins.filter((x) => x !== '__none__');
163
168
  }
164
169
 
165
- if (params.domain) {
166
- const newDomain = params.domain;
170
+ if (site.domain) {
171
+ const newDomain = site.domain;
167
172
  const ruleCountInDB = await states.site.count({
168
173
  $or: [{ domain: newDomain }, { domainAliases: newDomain }, { 'domainAliases.value': newDomain }],
169
174
  });
@@ -175,29 +180,47 @@ class RouterManager extends EventEmitter {
175
180
  updateSet.domain = newDomain;
176
181
  }
177
182
 
178
- const updated = await states.site.update({ _id: params.id }, { $set: updateSet }, { multi: false, upsert: false });
183
+ const updated = await states.site.update({ _id: site.id }, { $set: updateSet }, { multi: false, upsert: false });
179
184
 
180
- logger.info('router.site.updated', { params, updated });
181
- this.emit('router.site.updated', params.id);
185
+ logger.info('router.site.updated', { site, updated });
186
+ this.emit('router.site.updated', site.id);
182
187
 
183
- const dbSite = await states.site.findOne({ _id: params.id });
184
- await attachInterfaceUrls({ sites: dbSite, context, node: states.node });
188
+ const dbSite = await states.site.findOne({ _id: site.id });
189
+ await attachRuntimeDomainAliases({ sites: dbSite, context, node: states.node });
185
190
  return dbSite;
186
191
  }
187
192
 
188
- async addDomainAlias({ id, domainAlias }, context = {}) {
189
- await validateAddDomainAlias(domainAlias, context);
193
+ async addDomainAlias({ id, domainAlias: tmpAlias, force }, context = {}) {
194
+ const domainAlias = await validateAddDomainAlias(tmpAlias, context);
190
195
  const dbSite = await states.site.findOne({ _id: id });
191
196
  if (!dbSite) {
192
197
  throw new Error(`site ${id} does not exist`);
193
198
  }
194
199
 
195
- const siteCount = await states.site.count({
196
- $or: [{ domain: domainAlias }, { domainAliases: domainAlias }, { 'domainAliases.value': domainAlias }],
200
+ // check domain exists in site domain
201
+ const mainDomainSiteCount = await states.site.count({
202
+ domain: domainAlias,
197
203
  });
198
204
 
199
- if (siteCount > 0) {
200
- throw new Error(`domain alias ${domainAlias} already exists`);
205
+ if (mainDomainSiteCount > 0) {
206
+ if (!force) {
207
+ throw new Error(`${domainAlias} already exists`);
208
+ } else {
209
+ throw new Error(`${domainAlias} cannot be forced-added`);
210
+ }
211
+ }
212
+
213
+ // check domain exists in site alias domain
214
+ const aliasDomainSite = await states.site.findOne({
215
+ $or: [{ domainAliases: domainAlias }, { 'domainAliases.value': domainAlias }],
216
+ });
217
+
218
+ if (aliasDomainSite) {
219
+ if (!force) {
220
+ throw new Error(`${domainAlias} already exists`);
221
+ } else {
222
+ await this.deleteDomainAlias({ id: aliasDomainSite.id, domainAlias });
223
+ }
201
224
  }
202
225
 
203
226
  const updateResult = await states.site.update(
@@ -207,13 +230,13 @@ class RouterManager extends EventEmitter {
207
230
  logger.debug('add domain alias update result', { id, updateResult, domainAlias });
208
231
 
209
232
  const newSite = await states.site.findOne({ _id: id });
210
- await attachInterfaceUrls({ sites: newSite, context, node: states.node });
233
+ await attachRuntimeDomainAliases({ sites: newSite, context, node: states.node });
211
234
 
212
235
  return newSite;
213
236
  }
214
237
 
215
- async deleteDomainAlias({ id, domainAlias }, context = {}) {
216
- await validateAddDomainAlias(domainAlias, context);
238
+ async deleteDomainAlias({ id, domainAlias: tmpAlias }, context = {}) {
239
+ const domainAlias = await validateAddDomainAlias(tmpAlias, context);
217
240
  const dbSite = await states.site.findOne({ _id: id });
218
241
  if (!dbSite) {
219
242
  throw new Error(`site ${id} does not exist`);
@@ -221,22 +244,22 @@ class RouterManager extends EventEmitter {
221
244
 
222
245
  dbSite.domainAliases = dbSite.domainAliases.filter((x) => {
223
246
  if (typeof x === 'string') {
224
- return x !== domainAlias;
247
+ return toLower(x) !== domainAlias;
225
248
  }
226
249
 
227
- return x.value !== domainAlias;
250
+ return toLower(x.value) !== domainAlias;
228
251
  });
229
252
 
230
253
  const updateResult = await states.site.update({ _id: id }, { $set: { domainAliases: dbSite.domainAliases } });
231
254
  logger.debug('remove domain alias update result', { id, updateResult, domainAlias });
232
255
 
233
- await attachInterfaceUrls({ sites: dbSite, context, node: states.node });
256
+ await attachRuntimeDomainAliases({ sites: dbSite, context, node: states.node });
234
257
 
235
258
  return dbSite;
236
259
  }
237
260
 
238
- async addRoutingRule({ id, rule, skipCheckDynamicBlacklist = false }, context = {}) {
239
- await validateAddRule({ id, rule }, context);
261
+ async addRoutingRule({ id, rule: tempRule, skipCheckDynamicBlacklist = false }, context = {}) {
262
+ const { rule } = await validateAddRule({ id, rule: tempRule }, context);
240
263
  const dbSite = await states.site.findOne({ _id: id });
241
264
  if (!dbSite) {
242
265
  throw new Error(`site ${id} does not exist`);
@@ -260,19 +283,19 @@ class RouterManager extends EventEmitter {
260
283
  await this.validateRouterConfig('addRoutingRule', { id, rule });
261
284
 
262
285
  // add child blocklet rules
263
- for (const x of await this.getRules(rule)) {
286
+ for (const x of await this.getRulesForMutation(rule)) {
264
287
  await states.site.addRuleToSite(id, x);
265
288
  }
266
289
 
267
290
  const newSite = await states.site.findOne({ _id: id });
268
- await attachInterfaceUrls({ sites: newSite, context, node: states.node });
291
+ await attachRuntimeDomainAliases({ sites: newSite, context, node: states.node });
269
292
 
270
293
  this.emit('router.rule.created', newSite);
271
294
  return newSite;
272
295
  }
273
296
 
274
- async updateRoutingRule({ id, rule, skipProtectedRuleChecking = false }, context = {}) {
275
- await validateEditRule({ id, rule }, context);
297
+ async updateRoutingRule({ id, rule: tmpRule, skipProtectedRuleChecking = false }, context = {}) {
298
+ const { rule } = await validateEditRule({ id, rule: tmpRule }, context);
276
299
  const dbSite = await states.site.findOne({ _id: id, 'rules.id': rule.id });
277
300
  if (!dbSite) {
278
301
  throw new Error(`site ${id}, rule ${rule.id} does not exist`);
@@ -300,13 +323,16 @@ class RouterManager extends EventEmitter {
300
323
  await this.validateRouterConfig('updateRoutingRule', { id, rule });
301
324
 
302
325
  // update rules
303
- const newRules = [...dbSite.rules.filter((x) => x.groupId !== rule.id), ...(await this.getRules(rule))];
326
+ const newRules = [
327
+ ...dbSite.rules.filter((x) => (x.groupId && x.groupId !== rule.id) || x.id !== rule.id), // 有些路由没有 rule.groupId
328
+ ...(await this.getRulesForMutation(rule)),
329
+ ];
304
330
 
305
331
  const updateResult = await states.site.update({ _id: id }, { $set: { rules: newRules } });
306
332
  logger.info('update result', { updateResult });
307
333
  const newSite = await states.site.findOne({ _id: id });
308
334
 
309
- await attachInterfaceUrls({ sites: newSite, context, node: states.node });
335
+ await attachRuntimeDomainAliases({ sites: newSite, context, node: states.node });
310
336
 
311
337
  this.emit('router.rule.updated', newSite);
312
338
 
@@ -335,64 +361,12 @@ class RouterManager extends EventEmitter {
335
361
  logger.info('router.rule.removed', { id, ruleId });
336
362
  const newSite = await states.site.findOne({ _id: id });
337
363
 
338
- await attachInterfaceUrls({ sites: newSite, context, node: states.node });
364
+ await attachRuntimeDomainAliases({ sites: newSite, context, node: states.node });
339
365
 
340
366
  this.emit('router.rule.removed', newSite);
341
367
  return newSite;
342
368
  }
343
369
 
344
- // eslint-disable-next-line no-unused-vars
345
- async addCertificate(entity, context = {}) {
346
- entity.type = 'nginx';
347
- this.fixCertificate(entity);
348
-
349
- this.validateCertificate(entity);
350
-
351
- const info = Certificate.fromPEM(entity.certificate);
352
- const domain = get(info, 'subject.commonName', '');
353
-
354
- const cert = await states.certificate.find({ domain });
355
- const hasOne = cert && cert.length;
356
-
357
- if (hasOne) {
358
- throw new Error('certificate has exists!');
359
- }
360
-
361
- const newCert = await states.certificate.upsert({
362
- ...entity,
363
- domain,
364
- });
365
- logger.info('add certificate result', { domain: newCert.domain });
366
- this.emit('router.certificate.add', { type: 'nginx', data: newCert });
367
- }
368
-
369
- // eslint-disable-next-line no-unused-vars
370
- async deleteCertificate({ id }, context = {}) {
371
- const tmpCert = await states.certificate.find({ _id: id });
372
- if (!tmpCert) {
373
- throw new Error('certificate does not exist');
374
- }
375
-
376
- const removeResult = await states.certificate.remove({ _id: id });
377
-
378
- logger.info('delete certificate', { removeResult, domain: tmpCert.domain });
379
- this.emit('router.certificate.remove', { type: 'nginx', data: { domain: tmpCert.domain } });
380
- return {};
381
- }
382
-
383
- // eslint-disable-next-line no-unused-vars
384
- async updateNginxHttpsCert(entity, context = {}) {
385
- entity.type = 'nginx';
386
- this.fixCertificate(entity);
387
- this.validateCertificate(entity, entity.domain);
388
- const dbEntity = await states.certificate.upsert(entity);
389
- this.emit('router.certificate.updated', { type: 'nginx', data: dbEntity });
390
- }
391
-
392
- findCertificateByDomain(domain) {
393
- return states.certificate.findOne({ domain });
394
- }
395
-
396
370
  // ============================================================================================
397
371
  // Internal API that are used by public APIs and called from CLI
398
372
  // ============================================================================================
@@ -448,16 +422,16 @@ class RouterManager extends EventEmitter {
448
422
  }
449
423
 
450
424
  async getMatchedCert(domain) {
451
- const certs = await states.certificate.find();
425
+ const certs = await this.certManager.getAllNormal();
452
426
  const matchedCert = certs.find((cert) => this.isCertMatchedDomain(cert, domain));
453
427
 
454
428
  if (matchedCert) {
455
429
  return {
456
430
  id: matchedCert.id,
457
431
  domain: matchedCert.domain,
458
- issuer: matchedCert.issuer,
459
- validFrom: matchedCert.validFrom,
460
- validTo: matchedCert.validTo,
432
+ issuer: matchedCert.meta.issuer,
433
+ validFrom: matchedCert.meta.validFrom,
434
+ validTo: matchedCert.meta.validTo,
461
435
  };
462
436
  }
463
437
 
@@ -522,10 +496,7 @@ class RouterManager extends EventEmitter {
522
496
  const info = await states.node.read();
523
497
  const httpsEnabled = get(info, 'routing.https', true);
524
498
 
525
- const selection = { domain: 1, privateKey: 1, certificate: 1 };
526
- const certificates = httpsEnabled
527
- ? await states.certificate.find({ type: get(info, 'routing.provider', null) }, selection)
528
- : [];
499
+ const certificates = httpsEnabled ? await this.certManager.getAllNormal() : [];
529
500
 
530
501
  let sites = await states.site.getSites();
531
502
  // mutate data by input
@@ -570,14 +541,14 @@ class RouterManager extends EventEmitter {
570
541
  // get provider
571
542
  const providerName = getProviderFromNodeInfo(info);
572
543
  const Provider = getProvider(providerName);
573
- const tempPath = path.join(os.tmpdir(), `${providerName}-${Date.now()}`);
574
- const provider = new Provider({ configDirectory: tempPath });
544
+ const tmpDir = path.join(os.tmpdir(), `${providerName}-${Date.now()}`);
545
+ const provider = new Provider({ configDir: tmpDir });
575
546
  const tempRouter = new Router({
576
547
  provider,
577
548
  getRoutingParams: async () => ({
578
549
  sites: await ensureLatestInfo(sites),
579
550
  certificates,
580
- globalHeaders: get(info, 'routing.headers', {}),
551
+ commonHeaders: get(info, 'routing.headers', {}),
581
552
  services: [], // TODO: do we need to add some item here?
582
553
  nodeInfo: info,
583
554
  }),
@@ -586,47 +557,14 @@ class RouterManager extends EventEmitter {
586
557
  await tempRouter.updateRoutingTable();
587
558
  try {
588
559
  await tempRouter.validateConfig();
589
- await fse.remove(tempPath);
560
+ await fse.remove(tmpDir);
590
561
  } catch (error) {
562
+ // 如果出错,保留 Nginx 配置文件,方便定位问题
591
563
  logger.error('validate router config failed', { error, action, data });
592
- await fse.remove(tempPath);
593
564
  throw error;
594
565
  }
595
566
  }
596
567
 
597
- validateCertificate(cert, domain) {
598
- const certificate = Certificate.fromPEM(cert.certificate);
599
- const privateKey = PrivateKey.fromPEM(cert.privateKey);
600
-
601
- const data = Buffer.allocUnsafe(100);
602
- const signature = privateKey.sign(data, 'sha512');
603
- if (!certificate.publicKey.verify(data, signature, 'sha512')) {
604
- throw new Error('Invalid certificate: signature verify failed');
605
- }
606
-
607
- const certDomain = get(certificate, 'subject.commonName', '');
608
- if (domain && domain !== certDomain) {
609
- throw new Error('Invalid certificate: domain does not match');
610
- }
611
-
612
- const validFrom = get(certificate, 'validFrom', '');
613
- if (!validFrom || new Date(validFrom).getTime() > Date.now()) {
614
- throw new Error('Invalid certificate: not in valid period');
615
- }
616
- const validTo = get(certificate, 'validTo', '');
617
- if (!validTo || new Date(validTo).getTime() < Date.now()) {
618
- throw new Error('Invalid certificate: not in valid period');
619
- }
620
-
621
- return certificate;
622
- }
623
-
624
- fixCertificate(entity) {
625
- // Hack: this logic exists because gql does not allow line breaks in arg values
626
- entity.privateKey = entity.privateKey.split('|').join('\n');
627
- entity.certificate = entity.certificate.split('|').join('\n');
628
- }
629
-
630
568
  fixRootBlockletRule(rule) {
631
569
  if (!rule.id) {
632
570
  rule.id = uuid.v4();
@@ -634,9 +572,9 @@ class RouterManager extends EventEmitter {
634
572
  rule.groupId = rule.id;
635
573
  rule.from.pathPrefix = normalizePathPrefix(rule.from.pathPrefix);
636
574
  if (rule.to.type === ROUTING_RULE_TYPES.BLOCKLET) {
637
- rule.from.groupPathPrefix = rule.from.pathPrefix;
638
- rule.to.realDid = rule.to.did;
639
- rule.to.realInterfaceName = rule.to.interfaceName;
575
+ // pathPrefix of root blocklet maybe changed to another prefix than '/', so use old groupPathPrefix first
576
+ rule.from.groupPathPrefix = rule.from.groupPathPrefix || rule.from.pathPrefix;
577
+ rule.to.componentId = rule.to.did;
640
578
  }
641
579
  if (rule.to.url) {
642
580
  rule.to.url = normalizeRedirectUrl(rule.to.url);
@@ -645,64 +583,85 @@ class RouterManager extends EventEmitter {
645
583
 
646
584
  /**
647
585
  * get all rules to be add or update to site from root rule
648
- * @param {*} rule
586
+ * @param {*} rootRule
649
587
  */
650
- async getRules(rule) {
651
- if (rule.to.type !== ROUTING_RULE_TYPES.BLOCKLET) {
652
- return [rule];
588
+ async getRulesForMutation(rootRule) {
589
+ if (rootRule.to.type !== ROUTING_RULE_TYPES.BLOCKLET) {
590
+ return [rootRule];
653
591
  }
654
592
 
655
593
  const rules = [];
656
- let occupied = false;
657
594
 
658
595
  // get child rules
659
- const blocklet = await states.blocklet.getBlocklet(rule.to.did);
660
- for (const childMeta of blocklet.meta.children || []) {
661
- for (const mountPoint of childMeta.mountPoints) {
662
- const child = blocklet.children.find((b) => b.meta.name === childMeta.name);
663
-
664
- if (!child) {
665
- logger.error(`Child blocklet ${childMeta.name} does not exist`);
666
- // eslint-disable-next-line no-continue
667
- continue;
668
- }
596
+ const blocklet = await states.blocklet.getBlocklet(rootRule.to.did);
669
597
 
670
- if (mountPoint.root.interfaceName === rule.to.interfaceName) {
671
- const pathPrefix = path.join(rule.from.pathPrefix, mountPoint.root.prefix);
672
- const isRootPath = pathPrefix === rule.from.pathPrefix;
673
- if (isRootPath) {
674
- occupied = true;
675
- }
598
+ // blocklet may be mounted in relative prefix (for old usage), so blockletPrefix may not be '/'
599
+ // blocklet prefix is the origin pathPrefix in rootRule
600
+ const blockletPrefix = normalizePathPrefix(rootRule.from.pathPrefix);
676
601
 
677
- // if is root path, child rule become root rule
678
- const childRule = {
679
- id: isRootPath ? rule.id : uuid.v4(),
680
- groupId: rule.id,
681
- from: {
682
- pathPrefix: normalizePathPrefix(pathPrefix),
683
- groupPathPrefix: rule.from.pathPrefix,
684
- },
685
- to: {
686
- type: ROUTING_RULE_TYPES.BLOCKLET,
687
- port: findInterfacePortByName(child, mountPoint.child.interfaceName),
688
- did: rule.to.did, // root blocklet did
689
- interfaceName: rule.to.interfaceName, // root blocklet interface
690
- realDid: child.meta.did, // child blocklet did
691
- realInterfaceName: mountPoint.child.interfaceName,
692
- },
693
- isProtected: isRootPath ? rule.isProtected : true,
694
- };
695
-
696
- rules.push(childRule);
697
- }
698
- }
699
- }
602
+ // root component's mountPoint may not be '/'
603
+ const rootComponentPrefix = joinUrl(blockletPrefix, blocklet.mountPoint || '/');
604
+ rootRule.from.pathPrefix = normalizePathPrefix(rootComponentPrefix);
605
+
606
+ const isOccupiable = blocklet.meta.group === BlockletGroup.gateway;
700
607
 
701
- // get root rule
702
- if (!occupied) {
703
- rules.push(rule);
608
+ if (!isOccupiable) {
609
+ rules.push(rootRule);
704
610
  }
705
611
 
612
+ forEachChildSync(blocklet, (component, { id, ancestors }) => {
613
+ if (component.meta.group === BlockletGroup.gateway) {
614
+ return;
615
+ }
616
+
617
+ const { mountPoint } = component;
618
+ if (!mountPoint) {
619
+ logger.error(`mountPoint of child ${component.meta.name} does not exist`);
620
+ // eslint-disable-next-line no-continue
621
+ return;
622
+ }
623
+
624
+ const childWebInterface = findWebInterface(component);
625
+ if (!childWebInterface) {
626
+ logger.error(`web interface of child ${component.meta.name} does not exist`);
627
+ // eslint-disable-next-line no-continue
628
+ return;
629
+ }
630
+
631
+ const pathPrefix = path.join(
632
+ blockletPrefix,
633
+ // level 1 component should be independent with root component
634
+ ...ancestors.slice(1).map((x) => x.mountPoint || ''),
635
+ mountPoint
636
+ );
637
+
638
+ const occupied = normalizePathPrefix(pathPrefix) === normalizePathPrefix(rootRule.from.pathPrefix);
639
+
640
+ if (occupied && !isOccupiable) {
641
+ return;
642
+ }
643
+
644
+ // if is root path, child rule become root rule
645
+ const childRule = {
646
+ id: occupied ? rootRule.id : uuid.v4(),
647
+ groupId: rootRule.id,
648
+ from: {
649
+ pathPrefix: normalizePathPrefix(pathPrefix),
650
+ groupPathPrefix: blockletPrefix,
651
+ },
652
+ to: {
653
+ type: ROUTING_RULE_TYPES.BLOCKLET,
654
+ port: findInterfacePortByName(component, childWebInterface.name),
655
+ did: rootRule.to.did, // root component did
656
+ interfaceName: rootRule.to.interfaceName, // root component interface
657
+ componentId: id,
658
+ },
659
+ isProtected: occupied ? rootRule.isProtected : true,
660
+ };
661
+
662
+ rules.push(childRule);
663
+ });
664
+
706
665
  return rules;
707
666
  }
708
667
  }
@@ -1,3 +1,38 @@
1
1
  # State DB
2
2
 
3
- All ABT Node states are managed by files in this folder.
3
+ All Blocklet Server states are managed by files in this folder.
4
+
5
+ ## blocklet
6
+
7
+ - `meta` meta of bundle _defined in @blocklet/meta/schema_
8
+ - `did`, `name`: component id, default from source meta, can be changed before install
9
+ - `title`, `description`: component info, default from source meta, can be changed before install
10
+ - `bundleDdid`, `bundleName`: bundle id, copy from source meta
11
+ - `version`: component version
12
+ - `price`: used for charging
13
+ - `logo`: component logo
14
+ - `logoUrl`: component logo (from store)
15
+ - `interfaces`: for resolving: runtime port; service config; web base prefix; api base prefix
16
+ - `environments`: component environment
17
+ - `scripts`: for hook of component lifecycle
18
+ - `engine`, `timeout`, `group`, `main`: related to process startup
19
+ - `dist`: bundle source
20
+ - `theme`: app theme
21
+ - `navigation`: app navigation`
22
+ - `source`: type of bundle source
23
+ - `bundleSource`: download infomation of bundle srouce
24
+ - `deployedFrom`: extra information of bundle source
25
+ - `appDid`: app instance id _app only_
26
+ - `status`, `startedAt`, `installedAt`, `stoppedAt` app status
27
+ - `deletedAt` _component only_
28
+ - `mode` app mode
29
+ - `ports` runtime port resolved from meta
30
+ - `<ENV_NAME>`: `<port number>`
31
+ - `environments[]`: env variables generated by server, e.g. BLOCKLET_APP_SK, BLOCKLET_APP_DID
32
+ - `<key>`: `<value>`
33
+ - `children`: component of app, same structure as parent, can be nested
34
+ - `mountPoint` mount path of web site. _component only_
35
+ - `dynamic` is dynamically added. _component only_
36
+ - `controller`
37
+ - `tokens`
38
+ - `downloadTokenList`