@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
@@ -1,83 +1,227 @@
1
+ /* eslint-disable no-async-promise-executor */
1
2
  /* eslint-disable no-await-in-loop */
2
3
  /* eslint-disable function-paren-newline */
3
4
  /* eslint-disable no-underscore-dangle */
4
5
  const omit = require('lodash/omit');
5
6
  const uniq = require('lodash/uniq');
7
+ const cloneDeep = require('lodash/cloneDeep');
6
8
  const detectPort = require('detect-port');
7
9
  const Lock = require('@abtnode/util/lib/lock');
10
+ const security = require('@abtnode/util/lib/security');
8
11
  const { fixPerson, fixInterfaces } = require('@blocklet/meta/lib/fix');
12
+ const { getDisplayName, forEachBlocklet, forEachBlockletSync, forEachChildSync } = require('@blocklet/meta/lib/util');
9
13
  const {
10
14
  BlockletStatus,
11
15
  BlockletSource,
12
16
  BLOCKLET_MODES,
13
17
  BLOCKLET_DEFAULT_PORT_NAME,
14
18
  BLOCKLET_INTERFACE_TYPE_SERVICE,
15
- } = require('@blocklet/meta/lib/constants');
19
+ } = require('@blocklet/constant');
20
+ const { APP_STRUCT_VERSION } = require('@abtnode/constant');
16
21
 
17
22
  const logger = require('@abtnode/logger')('state-blocklet');
18
23
 
19
24
  const BaseState = require('./base');
20
- const { forEachBlocklet } = require('../util/blocklet');
21
- const { validateBlockletMeta } = require('../util');
25
+ const { checkDuplicateComponents, ensureMeta } = require('../util/blocklet');
26
+ const { validateBlockletMeta } = require('../util/blocklet');
22
27
 
23
28
  const lock = new Lock('blocklet-port-assign-lock');
24
29
 
30
+ const isHex = (str) => /^0x[0-9a-f]+$/i.test(str);
25
31
  const getMaxPort = (ports = {}) => Math.max(Object.values(ports).map(Number));
26
32
 
33
+ // structV1Did is just for migration purpose and should be removed in the future
34
+ const getConditions = (did) => [{ 'meta.did': did }, { appDid: did }, { appPid: did }, { structV1Did: did }];
35
+
27
36
  const getExternalPortsFromMeta = (meta) =>
28
37
  (meta.interfaces || []).map((x) => x.port && x.port.external).filter(Boolean);
29
38
 
