@abtnode/core 1.8.67 → 1.8.68-beta-500af7e5

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.
@@ -1,16 +1,22 @@
1
1
  /**
2
2
  * @typedef {{
3
- * did?: string
3
+ * did: string
4
+ * event: import('events').EventEmitter,
5
+ * userDid: string,
6
+ * referrer: string,
4
7
  * }} SpaceBackupInput
5
8
  */
6
9
 
7
10
  const { isValid } = require('@arcblock/did');
8
- const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
11
+ const { BLOCKLET_CONFIGURABLE_KEY, BlockletEvents } = require('@blocklet/constant');
9
12
  const { getBlockletInfo } = require('@blocklet/meta');
10
- const { SpaceClient, SyncFolderPushCommand } = require('@did-space/client');
13
+ const { SpaceClient, BackupBlockletCommand } = require('@did-space/client');
11
14
  const { ensureDirSync } = require('fs-extra');
12
15
  const { isEmpty } = require('lodash');
13
- const { join } = require('path');
16
+ const { join, basename } = require('path');
17
+
18
+ const logger = require('@abtnode/logger')('@abtnode/core:storage:backup');
19
+
14
20
  const states = require('../../../states');
15
21
  const { AuditLogBackup } = require('./audit-log');
16
22
  const { BlockletBackup } = require('./blocklet');
@@ -131,12 +137,22 @@ class SpacesBackup {
131
137
  }
132
138
 
133
139
  async export() {
140
+ this.input.event.emit(BlockletEvents.backupProgress, {
141
+ did: this.input.did,
142
+ message: 'Preparing data for backup...',
143
+ progress: 15,
144
+ });
134
145
  await Promise.all(
135
146
  this.storages.map((storage) => {
136
147
  storage.ensureParams(this);
137
148
  return storage.export();
138
149
  })
139
150
  );
151
+ this.input.event.emit(BlockletEvents.backupProgress, {
152
+ did: this.input.did,
153
+ message: 'Data ready, start backup...',
154
+ progress: 20,
155
+ });
140
156
  }
141
157
 
142
158
  async syncToSpaces() {
@@ -146,22 +162,34 @@ class SpacesBackup {
146
162
  wallet,
147
163
  });
148
164
 
