@abtnode/core 1.8.68 → 1.8.69-beta-54faead3

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.
@@ -0,0 +1,49 @@
1
+ const { removeSync, outputJsonSync, readJSONSync } = require('fs-extra');
2
+ const { join } = require('path');
3
+ const security = require('@abtnode/util/lib/security');
4
+ const { BaseRestore } = require('./base');
5
+
6
+ class BlockletRestore extends BaseRestore {
7
+ filename = 'blocklet.json';
8
+
9
+ async import(params) {
10
+ const blocklet = this.decrypt(this.getBlocklet(), params);
11
+ removeSync(join(this.restoreDir, this.filename));
12
+ outputJsonSync(join(this.restoreDir, this.filename), blocklet);
13
+ }
14
+
15
+ /**
16
+ *
17
+ * @description
18
+ * @return {import('@abtnode/client').BlockletState}
19
+ * @memberof BlockletRestore
20
+ */
21
+ getBlocklet() {
22
+ return readJSONSync(join(this.restoreDir, this.filename));
23
+ }
24
+
25
+ /**
26
+ *
27
+ * @description 解密加密的数据
28
+ * @param {import('@abtnode/client').BlockletState} blocklet
29
+ * @return {import('@abtnode/client').BlockletState}
30
+ * @memberof BlockletRestore
31
+ */
32
+ decrypt(blocklet, params) {
33
+ const { password } = this.input;
34
+ if (Array.isArray(blocklet.migratedFrom)) {
35
+ blocklet.migratedFrom = blocklet.migratedFrom.map((x) => {
36
+ x.appSk = security.decrypt(x.appSk, params.salt, password);
37
+ return x;
38
+ });
39
+ }
40
+
41
+ return blocklet;
42
+ }
43
+
44
+ getImportParams() {
45
+ return { salt: this.getBlocklet().appDid };
46
+ }
47
+ }
48
+
49
+ module.exports = { BlockletRestore };
@@ -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');
@@ -8,10 +8,13 @@ class BlockletsRestore extends BaseRestore {
8
8
  filename = 'blocklets';
9
9
 
10
10
  async import() {
11
- const blockletsDir = join(this.blockletRestoreDir, this.filename);
11
+ const blockletsDir = join(this.restoreDir, 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', {
@@ -7,13 +7,13 @@ class LogsRestore extends BaseRestore {
7
7
  filename = 'logs.zip';
8
8
 
9
9
  async import() {
10
- const blockletZipPath = join(this.blockletRestoreDir, this.filename);
10
+ const blockletZipPath = join(this.restoreDir, this.filename);
11
11
 
12
12
  if (!existsSync(blockletZipPath)) {
13
13
  throw new Error(`file not found: ${blockletZipPath}`);
14
14
  }
15
15
 
16
- await zipToDir(blockletZipPath, join(this.blockletRestoreDir, 'logs'));
16
+ await zipToDir(blockletZipPath, join(this.restoreDir, 'logs'));
17
17
  removeSync(blockletZipPath);
18
18
  }
19
19
  }
@@ -1,17 +1,27 @@
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 merge = require('lodash/merge');
16
+ const { BlockletEvents } = require('@blocklet/constant');
17
+ const { SpaceClient, RestoreBlockletCommand } = require('@did-space/client');
18
+ const { ensureDirSync, existsSync, rmdirSync } = require('fs-extra');
19
+ const { join, basename } = require('path');
20
+
21
+ const logger = require('@abtnode/logger')('@abtnode/core:storage:restore');
22
+
14
23
  const { BlockletExtrasRestore } = require('./blocklet-extras');
24
+ const { BlockletRestore } = require('./blocklet');
15
25
  const { BlockletsRestore } = require('./blocklets');
16
26
 
17
27
  class SpacesRestore {
@@ -27,7 +37,7 @@ class SpacesRestore {
27
37
  * @type {string}
28
38
  * @memberof SpacesRestore
29
39
  */
30
- blockletRestoreDir;
40
+ restoreDir;
31
41
 
32
42
  /**
33
43
  *
@@ -35,15 +45,7 @@ class SpacesRestore {
35
45
  * @type {string}
36
46
  * @memberof SpacesRestore
37
47
  */
38
- serverDataDir;
39
-
40
- /**
41
- *
42
- * @description spaces 的 endpoint
43
- * @type {import('@ocap/wallet').WalletObject}
44
- * @memberof SpacesRestore
45
- */
46
- blockletWallet;
48
+ serverDir;
47
49
 
48
50
  storages;
49
51
 
@@ -55,7 +57,11 @@ class SpacesRestore {
55
57
  constructor(input) {
56
58
  this.verify(input);
57
59
  this.input = input;
58
- this.storages = [new BlockletExtrasRestore(this.input), new BlockletsRestore(this.input)];
60
+ this.storages = [
61
+ new BlockletExtrasRestore(this.input),
62
+ new BlockletRestore(this.input),
63
+ new BlockletsRestore(this.input),
64
+ ];
59
65
  }
60
66
 
61
67
  /**
@@ -68,72 +74,80 @@ class SpacesRestore {
68
74
  if (!validUrl.isWebUri(input.endpoint)) {
69
75
  throw new Error(`endpoint(${input.endpoint}) must be a WebUri`);
70
76
  }
77
+ if (!input.endpoint.includes(input.appDid)) {
78
+ throw new Error(`endpoint and blocklet.appDid(${input.appDid}) do not match`);
79
+ }
71
80
  }
72
81
 
73
82
  async initialize() {
74
- this.serverDataDir = process.env.ABT_NODE_DATA_DIR;
75
- this.blockletWallet = await this.getBlockletWallet();
76
-
77
- if (!this.input.endpoint.includes(this.blockletWallet.address)) {
78
- throw new Error(`endpoint and blocklet.appDid(${this.blockletWallet.address}) do not match`);
83
+ this.serverDir = process.env.ABT_NODE_DATA_DIR;
84
+ this.restoreDir = join(this.serverDir, 'tmp/restore', this.input.appDid);
85
+ if (existsSync(this.restoreDir)) {
86
+ rmdirSync(this.restoreDir, { recursive: true });
79
87
  }
88
+ ensureDirSync(this.restoreDir);
80
89
 
81
- this.blockletRestoreDir = join(process.env.ABT_NODE_DATA_DIR, 'tmp/restore', this.blockletWallet.address);
82
- if (existsSync(this.blockletRestoreDir)) {
83
- rmdirSync(this.blockletRestoreDir, { recursive: true });
84
- }
85
- ensureDirSync(this.blockletRestoreDir);
90
+ this.storages.map((x) => x.ensureParams(this));
86
91
  }
87
92
 
88
- /**
89
- *
90
- * @returns {Promise<void>}
91
- * @memberof SpacesRestore
92
- */
93
93
  async restore() {
94
94
  await this.initialize();
95
95
  await this.syncFromSpaces();
96
- await this.import();
97
- }
98
96
 
99
- async getBlockletWallet() {
100
- // @FIXME: blocklet 钱包类型如何得知呢?
101
- const wallet = fromSecretKey(this.input.blockletSecretKey, WalletType({ role: types.RoleType.ROLE_APPLICATION }));
97
+ const params = await Promise.all(this.storages.map((x) => x.getImportParams()));
98
+ await this.import(merge(...params));
102
99
 
103
- return wallet;
100
+ return this.storages.map((x) => x.getInstallParams());
104
101
  }
105
102
 
106
103
  async syncFromSpaces() {
107
- const { endpoint } = this.input;
108
- const wallet = await this.getBlockletWallet();
104
+ const { endpoint, wallet, delegation } = this.input;
109
105
 
110
106
  const spaceClient = new SpaceClient({
111
107
  endpoint,
108
+ delegation,
112
109
  wallet,
113
110
  });
114
111
 
115
- const { errorCount } = await spaceClient.send(
116
- new SyncFolderPullCommand({
117
- source: join('.did-objects', this.blockletWallet.address, '/'),
118
- target: this.blockletRestoreDir,
112
+ const { errorCount, message } = await spaceClient.send(
113
+ new RestoreBlockletCommand({
114
+ appDid: this.input.appDid,
115
+ target: join(this.restoreDir, '/'),
119
116
  debug: true,
120
- concurrency: 64,
121
- retryCount: 100,
117
+ concurrency: 32,
118
+ retryCount: 10,
119
+ onProgress: (data) => {
120
+ logger.info('restore progress', { appDid: this.input.appDid, data });
121
+ this.input.event.emit(BlockletEvents.restoreProgress, {
122
+ appDid: this.input.appDid,
123
+ message: `Downloaded file ${basename(data.key)} (${data.completed}/${data.total})`,
124
+ });
125
+ },
126
+
127
+ userDid: this.input.userDid,
128
+ referrer: this.input.referrer,
122
129
  })
123
130
  );
124
131
 
125
132
  if (errorCount !== 0) {
126
- throw new Error(`Sync from spaces encountered ${errorCount} error`);
133
+ throw new Error(`Sync from spaces encountered error: ${message}`);
127
134
  }
128
135
  }
129
136
 
130
- async import() {
137
+ async import(params) {
138
+ this.input.event.emit(BlockletEvents.restoreProgress, {
139
+ appDid: this.input.appDid,
140
+ message: 'Preparing to import data...',
141
+ });
131
142
  await Promise.all(
132
143
  this.storages.map((storage) => {
133
- storage.ensureParams(this);
134
- return storage.import();
144
+ return storage.import(params);
135
145
  })
136
146
  );
147
+ this.input.event.emit(BlockletEvents.restoreProgress, {
148
+ appDid: this.input.appDid,
149
+ message: 'Importing data successfully...',
150
+ });
137
151
  }
138
152
  }
139
153
 
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({
@@ -236,6 +242,14 @@ module.exports = ({
236
242
  );
237
243
 
238
244
  logger.info('take snapshot after updated blocklet app', { event: eventName, did: blocklet.meta.did, hash });
245
+ } else if (BlockletEvents.spaceConnected === eventName) {
246
+ nodeState
247
+ .read()
248
+ .then((info) => handleRouting(info))
249
+ .catch((err) => {
250
+ logger.error('Reload gateway failed on blocklet.connectedSpace', { error: err });
251
+ });
252
+ logger.info('Reload gateway after blocklet connected to space', { event: eventName, did: blocklet.appDid });
239
253
  }
240
254
 
241
255
  if (
@@ -273,6 +287,13 @@ module.exports = ({
273
287
  }
274
288
  };
275
289
 
290
+ /**
291
+ *
292
+ *
293
+ * @param {*} subject
294
+ * @param {string} event
295
+ * @param {(event: string, data: any) => Promise<void> | void} handler
296
+ */
276
297
  const listen = (subject, event, handler) => subject.on(event, (data) => handler(event, data));
277
298
 
278
299
  [
@@ -291,6 +312,11 @@ module.exports = ({
291
312
  BlockletEvents.startFailed,
292
313
  BlockletEvents.stopped,
293
314
  BlockletEvents.appDidChanged,
315
+
316
+ BlockletEvents.backupProgress,
317
+ BlockletEvents.restoreProgress,
318
+
319
+ BlockletEvents.spaceConnected,
294
320
  ].forEach((eventName) => {
295
321
  listen(blockletManager, eventName, handleBlockletEvent);
296
322
  });
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),
@@ -3,6 +3,7 @@
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');
8
9
  const isEqual = require('lodash/isEqual');
@@ -26,7 +27,6 @@ const {
26
27
  NAME_FOR_WELLKNOWN_SITE,
27
28
  DEFAULT_HTTP_PORT,
28
29
  DEFAULT_HTTPS_PORT,
29
- NODE_MODES,
30
30
  ROUTING_RULE_TYPES,
31
31
  CERTIFICATE_EXPIRES_OFFSET,
32
32
  DEFAULT_SERVICE_PATH,
@@ -44,6 +44,7 @@ const {
44
44
  BLOCKLET_INTERFACE_PUBLIC,
45
45
  BLOCKLET_INTERFACE_WELLKNOWN,
46
46
  BLOCKLET_INTERFACE_TYPE_WELLKNOWN,
47
+ BLOCKLET_CONFIGURABLE_KEY,
47
48
  BlockletEvents,
48
49
  BLOCKLET_MODES,
49
50
  } = require('@blocklet/constant');
@@ -56,6 +57,7 @@ const {
56
57
  findInterfacePortByName,
57
58
  getWellknownSitePort,
58
59
  getServerDidDomain,
60
+ isGatewayCacheEnabled,
59
61
  } = require('../util');
60
62
  const { getIpDnsDomainForBlocklet, getDidDomainForBlocklet } = require('../util/get-domain-for-blocklet');
61
63
  const { getFromCache: getAccessibleExternalNodeIp } = require('../util/get-accessible-external-node-ip');
@@ -122,7 +124,7 @@ const addCorsToSite = (site, rawUrl) => {
122
124
  site.corsAllowedOrigins.push(url.hostname);
123
125
  }
124
126
  } catch (err) {
125
- // Do nothing
127
+ console.error('addCorsToSite', err);
126
128
  }
127
129
  };
128
130
 
@@ -336,6 +338,22 @@ const ensureCorsForWebWallet = async (sites) => {
336
338
  return sites;
337
339
  };
338
340
 
341
+ const ensureCorsForDidSpace = async (sites = [], blocklets) => {
342
+ return sites.map((site) => {
343
+ const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
344
+ if (blocklet) {
345
+ const endpoint = blocklet.environments.find(
346
+ (x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT
347
+ );
348
+ if (endpoint && isUrl(endpoint.value)) {
349
+ addCorsToSite(site, endpoint.value);
350
+ }
351
+ }
352
+
353
+ return site;
354
+ });
355
+ };
356
+
339
357
  const filterSitesForRemovedBlocklets = async (sites = [], blocklets) => {
340
358
  return sites.filter((site) => {
341
359
  if (!site.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
@@ -382,7 +400,7 @@ const ensureBlockletCache = async (sites = [], blocklets) => {
382
400
  const clone = cloneDeep(rule);
383
401
  clone.from.pathPrefix = joinUrl(rule.from.pathPrefix, cachePrefix);
384
402
  clone.to.cacheGroup = 'blockletProxy';
385
- clone.to.target = cachePrefix;
403
+ clone.to.targetPrefix = cachePrefix;
386
404
  clone.dynamic = true; // mark as dynamic to avoid redundant generated rules
387
405
  cacheRules.push(clone);
388
406
  });
@@ -407,6 +425,7 @@ const ensureLatestInfo = async (sites = [], { withDefaultCors = true } = {}) =>
407
425
  result = await ensureBlockletCache(result, blocklets);
408
426
  result = await ensureWellknownRule(result);
409
427
  result = await ensureCorsForWebWallet(result);
428
+ result = await ensureCorsForDidSpace(result, blocklets);
410
429
  result = await ensureLatestInterfaceInfo(result);
411
430
 
412
431
  return result;
@@ -518,7 +537,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
518
537
 
519
538
  const ensureDomainCert = async (domain, url) => {
520
539
  const cert = await certManager.getByDomain(domain);
521
- if (!cert) {
540
+ if (!cert || get(cert, 'meta.validTo') <= Date.now()) {
522
541
  await downloadCert({
523
542
  domain,
524
543
  url,
@@ -1115,7 +1134,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1115
1134
  configDir: path.join(dataDirs.router, providerName),
1116
1135
  httpPort: nodeInfo.routing.httpPort || DEFAULT_HTTP_PORT,
1117
1136
  httpsPort: nodeInfo.routing.httpsPort || DEFAULT_HTTPS_PORT,
1118
- cacheDisabled: nodeInfo.mode === NODE_MODES.DEBUG,
1137
+ cacheEnabled: isGatewayCacheEnabled(nodeInfo),
1119
1138
  }),
1120
1139
  getRoutingParams: async () => {
1121
1140
  try {
@@ -1220,7 +1239,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1220
1239
  configDir: path.join(dataDirs.router, info.routing.provider),
1221
1240
  httpPort: info.routing.httpPort || DEFAULT_HTTP_PORT,
1222
1241
  httpsPort: info.routing.httpsPort || DEFAULT_HTTPS_PORT,
1223
- cacheDisabled: info.mode === NODE_MODES.DEBUG,
1242
+ cacheEnabled: isGatewayCacheEnabled(info),
1224
1243
  });
1225
1244
  await providerInstance.stop();
1226
1245
  logger.info('original router stopped:', { provider: info.routing.provider });
@@ -1408,6 +1427,8 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1408
1427
  deleteRoutingRule,
1409
1428
  addDomainAlias,
1410
1429
  deleteDomainAlias,
1430
+
1431
+ isGatewayCacheEnabled,
1411
1432
  };
1412
1433
  };
1413
1434
 
@@ -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':
@@ -28,6 +28,7 @@ const lock = new Lock('blocklet-port-assign-lock');
28
28
 
29
29
  const isHex = (str) => /^0x[0-9a-f]+$/i.test(str);
30
30
  const getMaxPort = (ports = {}) => Math.max(Object.values(ports).map(Number));
31
+ const getConditions = (did) => [{ 'meta.did': did }, { appDid: did }, { appPid: did }];
31
32
 
32
33
  const getExternalPortsFromMeta = (meta) =>
33
34
  (meta.interfaces || []).map((x) => x.port && x.port.external).filter(Boolean);
@@ -48,7 +49,16 @@ const formatBlocklet = (blocklet, phase, dek) => {
48
49
  return;
49
50
  }
50
51
 
51
- ['BLOCKLET_APP_SK'].forEach((key) => {
52
+ (Array.isArray(b.migratedFrom) ? b.migratedFrom : []).forEach((x) => {
53
+ if (phase === 'onUpdate' && isHex(x.appSk) === true) {
54
+ x.appSk = security.encrypt(x.appSk, b.meta.did, dek);
55
+ }
56
+ if (phase === 'onRead' && isHex(x.appSk) === false) {
57
+ x.appSk = security.decrypt(x.appSk, b.meta.did, dek);
58
+ }
59
+ });
60
+
61
+ ['BLOCKLET_APP_SK', 'BLOCKLET_APP_PSK'].forEach((key) => {
52
62
  const env = b.environments.find((x) => x.key === key);
53
63
  if (!env) {
54
64
  return;
@@ -98,7 +108,7 @@ class BlockletState extends BaseState {
98
108
  resolve(null);
99
109
  }
100
110
 
101
- this.findOne({ $or: [{ 'meta.did': did }, { appDid: did }] }, (err, doc) => {
111
+ this.findOne({ $or: getConditions(did) }, (err, doc) => {
102
112
  if (err) {
103
113
  return reject(err);
104
114
  }
@@ -114,7 +124,7 @@ class BlockletState extends BaseState {
114
124
  resolve(null);
115
125
  }
116
126
 
117
- this.findOne({ $or: [{ 'meta.did': did }, { appDid: did }] }, (err, doc) => {
127
+ this.findOne({ $or: getConditions(did) }, (err, doc) => {
118
128
  if (err) {
119
129
  return reject(err);
120
130
  }
@@ -130,7 +140,7 @@ class BlockletState extends BaseState {
130
140
  resolve(null);
131
141
  }
132
142
 
133
- this.findOne({ $or: [{ 'meta.did': did }, { appDid: did }] }, { status: 1 }, (err, doc) => {
143
+ this.findOne({ $or: getConditions(did) }, { status: 1 }, (err, doc) => {
134
144
  if (err) {
135
145
  return reject(err);
136
146
  }
@@ -146,7 +156,7 @@ class BlockletState extends BaseState {
146
156
  resolve(false);
147
157
  }
148
158
 
149
- this.count({ $or: [{ 'meta.did': did }, { appDid: did }] }, (err, count) => {
159
+ this.count({ $or: getConditions(did) }, (err, count) => {
150
160
  if (err) {
151
161
  return reject(err);
152
162
  }
@@ -199,6 +209,9 @@ class BlockletState extends BaseState {
199
209
  deployedFrom = '',
200
210
  mode = BLOCKLET_MODES.PRODUCTION,
201
211
  children: rawChildren = [],
212
+ appPid = null, // the permanent appDid, which will not change after initial set
213
+ migratedFrom = [], // the complete migrate history
214
+ externalSk = false, // whether sk is managed by some party beside server, such as did-wallet
202
215
  } = {}) {
203
216
  return this.getBlocklet(meta.did).then(
204
217
  (exist) =>
@@ -229,6 +242,7 @@ class BlockletState extends BaseState {
229
242
 
230
243
  const data = {
231
244
  appDid: null, // will updated later when updating blocklet environments
245
+ appPid,
232
246
  mode,
233
247
  meta: sanitized,
234
248
  status,
@@ -237,6 +251,8 @@ class BlockletState extends BaseState {
237
251
  ports,
238
252
  environments: [],
239
253
  children,
254
+ migratedFrom,
255
+ externalSk,
240
256
  };
241
257
 
242
258
  // add to db