39
+ const formatBlocklet = (blocklet, phase, dek) => {
40
+ forEachBlockletSync(blocklet, (b) => {
41
+ if (b.meta) {
42
+ fixPerson(b.meta);
43
+ fixInterfaces(b.meta);
44
+ }
45
+
46
+ if (phase === 'onRead') {
47
+ b.children = b.children || [];
48
+ b.environments = b.environments || [];
49
+ }
50
+
51
+ if (!b.environments || !b.meta || !dek) {
52
+ return;
53
+ }
54
+
55
+ (Array.isArray(b.migratedFrom) ? b.migratedFrom : []).forEach((x) => {
56
+ if (phase === 'onUpdate' && isHex(x.appSk) === true) {
57
+ x.appSk = security.encrypt(x.appSk, b.meta.did, dek);
58
+ }
59
+ if (phase === 'onRead' && isHex(x.appSk) === false) {
60
+ x.appSk = security.decrypt(x.appSk, b.meta.did, dek);
61
+ }
62
+ });
63
+
64
+ ['BLOCKLET_APP_SK', 'BLOCKLET_APP_PSK'].forEach((key) => {
65
+ const env = b.environments.find((x) => x.key === key);
66
+ if (!env) {
67
+ return;
68
+ }
69
+ // salt in blocklet state is different from the salt in blocklet-extra state
70
+ // in blocklet-extra state, salt is app meta did in each component
71
+ // in blocklet state, salt is component meta did in each component
72
+ if (phase === 'onUpdate' && isHex(env.value) === true) {
73
+ env.value = security.encrypt(env.value, b.meta.did, dek);
74
+ }
75
+ if (phase === 'onRead' && isHex(env.value) === false) {
76
+ env.value = security.decrypt(env.value, b.meta.did, dek);
77
+ }
78
+ });
79
+ });
80
+
81
+ return blocklet;
82
+ };
83
+
84
+ const fixChildren = (children) => {
85
+ if (!children) {
86
+ return;
87
+ }
88
+
89
+ children.forEach((child) => {
90
+ child.mode = child.mode || BLOCKLET_MODES.PRODUCTION;
91
+ });
92
+ };
93
+
94
+ // type Application = {
95
+ // appDid: DID,
96
+ // appPid: DID,
97
+ // meta: Meta,
98
+ // source: BlockletSource,
99
+ // deployedFrom: string,
100
+ // mode: BLOCKLET_MODES;
101
+ // status: BlockletStatus,
102
+ // children: Component,
103
+ // ports: Ports;
104
+ // mountPoint: string; // deprecated
105
+ // migratedFrom: Array<{ appSk: string, appDid: DID, at: Date }>,
106
+ // externalSk: boolean,
107
+ // structV1Did: DID, // just for migration purpose and should be removed in the future
108
+ // };
109
+
110
+ // type Component = {
111
+ // mountPoint: string;
112
+ // meta: Meta,
113
+ // bundleSource: ComponentSource,
114
+ // source: BlockletSource,
115
+ // deployedFrom: string,
116
+ // mode: BLOCKLET_MODES;
117
+ // status: BlockletStatus,
118
+ // children: Component,
119
+ // ports: Ports;
120
+ // dependents: Array<{
121
+ // did: DID, // blocklet did
122
+ // required: boolean,
123
+ // version: SemverRange,
124
+ // }>;
125
+ // dependencies: Array<{
126
+ // id: string, // format: <did1/did2/did3>
127
+ // required: boolean,
128
+ // }>;
129
+ // dynamic: boolean; // deprecated
130
+ // };
131
+
132
+ // type Ports = {
133
+ // [name: string]: Number; // name is in meta.interfaces[].port
134
+ // };
135
+
30
136
  class BlockletState extends BaseState {
31
137
  /**
32
138
  * Creates an instance of BlockletState
33
139
  * @param {string} baseDir
34
- * @param {object} options
35
- * @param {string} options.blockletPort - from which port to start new blocklets
140
+ * @param {object} config
141
+ * @param {string} config.blockletPort - from which port to start new blocklets
36
142
  * @memberof BlockletState
37
143
  */
38
- constructor(baseDir, options = {}) {
39
- super(baseDir, { filename: 'blocklet.db', ...options });
144
+ constructor(baseDir, config = {}) {
145
+ super(baseDir, { filename: 'blocklet.db', ...config });
40
146
 
41
- this.defaultPort = options.blockletPort || 5555;
147
+ this.defaultPort = config.blockletPort || 5555;
42
148
  }
43
149
 
44
- getBlocklet(did) {
150
+ getBlocklet(did, { decryptSk = true } = {}) {
45
151
  return new Promise((resolve, reject) => {
46
- this.db.findOne({ 'meta.did': did }, (err, doc) => {
152
+ if (!did) {
153
+ resolve(null);
154
+ }
155
+
156
+ this.findOne({ $or: getConditions(did) }, (err, doc) => {
47
157
  if (err) {
48
158
  return reject(err);
49
159
  }
50
160
 
51
- if (doc) {
52
- // TODO: this only exists for backward compatible
53
- fixPerson(doc.meta);
54
- fixInterfaces(doc.meta);
55
- doc.children = doc.children || [];
161
+ return resolve(doc ? formatBlocklet(doc, 'onRead', decryptSk ? this.config.dek : null) : null);
162
+ });
163
+ });
164
+ }
165
+
166
+ getBlockletMetaDid(did) {
167
+ return new Promise((resolve, reject) => {
168
+ if (!did) {
169
+ resolve(null);
170
+ }
171
+
172
+ this.findOne({ $or: getConditions(did) }, (err, doc) => {
173
+ if (err) {
174
+ return reject(err);
56
175
  }
57
176
 
58
- return resolve(doc);
177
+ return resolve(doc ? doc.meta.did : null);
178
+ });
179
+ });
180
+ }
181
+
182
+ async getBlockletStatus(did) {
183
+ return new Promise((resolve, reject) => {
184
+ if (!did) {
185
+ resolve(null);
186
+ }
187
+
188
+ this.findOne({ $or: getConditions(did) }, { status: 1 }, (err, doc) => {
189
+ if (err) {
190
+ return reject(err);
191
+ }
192
+
193
+ return resolve(doc ? doc.status : null);
194
+ });
195
+ });
196
+ }
197
+
198
+ hasBlocklet(did) {
199
+ return new Promise((resolve, reject) => {
200
+ if (!did) {
201
+ resolve(false);
202
+ }
203
+
204
+ this.count({ $or: getConditions(did) }, (err, count) => {
205
+ if (err) {
206
+ return reject(err);
207
+ }
208
+
209
+ return resolve(!!count);
59
210
  });
60
211
  });
61
212
  }
62
213
 
63
214
  getBlocklets(query = {}, projection) {
64
215
  return new Promise((resolve, reject) => {
65
- this.db
66
- .find(query, projection)
67
- .sort('-createdAt')
68
- .exec((err, docs) => {
216
+ this.cursor(query)
217
+ .projection(projection)
218
+ .sort({ createdAt: -1 })
219
+ .exec((err, docs = []) => {
69
220
  if (err) {
70
221
  return reject(err);
71
222
  }
72
223
 
73
- return resolve(
74
- docs.filter(Boolean).map((doc) => {
75
- // TODO: this only exists for backward compatible
76
- fixPerson(doc.meta);
77
- fixInterfaces(doc.meta);
78
- return doc;
79
- })
80
- );
224
+ return resolve(docs.filter(Boolean).map((doc) => formatBlocklet(doc, 'onRead', this.config.dek)));
81
225
  });
82
226
  });
83
227
  }
@@ -91,32 +235,35 @@ class BlockletState extends BaseState {
91
235
  return reject(new Error(`Try to remove non-existing blocklet ${did}`));
92
236
  }
93
237
 
94
- this.db.remove({ _id: doc._id }, (err) => {
238
+ this.remove({ _id: doc._id }, (err) => {
95
239
  if (err) {
96
240
  return reject(err);
97
241
  }
98
242
 
99
243
  this.emit('remove', doc);
100
- return resolve(doc);
244
+ return resolve(formatBlocklet(doc, 'onRead', this.config.dek));
101
245
  });
102
246
  })
103
247
  );
104
248
  }
105
249
 
106
250
  addBlocklet({
107
- did,
108
251
  meta,
109
252
  source = BlockletSource.registry,
110
253
  status = BlockletStatus.added,
111
- deployedFrom,
254
+ deployedFrom = '',
112
255
  mode = BLOCKLET_MODES.PRODUCTION,
113
- childrenMeta = [],
256
+ children: rawChildren = [],
257
+ appPid = null, // the permanent appDid, which will not change after initial set
258
+ migratedFrom = [], // the complete migrate history
259
+ // whether sk is managed by some party beside server, such as did-wallet
260
+ // externalSk is always true in struct V2 blocklet
261
+ externalSk = true,
114
262
  } = {}) {
115
- return this.getBlocklet(did).then(
116
- (doc) =>
117
- // eslint-disable-next-line no-async-promise-executor
263
+ return this.getBlocklet(meta.did).then(
264
+ (exist) =>
118
265
  new Promise(async (resolve, reject) => {
119
- if (doc) {
266
+ if (exist) {
120
267
  reject(new Error('Blocklet already added'));
121
268
  return;
122
269
  }
@@ -124,39 +271,48 @@ class BlockletState extends BaseState {
124
271
  try {
125
272
  fixPerson(meta);
126
273
  fixInterfaces(meta);
127
- const sanitized = validateBlockletMeta(meta);
274
+ let sanitized = validateBlockletMeta(meta);
275
+ // bundle info
276
+ sanitized = ensureMeta(sanitized);
277
+ sanitized = omit(sanitized, ['htmlAst']);
128
278
 
129
279
  // get ports
130
280
  await lock.acquire();
131
281
 
132
282
  const ports = await this.getBlockletPorts({ interfaces: sanitized.interfaces || [] });
133
283
 
134
- const children = await this.fillChildrenPorts(this.getChildrenFromMetas(childrenMeta), {
284
+ const children = await this.fillChildrenPorts(rawChildren, {
135
285
  defaultPort: getMaxPort(ports),
136
286
  });
137
287
 
288
+ fixChildren(children);
289
+
290
+ const data = {
291
+ appDid: null, // will updated later when updating blocklet environments
292
+ appPid,
293
+ mode,
294
+ meta: sanitized,
295
+ status,
296
+ source,
297
+ deployedFrom,
298
+ ports,
299
+ environments: [],
300
+ children,
301
+ migratedFrom,
302
+ externalSk,
303
+ structVersion: APP_STRUCT_VERSION,
304
+ };
305
+
138
306
  // add to db
139
- this.db.insert(
140
- {
141
- mode,
142
- meta: omit(sanitized, ['htmlAst']),
143
- status,
144
- source,
145
- deployedFrom,
146
- ports,
147
- environments: [],
148
- children,
149
- },
150
- (err, newDoc) => {
151
- lock.release();
152
- if (err) {
153
- return reject(err);
154
- }
155
-
156
- this.emit('add', newDoc);
157
- return resolve(newDoc);
307
+ this.insert(data, (err, doc) => {
308
+ lock.release();
309
+ if (err) {
310
+ return reject(err);
158
311
  }
159
- );
312
+
313
+ this.emit('add', doc);
314
+ return resolve(doc);
315
+ });
160
316
  } catch (err) {
161
317
  lock.release();
162
318
  reject(err);
@@ -165,7 +321,28 @@ class BlockletState extends BaseState {
165
321
  );
166
322
  }
167
323
 
168
- upgradeBlocklet({ meta, source, deployedFrom, children } = {}) {
324
+ // FIXME: 这个接口比较危险,可能会修改一些本不应该修改的字段,后续需要考虑改进
325
+ updateBlocklet(did, updates) {
326
+ return this.getBlocklet(did).then(
327
+ (doc) =>
328
+ new Promise(async (resolve, reject) => {
329
+ if (!doc) {
330
+ reject(new Error('Blocklet does not exist'));
331
+ return;
332
+ }
333
+
334
+ try {
335
+ const formatted = formatBlocklet(cloneDeep(updates), 'onUpdate', this.config.dek);
336
+ const newDoc = await this.updateById(doc._id, { $set: formatted });
337
+ resolve(newDoc);
338
+ } catch (err) {
339
+ reject(err);
340
+ }
341
+ })
342
+ );
343
+ }
344
+
345
+ upgradeBlocklet({ meta, source, deployedFrom = '', children } = {}) {
169
346
  return this.getBlocklet(meta.did).then(
170
347
  (doc) =>
171
348
  // eslint-disable-next-line no-async-promise-executor
@@ -195,17 +372,18 @@ class BlockletState extends BaseState {
195
372
  logger.info('Fill children ports when when upgrading blocklet', { name: doc.meta.name, did: doc.meta.did });
196
373
  await this.fillChildrenPorts(children, { oldChildren: doc.children, defaultPort: getMaxPort(ports) });
197
374
 
375
+ fixChildren(children);
376
+
198
377
  // add to db
199
- const newDoc = await this.updateById(doc._id, {
200
- $set: {
201
- meta: omit(sanitized, ['htmlAst']),
202
- source,
203
- deployedFrom,
204
- children,
205
- ports,
206
- },
378
+ const newDoc = await this.updateBlocklet(meta.did, {
379
+ meta: omit(sanitized, ['htmlAst']),
380
+ source,
381
+ deployedFrom,
382
+ children,
383
+ ports,
207
384
  });
208
385
  lock.release();
386
+
209
387
  this.emit('upgrade', newDoc);
210
388
  resolve(newDoc);
211
389
  } catch (err) {
@@ -224,7 +402,7 @@ class BlockletState extends BaseState {
224
402
  defaultPort = 0,
225
403
  } = {}) {
226
404
  try {
227
- const blocklets = await this.getBlocklets({}, { port: 1, ports: 1, 'meta.interfaces': 1 });
405
+ const blocklets = await this.getBlocklets({}, { port: 1, ports: 1, 'meta.interfaces': 1, children: 1 });
228
406
 
229
407
  const occupiedExternalPorts = new Map();
230
408
  const occupiedInternalPorts = new Map();
@@ -331,31 +509,43 @@ class BlockletState extends BaseState {
331
509
  * @return {Object} { <did> : { interfaceName } }
332
510
  */
333
511
  async groupAllInterfaces() {
334
- const blocklets = await this.getBlocklets({}, { meta: 1 });
512
+ const blocklets = await this.getBlocklets({}, { meta: 1, children: 1 });
335
513
  const result = {};
336
- blocklets.forEach((blocklet) => {
337
- const { did, interfaces } = blocklet.meta;
338
- if (typeof result[did] === 'undefined') {
339
- result[did] = {};
514
+ const fillResult = (component, { id }) => {
515
+ const { interfaces } = component.meta;
516
+ if (typeof result[id] === 'undefined') {
517
+ result[id] = {};
340
518
  }
341
519
 
342
520
  (interfaces || []).forEach((x) => {
343
- result[did][x.name] = x;
521
+ result[id][x.name] = x;
344
522
  });
523
+ };
524
+
525
+ blocklets.forEach((blocklet) => {
526
+ forEachBlockletSync(blocklet, fillResult);
345
527
  });
346
528
 
347
529
  return result;
348
530
  }
349
531
 
350
- async setBlockletStatus(did, status) {
532
+ /**
533
+ * @param {String} did blocklet did
534
+ * @param {BlockletStatus} status blocklet status
535
+ *
536
+ * children status only different with parent before blocklet installation
537
+ * @param {Array<componentId>} children
538
+ */
539
+ async setBlockletStatus(did, status, { children } = {}) {
351
540
  if (typeof status === 'undefined') {
352
541
  throw new Error('Unsupported blocklet status');
353
542
  }
354
- const record = await this.getBlocklet(did);
355
- // when status change update record
356
- if (record.status === status) {
357
- return record;
543
+
544
+ const doc = await this.getBlocklet(did);
545
+ if (doc.status === status && !children) {
546
+ return formatBlocklet(doc, 'onRead', this.config.dek);
358
547
  }
548
+
359
549
  const updates = { status, startedAt: undefined, stoppedAt: undefined };
360
550
  if (status === BlockletStatus.running) {
361
551
  updates.startedAt = new Date();
@@ -367,30 +557,46 @@ class BlockletState extends BaseState {
367
557
  updates.stoppedAt = new Date();
368
558
  }
369
559
 
370
- const res = await this.update(record._id, {
371
- $set: updates,
560
+ // update children status
561
+ forEachChildSync(doc, (child, { id }) => {
562
+ if (children === 'all') {
563
+ child.status = status;
564
+ return;
565
+ }
566
+
567
+ if (!children) {
568
+ if (
569
+ ![
570
+ BlockletStatus.waiting,
571
+ BlockletStatus.upgrading,
572
+ BlockletStatus.installing,
573
+ BlockletStatus.starting,
574
+ ].includes(status)
575
+ ) {
576
+ child.status = status;
577
+ }
578
+
579
+ return;
580
+ }
581
+
582
+ if (children.includes(id)) {
583
+ child.status = status;
584
+ }
372
585
  });
373
- return res;
374
- }
375
586
 
376
- getChildrenFromMetas(childrenMeta) {
377
- return childrenMeta.map((x) => ({ meta: x }));
587
+ updates.children = doc.children;
588
+ return this.updateBlocklet(did, updates);
378
589
  }
379
590
 
380
- async fillChildrenPorts(children, { defaultPort = 0, oldChildren } = {}) {
591
+ async fillChildrenPorts(children, { defaultPort = 0, oldChildren, returnMaxPort } = {}) {
381
592
  let _maxPort = defaultPort;
382
593
  for (const child of children || []) {
383
594
  // generate ports
384
595
  const childMeta = child.meta;
596
+ const oldChild = (oldChildren || []).find((x) => x.meta.did === child.meta.did);
385
597
 
386
598
  // get skipOccupiedCheckPorts
387
- let skipOccupiedCheckPorts = [];
388
- if (Array.isArray(oldChildren)) {
389
- const oldChild = oldChildren.find((x) => x.meta.did === child.meta.did);
390
- if (oldChild) {
391
- skipOccupiedCheckPorts = getExternalPortsFromMeta(oldChild.meta);
392
- }
393
- }
599
+ const skipOccupiedCheckPorts = oldChild ? getExternalPortsFromMeta(oldChild.meta) : [];
394
600
 
395
601
  const ports = await this.getBlockletPorts({
396
602
  interfaces: childMeta.interfaces || [],
@@ -399,22 +605,17 @@ class BlockletState extends BaseState {
399
605
  });
400
606
  _maxPort = getMaxPort(ports);
401
607
 
402
- // fill old child's port to new child
403
- if (Array.isArray(oldChildren)) {
404
- const oldChild = oldChildren.find((x) => x.meta.did === child.meta.did);
405
- if (oldChild && oldChild.ports) {
406
- logger.info('Merge the previous ports to child blocklet', {
407
- did: child.meta.did,
408
- name: child.meta.name,
409
- oldPorts: oldChild.ports,
410
- ports,
411
- });
412
- Object.keys(ports).forEach((p) => {
413
- ports[p] = oldChild.ports[p] || ports[p];
414
- });
415
- child.ports = ports;
416
- continue; // eslint-disable-line
417
- }
608
+ if (oldChild && oldChild.ports) {
609
+ // fill old child's port to new child
610
+ logger.info('Merge the previous ports to child blocklet', {
611
+ did: child.meta.did,
612
+ name: child.meta.name,
613
+ oldPorts: oldChild.ports,
614
+ ports,
615
+ });
616
+ Object.keys(ports).forEach((p) => {
617
+ ports[p] = oldChild.ports[p] || ports[p];
618
+ });
418
619
  }
419
620
 
420
621
  // assign a new port to child
@@ -422,10 +623,70 @@ class BlockletState extends BaseState {
422
623
  child.ports = ports;
423
624
  }
424
625
 
626
+ for (const child of children || []) {
627
+ const oldChild = (oldChildren || []).find((x) => x.meta.did === child.meta.did);
628
+
629
+ _maxPort = await this.fillChildrenPorts(child.children || [], {
630
+ defaultPort: _maxPort,
631
+ oldChildren: oldChild?.children,
632
+ returnMaxPort: true,
633
+ });
634
+ }
635
+
636
+ if (returnMaxPort) {
637
+ return _maxPort;
638
+ }
639
+
425
640
  return children;
426
641
  }
642
+
643
+ async addChildren(did, children) {
644
+ const parent = await this.getBlocklet(did);
645
+ if (!parent) {
646
+ throw new Error('Blocklet does not exist');
647
+ }
648
+
649
+ const oldChildren = parent.children || [];
650
+
651
+ const newChildren = [...oldChildren];
652
+ for (const child of children) {
653
+ const { meta, mountPoint, bundleSource = null, source = '', deployedFrom = '', mode } = child;
654
+
655
+ if (!mountPoint) {
656
+ throw new Error(`mountPoint is required when adding component ${getDisplayName(child, true)}`);
657
+ }
658
+
659
+ checkDuplicateComponents([child, ...newChildren]);
660
+
661
+ newChildren.push({
662
+ mountPoint,
663
+ meta,
664
+ bundleSource,
665
+ source,
666
+ deployedFrom,
667
+ mode,
668
+ status: BlockletStatus.added,
669
+ children: child.children,
670
+ });
671
+
672
+ fixChildren(newChildren);
673
+ }
674
+
675
+ // use upgradeBlocklet to assign ports to children and write new data to db
676
+ return this.upgradeBlocklet({
677
+ meta: parent.meta,
678
+ source: parent.source,
679
+ deployedFrom: parent.deployedFrom,
680
+ children: newChildren,
681
+ });
682
+ }
683
+
684
+ async updateStructV1Did(did, v1Did) {
685
+ return this.updateBlocklet(did, { structV1Did: v1Did });
686
+ }
427
687
  }
428
688
 
429
689
  BlockletState.BlockletStatus = BlockletStatus;
690
+ BlockletState.formatBlocklet = formatBlocklet;
430
691
 
431
692
  module.exports = BlockletState;
@@ -5,13 +5,15 @@ const BaseState = require('./base');
5
5
  * This db is used to save arbitrary cached data.
6
6
  */
7
7
  class CacheState extends BaseState {
8
- constructor(baseDir, options = {}) {
9
- super(baseDir, { filename: 'cache.db', ...options });
8
+ constructor(baseDir, config = {}) {
9
+ super(baseDir, { filename: 'cache.db', ...config });
10
10
 
11
- this.db.ensureIndex({ fieldName: 'key', unique: true }, (error) => {
12
- if (error) {
13
- logger.error('ensure index failed', { error });
14
- }
11
+ this.onReady(() => {
12
+ this.ensureIndex({ fieldName: 'key', unique: true }, (error) => {
13
+ if (error) {
14
+ logger.error('ensure index failed', { error });
15
+ }
16
+ });
15
17
  });
16
18
  }
17
19