149
- // FIXME: Spaces 里面预览出 blocklet 的样式,需要规划一个好的数据结构
150
- const { errorCount } = await spaceClient.send(
151
- new SyncFolderPushCommand({
165
+ const { errorCount, message } = await spaceClient.send(
166
+ new BackupBlockletCommand({
167
+ appDid: this.blocklet.appDid,
152
168
  source: join(this.blockletBackupDir, '/'),
153
- target: join('.did-objects', this.blocklet.appDid),
154
169
  debug: true,
155
- concurrency: 64,
156
- retryCount: 100,
170
+ concurrency: 32,
171
+ retryCount: 10,
157
172
  filter: (object) => {
173
+ // FIXME: @yejianchao 这里需要更完整的黑名单
158
174
  return object.name !== '.DS_Store';
159
175
  },
176
+ onProgress: (data) => {
177
+ logger.info('backup progress', { appDid: this.input.did, data });
178
+ const percent = (data.completed * 100) / data.total;
179
+ this.input.event.emit(BlockletEvents.backupProgress, {
180
+ did: this.input.did,
181
+ message: `Uploaded file ${basename(data.key)} (${data.completed}/${data.total})`,
182
+ // 0.8 是因为上传文件到 spaces 占进度的 80%,+ 20 是因为需要累加之前的进度
183
+ progress: +Math.ceil(percent * 0.8).toFixed(2) + 20,
184
+ });
185
+ },
186
+ userDid: this.input.userDid,
187
+ referrer: this.input.referrer,
160
188
  })
161
189
  );
162
190
 
163
191
  if (errorCount !== 0) {
164
- throw new Error(`Sync to spaces encountered ${errorCount} error`);
192
+ throw new Error(`Sync to spaces encountered ${errorCount} error: ${message}`);
165
193
  }
166
194
  }
167
195
 
@@ -13,14 +13,6 @@ class BaseRestore {
13
13
  */
14
14
  blockletRestoreDir;
15
15
 
16
- /**
17
- *
18
- * @description spaces 的 endpoint
19
- * @type {import('@ocap/wallet').WalletObject}
20
- * @memberof BaseRestore
21
- */
22
- blockletWallet;
23
-
24
16
  /**
25
17
  *
26
18
  * @description server 的数据目录
@@ -41,13 +33,22 @@ class BaseRestore {
41
33
  */
42
34
  ensureParams(spaces) {
43
35
  this.blockletRestoreDir = spaces.blockletRestoreDir;
44
- this.blockletWallet = spaces.blockletWallet;
45
36
  this.serverDataDir = spaces.serverDataDir;
46
37
  }
47
38
 
48
39
  async import() {
49
40
  throw new Error('not implemented');
50
41
  }
42
+
43
+ /**
44
+ * Generate params for BlockletManager to install
45
+ *
46
+ * @return {object}
47
+ * @memberof BaseRestore
48
+ */
49
+ getInstallParams() {
50
+ return {};
51
+ }
51
52
  }
52
53
 
53
54
  module.exports = {
@@ -2,6 +2,7 @@ const { removeSync, outputJsonSync, readJSONSync } = require('fs-extra');
2
2
  const { cloneDeep, isArray } = require('lodash');
3
3
  const { join } = require('path');
4
4
  const security = require('@abtnode/util/lib/security');
5
+ const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
5
6
  const { BaseRestore } = require('./base');
6
7
 
7
8
  class BlockletExtrasRestore extends BaseRestore {
@@ -24,7 +25,6 @@ class BlockletExtrasRestore extends BaseRestore {
24
25
  * @type {import('@abtnode/client').BlockletState}
25
26
  */
26
27
  const blockletExtra = readJSONSync(join(this.blockletRestoreDir, this.filename));
27
-
28
28
  return this.cleanData(blockletExtra);
29
29
  }
30
30
 
@@ -32,7 +32,6 @@ class BlockletExtrasRestore extends BaseRestore {
32
32
  *
33
33
  * @description 清理数据并加密
34
34
  * @param {import('@abtnode/client').BlockletState} blockletExtraInput
35
- * @return {Promise<void>}
36
35
  * @memberof BlockletExtrasRestore
37
36
  */
38
37
  async cleanData(blockletExtraInput) {
@@ -43,7 +42,7 @@ class BlockletExtrasRestore extends BaseRestore {
43
42
  const currentBlockletExtra = queue.pop();
44
43
 
45
44
  // 加解密
46
- this.useBlockletDecryptConfigs(currentBlockletExtra.configs);
45
+ this.decrypt(currentBlockletExtra.configs);
47
46
 
48
47
  if (currentBlockletExtra?.children) {
49
48
  queue.push(...currentBlockletExtra.children);
@@ -55,31 +54,37 @@ class BlockletExtrasRestore extends BaseRestore {
55
54
 
56
55
  /**
57
56
  *
58
- * @description 清理数据并加密
57
+ * @description 解密加密的数据
59
58
  * @param {import('@abtnode/client').ConfigEntry[]} configs
60
59
  * @return {void}
61
60
  * @memberof BlockletExtrasRestore
62
61
  */
63
- useBlockletDecryptConfigs(configs) {
62
+ decrypt(configs) {
64
63
  if (!configs || !isArray(configs)) {
65
64
  return;
66
65
  }
67
66
 
67
+ const { appDid, password } = this.input;
68
68
  for (const config of configs) {
69
- // secure 为 true 的配置才需要被加密保存上次到 did spaces
70
69
  if (config.secure) {
71
- const encryptByBlocklet = config.value;
72
-
73
- // 再用 blocklet secret 加密,然后才可以上传到 spaces
74
- const decryptByBlocklet = security.decrypt(
75
- encryptByBlocklet,
76
- this.blockletWallet.address,
77
- Buffer.from(this.blockletWallet.secretKey)
78
- );
79
-
80
- config.value = decryptByBlocklet;
70
+ const encrypted = config.value;
71
+ const decrypted = security.decrypt(encrypted, appDid, Buffer.from(password));
72
+ config.value = decrypted;
81
73
  }
82
74
  }
75
+
76
+ const config = configs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK);
77
+ if (!config) {
78
+ throw new Error(`Invalid blocklet backup file, no ${BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK} found`);
79
+ }
80
+
81
+ this.appSk = config.value;
82
+ }
83
+
84
+ getInstallParams() {
85
+ return {
86
+ appSk: this.appSk,
87
+ };
83
88
  }
84
89
  }
85
90
 
@@ -1,4 +1,4 @@
1
- const { existsSync, remove } = require('fs-extra');
1
+ const { existsSync, remove, ensureDirSync } = require('fs-extra');
2
2
  const { join } = require('path');
3
3
  const fg = require('fast-glob');
4
4
  const { BaseRestore } = require('./base');
@@ -10,8 +10,11 @@ class BlockletsRestore extends BaseRestore {
10
10
  async import() {
11
11
  const blockletsDir = join(this.blockletRestoreDir, this.filename);
12
12
 
13
+ // blockletsDir 可以不存在, 因为还原的 blocklet 可能所有的组件都是来自 store 的
13
14
  if (!existsSync(blockletsDir)) {
14
- throw new Error(`dir not found: ${blockletsDir}`);
15
+ // FIXME: 如果文件夹不存在,需要创建文件夹,因为 installFromBackup 未做容错处理
16
+ ensureDirSync(blockletsDir);
17
+ return;
15
18
  }
16
19
 
17
20
  const paths = await fg('**/*.zip', {
@@ -1,16 +1,24 @@
1
1
  /**
2
2
  * @typedef {{
3
+ * appDid: string;
3
4
  * endpoint: string;
4
- * blockletSecretKey: string;
5
+ * password: Buffer;
6
+ * delegation: string;
7
+ * wallet: import('@ocap/wallet').WalletObject;
8
+ * event: import('events').EventEmitter,
9
+ * userDid: string,
10
+ * referrer: string,
5
11
  * }} SpaceRestoreInput
6
12
  */
7
13
 
8
- const { SpaceClient, SyncFolderPullCommand } = require('@did-space/client');
9
- const { types } = require('@ocap/mcrypto');
10
- const { fromSecretKey, WalletType } = require('@ocap/wallet');
11
- const { ensureDirSync, existsSync, rmdirSync } = require('fs-extra');
12
- const { join } = require('path');
13
14
  const validUrl = require('valid-url');
15
+ const { BlockletEvents } = require('@blocklet/constant');
16
+ const { SpaceClient, RestoreBlockletCommand } = require('@did-space/client');
17
+ const { ensureDirSync, existsSync, rmdirSync } = require('fs-extra');
18
+ const { join, basename } = require('path');
19
+
20
+ const logger = require('@abtnode/logger')('@abtnode/core:storage:restore');
21
+
14
22
  const { BlockletExtrasRestore } = require('./blocklet-extras');
15
23
  const { BlockletsRestore } = require('./blocklets');
16
24
 
@@ -37,14 +45,6 @@ class SpacesRestore {
37
45
  */
38
46
  serverDataDir;
39
47
 
40
- /**
41
- *
42
- * @description spaces 的 endpoint
43
- * @type {import('@ocap/wallet').WalletObject}
44
- * @memberof SpacesRestore
45
- */
46
- blockletWallet;
47
-
48
48
  storages;
49
49
 
50
50
  /**
@@ -72,68 +72,75 @@ class SpacesRestore {
72
72
 
73
73
  async initialize() {
74
74
  this.serverDataDir = process.env.ABT_NODE_DATA_DIR;
75
- this.blockletWallet = await this.getBlockletWallet();
76
75
 
77
- if (!this.input.endpoint.includes(this.blockletWallet.address)) {
78
- throw new Error(`endpoint and blocklet.appDid(${this.blockletWallet.address}) do not match`);
76
+ if (!this.input.endpoint.includes(this.input.appDid)) {
77
+ throw new Error(`endpoint and blocklet.appDid(${this.input.appDid}) do not match`);
79
78
  }
80
79
 
81
- this.blockletRestoreDir = join(process.env.ABT_NODE_DATA_DIR, 'tmp/restore', this.blockletWallet.address);
80
+ this.blockletRestoreDir = join(process.env.ABT_NODE_DATA_DIR, 'tmp/restore', this.input.appDid);
82
81
  if (existsSync(this.blockletRestoreDir)) {
83
82
  rmdirSync(this.blockletRestoreDir, { recursive: true });
84
83
  }
85
84
  ensureDirSync(this.blockletRestoreDir);
86
85
  }
87
86
 
88
- /**
89
- *
90
- * @returns {Promise<void>}
91
- * @memberof SpacesRestore
92
- */
93
87
  async restore() {
94
88
  await this.initialize();
95
89
  await this.syncFromSpaces();
96
90
  await this.import();
97
- }
98
-
99
- async getBlockletWallet() {
100
- // @FIXME: blocklet 钱包类型如何得知呢?
101
- const wallet = fromSecretKey(this.input.blockletSecretKey, WalletType({ role: types.RoleType.ROLE_APPLICATION }));
102
91
 
103
- return wallet;
92
+ return this.storages.map((x) => x.getInstallParams());
104
93
  }
105
94
 
106
95
  async syncFromSpaces() {
107
- const { endpoint } = this.input;
108
- const wallet = await this.getBlockletWallet();
96
+ const { endpoint, wallet, delegation } = this.input;
109
97
 
110
98
  const spaceClient = new SpaceClient({
111
99
  endpoint,
100
+ delegation,
112
101
  wallet,
113
102
  });
114
103
 
115
- const { errorCount } = await spaceClient.send(
116
- new SyncFolderPullCommand({
117
- source: join('.did-objects', this.blockletWallet.address, '/'),
118
- target: this.blockletRestoreDir,
104
+ const { errorCount, message } = await spaceClient.send(
105
+ new RestoreBlockletCommand({
106
+ appDid: this.input.appDid,
107
+ target: join(this.blockletRestoreDir, '/'),
119
108
  debug: true,
120
- concurrency: 64,
121
- retryCount: 100,
109
+ concurrency: 32,
110
+ retryCount: 10,
111
+ onProgress: (data) => {
112
+ logger.info('restore progress', { appDid: this.input.appDid, data });
113
+ this.input.event.emit(BlockletEvents.restoreProgress, {
114
+ did: this.input.appDid,
115
+ message: `Downloaded file ${basename(data.key)} (${data.completed}/${data.total})`,
116
+ });
117
+ },
118
+
119
+ userDid: this.input.userDid,
120
+ referrer: this.input.referrer,
122
121
  })
123
122
  );
124
123
 
125
124
  if (errorCount !== 0) {
126
- throw new Error(`Sync from spaces encountered ${errorCount} error`);
125
+ throw new Error(`Sync from spaces encountered ${errorCount} error: ${message}`);
127
126
  }
128
127
  }
129
128
 
130
129
  async import() {
130
+ this.input.event.emit(BlockletEvents.restoreProgress, {
131
+ did: this.input.appDid,
132
+ message: 'Preparing to import data...',
133
+ });
131
134
  await Promise.all(
132
135
  this.storages.map((storage) => {
133
136
  storage.ensureParams(this);
134
137
  return storage.import();
135
138
  })
136
139
  );
140
+ this.input.event.emit(BlockletEvents.restoreProgress, {
141
+ did: this.input.appDid,
142
+ message: 'Importing data successfully...',
143
+ });
137
144
  }
138
145
  }
139
146
 
package/lib/event.js CHANGED
@@ -81,7 +81,7 @@ module.exports = ({
81
81
  }
82
82
  };
83
83
 
84
- const handleBlockletAdd = async (name, { blocklet, context }) => {
84
+ const handleBlockletInstall = async (name, { blocklet, context }) => {
85
85
  try {
86
86
  const changed = await ensureBlockletRouting(blocklet, context);
87
87
  if (changed) {
@@ -169,11 +169,17 @@ module.exports = ({
169
169
  });
170
170
  };
171
171
 
172
+ /**
173
+ *
174
+ * @description 事件必须注册在这里才能被发布出去
175
+ * @param {string} eventName
176
+ * @param {any} payload
177
+ */
172
178
  const handleBlockletEvent = async (eventName, payload) => {
173
179
  const blocklet = payload.blocklet || payload;
174
180
 
175
181
  if ([BlockletEvents.installed].includes(eventName)) {
176
- await handleBlockletAdd(eventName, payload);
182
+ await handleBlockletInstall(eventName, payload);
177
183
 
178
184
  try {
179
185
  await node.createAuditLog({
@@ -273,6 +279,13 @@ module.exports = ({
273
279
  }
274
280
  };
275
281
 
282
+ /**
283
+ *
284
+ *
285
+ * @param {*} subject
286
+ * @param {string} event
287
+ * @param {(event: string, data: any) => Promise<void> | void} handler
288
+ */
276
289
  const listen = (subject, event, handler) => subject.on(event, (data) => handler(event, data));
277
290
 
278
291
  [
@@ -291,6 +304,9 @@ module.exports = ({
291
304
  BlockletEvents.startFailed,
292
305
  BlockletEvents.stopped,
293
306
  BlockletEvents.appDidChanged,
307
+
308
+ BlockletEvents.backupProgress,
309
+ BlockletEvents.restoreProgress,
294
310
  ].forEach((eventName) => {
295
311
  listen(blockletManager, eventName, handleBlockletEvent);
296
312
  });
package/lib/index.js CHANGED
@@ -26,7 +26,7 @@ const Maintain = require('./util/maintain');
26
26
  const resetNode = require('./util/reset-node');
27
27
  const DiskMonitor = require('./util/disk-monitor');
28
28
  const StoreUtil = require('./util/store');
29
- const createQueue = require('./queue');
29
+ const createQueue = require('./util/queue');
30
30
  const createEvents = require('./event');
31
31
  const pm2Events = require('./blocklet/manager/pm2-events');
32
32
  const { createStateReadyQueue, createStateReadyHandler } = require('./util/ready');
@@ -99,7 +99,25 @@ function ABTNode(options) {
99
99
  concurrency,
100
100
  maxRetries: 3,
101
101
  retryDelay: 5000, // retry after 5 seconds
102
- maxTimeout: 60 * 1000 * 15, // throw timeout error after 5 minutes
102
+ maxTimeout: 60 * 1000 * 15, // throw timeout error after 15 minutes
103
+ id: (job) => (job ? md5(`${job.entity}-${job.action}-${job.id}`) : ''),
104
+ },
105
+ });
106
+
107
+ const backupQueue = createQueue({
108
+ daemon: options.daemon,
109
+ name: 'backup_queue',
110
+ dataDir: dataDirs.core,
111
+ onJob: async (job) => {
112
+ if (typeof blockletManager.onJob === 'function') {
113
+ await blockletManager.onJob(job);
114
+ }
115
+ },
116
+ options: {
117
+ concurrency,
118
+ maxRetries: 3,
119
+ retryDelay: 10000, // retry after 10 seconds
120
+ maxTimeout: 60 * 1000 * 30, // throw timeout error after 30 minutes
103
121
  id: (job) => (job ? md5(`${job.entity}-${job.action}-${job.id}`) : ''),
104
122
  },
105
123
  });
@@ -129,6 +147,7 @@ function ABTNode(options) {
129
147
  dataDirs,
130
148
  startQueue,
131
149
  installQueue,
150
+ backupQueue,
132
151
  daemon: options.daemon,
133
152
  teamManager,
134
153
  });
@@ -192,7 +211,6 @@ function ABTNode(options) {
192
211
 
193
212
  // Blocklet manager
194
213
  installBlocklet: blockletManager.install.bind(blockletManager),
195
- installBlockletFromVc: blockletManager.installBlockletFromVc.bind(blockletManager),
196
214
  installComponent: blockletManager.installComponent.bind(blockletManager),
197
215
  startBlocklet: blockletManager.start.bind(blockletManager),
198
216
  stopBlocklet: blockletManager.stop.bind(blockletManager),
@@ -26,7 +26,6 @@ const {
26
26
  NAME_FOR_WELLKNOWN_SITE,
27
27
  DEFAULT_HTTP_PORT,
28
28
  DEFAULT_HTTPS_PORT,
29
- NODE_MODES,
30
29
  ROUTING_RULE_TYPES,
31
30
  CERTIFICATE_EXPIRES_OFFSET,
32
31
  DEFAULT_SERVICE_PATH,
@@ -56,6 +55,7 @@ const {
56
55
  findInterfacePortByName,
57
56
  getWellknownSitePort,
58
57
  getServerDidDomain,
58
+ isGatewayCacheEnabled,
59
59
  } = require('../util');
60
60
  const { getIpDnsDomainForBlocklet, getDidDomainForBlocklet } = require('../util/get-domain-for-blocklet');
61
61
  const { getFromCache: getAccessibleExternalNodeIp } = require('../util/get-accessible-external-node-ip');
@@ -382,7 +382,7 @@ const ensureBlockletCache = async (sites = [], blocklets) => {
382
382
  const clone = cloneDeep(rule);
383
383
  clone.from.pathPrefix = joinUrl(rule.from.pathPrefix, cachePrefix);
384
384
  clone.to.cacheGroup = 'blockletProxy';
385
- clone.to.target = cachePrefix;
385
+ clone.to.targetPrefix = cachePrefix;
386
386
  clone.dynamic = true; // mark as dynamic to avoid redundant generated rules
387
387
  cacheRules.push(clone);
388
388
  });
@@ -518,7 +518,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
518
518
 
519
519
  const ensureDomainCert = async (domain, url) => {
520
520
  const cert = await certManager.getByDomain(domain);
521
- if (!cert) {
521
+ if (!cert || get(cert, 'meta.validTo') <= Date.now()) {
522
522
  await downloadCert({
523
523
  domain,
524
524
  url,
@@ -1115,7 +1115,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1115
1115
  configDir: path.join(dataDirs.router, providerName),
1116
1116
  httpPort: nodeInfo.routing.httpPort || DEFAULT_HTTP_PORT,
1117
1117
  httpsPort: nodeInfo.routing.httpsPort || DEFAULT_HTTPS_PORT,
1118
- cacheDisabled: nodeInfo.mode === NODE_MODES.DEBUG,
1118
+ cacheEnabled: isGatewayCacheEnabled(nodeInfo),
1119
1119
  }),
1120
1120
  getRoutingParams: async () => {
1121
1121
  try {
@@ -1220,7 +1220,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1220
1220
  configDir: path.join(dataDirs.router, info.routing.provider),
1221
1221
  httpPort: info.routing.httpPort || DEFAULT_HTTP_PORT,
1222
1222
  httpsPort: info.routing.httpsPort || DEFAULT_HTTPS_PORT,
1223
- cacheDisabled: info.mode === NODE_MODES.DEBUG,
1223
+ cacheEnabled: isGatewayCacheEnabled(info),
1224
1224
  });
1225
1225
  await providerInstance.stop();
1226
1226
  logger.info('original router stopped:', { provider: info.routing.provider });
@@ -1408,6 +1408,8 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1408
1408
  deleteRoutingRule,
1409
1409
  addDomainAlias,
1410
1410
  deleteDomainAlias,
1411
+
1412
+ isGatewayCacheEnabled,
1411
1413
  };
1412
1414
  };
1413
1415
 
@@ -11,8 +11,11 @@ const {
11
11
  GATEWAY_REQ_LIMIT,
12
12
  } = require('@abtnode/constant');
13
13
  const { BLOCKLET_UI_INTERFACES, BLOCKLET_MODES } = require('@blocklet/constant');
14
+
14
15
  const logger = require('@abtnode/logger')('@abtnode/core:router');
15
16
 
17
+ const { isGatewayCacheEnabled } = require('../util');
18
+
16
19
  const expandSites = (sites = []) => {
17
20
  const result = [];
18
21
 
@@ -117,6 +120,7 @@ class Router {
117
120
  services,
118
121
  nodeInfo: pick(nodeInfo, ['name', 'version', 'port', 'mode', 'enableWelcomePage', 'routing']),
119
122
  requestLimit,
123
+ cacheEnabled: isGatewayCacheEnabled(nodeInfo),
120
124
  });
121
125
  }
122
126
 
@@ -183,6 +187,10 @@ Router.formatSites = (sites = []) => {
183
187
  result.forEach((site) => {
184
188
  if (Array.isArray(site.rules) && site.rules.length > 0) {
185
189
  const rules = cloneDeep(site.rules);
190
+
191
+ let hasRootPathBlockletRule = false;
192
+ let tmpBlockletRule;
193
+
186
194
  rules.forEach((rule) => {
187
195
  if ([ROUTING_RULE_TYPES.BLOCKLET].includes(rule.to.type) === false) {
188
196
  return;
@@ -197,6 +205,11 @@ Router.formatSites = (sites = []) => {
197
205
  }
198
206
 
199
207
  if (daemonRule) {
208
+ if (rule.from.pathPrefix === '/') {
209
+ hasRootPathBlockletRule = true;
210
+ }
211
+ tmpBlockletRule = rule;
212
+
200
213
  // Serve meta js: both prefix and suffix do not contain trailing slash
201
214
  // NOTICE: 这里隐含了一个约定
202
215
  // 如果安装的 blockletA 和 blockletB 都需要 __blocklet__.js
@@ -244,6 +257,24 @@ Router.formatSites = (sites = []) => {
244
257
  });
245
258
  }
246
259
  });
260
+
261
+ // ensure /__blocklet__.js should be proxy to daemon
262
+ if (daemonRule && !hasRootPathBlockletRule && tmpBlockletRule) {
263
+ site.rules.push({
264
+ from: {
265
+ pathPrefix: '/',
266
+ groupPathPrefix: '/',
267
+ pathSuffix: '/__blocklet__.js',
268
+ },
269
+ to: {
270
+ type: ROUTING_RULE_TYPES.DAEMON,
271
+ port: daemonRule.to.port,
272
+ did: tmpBlockletRule.to.did,
273
+ componentId: tmpBlockletRule.to.did,
274
+ cacheGroup: site.mode === BLOCKLET_MODES.PRODUCTION ? 'blockletJs' : '',
275
+ },
276
+ });
277
+ }
247
278
  }
248
279
  });
249
280
 
@@ -239,9 +239,11 @@ const getLogContent = async (action, args, context, result, info, node) => {
239
239
  case 'deleteRoutingRule':
240
240
  return `deleted routing rule from ${site}`; // prettier-ignore
241
241
  case 'updateGateway': {
242
- let message = args.requestLimit.enabled ? `status: enabled, rate: ${args.requestLimit.rate}` : 'status: disabled';
243
- message = `update gateway. ${message}`;
244
-
242
+ const changes = [
243
+ args.requestLimit.enabled ? `rate limit: enabled, rate: ${args.requestLimit.rate}` : 'rate limit: disabled',
244
+ args.cacheEnabled ? `global cache: enabled, rate: ${args.cacheEnabled}` : 'global cache: disabled',
245
+ ];
246
+ const message = `update gateway: ${changes.join('; ')}`;
245
247
  return message;
246
248
  }
247
249
  case 'createTransferInvitation':