@abtnode/core 1.8.0 → 1.8.3

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.
package/lib/api/node.js CHANGED
@@ -7,6 +7,7 @@ const isGitpod = require('@abtnode/util/lib/is-gitpod');
7
7
  const getFolderSize = require('@abtnode/util/lib/get-folder-size');
8
8
  const canPackageReadWrite = require('@abtnode/util/lib/can-pkg-rw');
9
9
  const { toDelegateAddress } = require('@arcblock/did-util');
10
+ const { STORE_DETAIL_PAGE_PATH_PREFIX } = require('@abtnode/constant');
10
11
 
11
12
  const logger = require('@abtnode/logger')('@abtnode/core:api:node');
12
13
 
@@ -40,7 +41,19 @@ class NodeAPI {
40
41
  // eslint-disable-next-line no-unused-vars
41
42
  async addRegistry({ url }, context) {
42
43
  logger.info('add registry', { url });
43
- const sanitized = sanitizeUrl(url);
44
+
45
+ const urlObj = new URL(url);
46
+ let newUrl = urlObj.origin;
47
+
48
+ // if the pathname is store blocklet list or blocklet detail
49
+ if (urlObj.pathname?.includes(STORE_DETAIL_PAGE_PATH_PREFIX)) {
50
+ const lastIndex = urlObj.pathname.lastIndexOf(STORE_DETAIL_PAGE_PATH_PREFIX);
51
+ const pathnamePrefix = urlObj.pathname.substring(0, lastIndex);
52
+ newUrl = `${newUrl}${pathnamePrefix || ''}`;
53
+ }
54
+
55
+ const sanitized = sanitizeUrl(newUrl);
56
+
44
57
  const info = await this.state.read();
45
58
  const exist = info.blockletRegistryList.find((x) => x.url === sanitized);
46
59
  if (exist) {
@@ -22,7 +22,7 @@ const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager');
22
22
  const downloadFile = require('@abtnode/util/lib/download-file');
23
23
  const Lock = require('@abtnode/util/lib/lock');
24
24
  const { getVcFromPresentation } = require('@abtnode/util/lib/vc');
25
- const { VC_TYPE_BLOCKLET_PURCHASE } = require('@abtnode/constant');
25
+ const { VC_TYPE_BLOCKLET_PURCHASE, WHO_CAN_ACCESS } = require('@abtnode/constant');
26
26
 
27
27
  const getBlockletEngine = require('@blocklet/meta/lib/engine');
28
28
  const {
@@ -115,6 +115,7 @@ const { getFactoryState } = require('../../util/chain');
115
115
  const runMigrationScripts = require('../migration');
116
116
  const hooks = require('../hooks');
117
117
  const { formatName } = require('../../util/get-domain-for-blocklet');
118
+ const handleInstanceInStore = require('../../util/public-to-store');
118
119
 
119
120
  const {
120
121
  isInProgress,
@@ -216,6 +217,7 @@ class BlockletManager extends BaseBlockletManager {
216
217
  * @param {{
217
218
  * url: string;
218
219
  * sync: boolean = false;
220
+ * downloadToken: string;
219
221
  * }} params
220
222
  * @param {{
221
223
  * [key: string]: any
@@ -223,9 +225,24 @@ class BlockletManager extends BaseBlockletManager {
223
225
  * @return {*}
224
226
  * @memberof BlockletManager
225
227
  */
226
- async install(params, context) {
228
+ async install(params, context = {}) {
227
229
  logger.debug('install blocklet', { params, context });
228
230
 
231
+ if (params.downloadToken) {
232
+ const info = await states.node.read();
233
+
234
+ context.headers = Object.assign(context?.headers || {}, {
235
+ 'x-server-did': info.did,
236
+ 'x-download-token': params.downloadToken,
237
+ // FIXME: 先保证兼容性,后续删除
238
+ 'x-server-publick-key': info.pk,
239
+ 'x-server-public-key': info.pk,
240
+ 'x-server-signature': sign(info.did, info.sk, {
241
+ exp: (Date.now() + 5 * 60 * 1000) / 1000,
242
+ }),
243
+ });
244
+ }
245
+
229
246
  const source = getSourceFromInstallParams(params);
230
247
  if (typeof context.startImmediately === 'undefined') {
231
248
  context.startImmediately = !!params.startImmediately;
@@ -319,10 +336,12 @@ class BlockletManager extends BaseBlockletManager {
319
336
  }
320
337
  }
321
338
 
339
+ const { inStore, registryUrl } = await parseSourceUrl(url);
340
+
322
341
  const blocklet = await states.blocklet.getBlocklet(meta.did);
323
342
  const isRunning = blocklet ? blocklet.status === BlockletStatus.running : false;
324
343
 
325
- return { meta, isFree, isInstalled: !!blocklet, isRunning };
344
+ return { meta, isFree, isInstalled: !!blocklet, isRunning, inStore, registryUrl };
326
345
  }
327
346
 
328
347
  async installBlockletFromVc({ vcPresentation, challenge }, context) {
@@ -423,7 +442,7 @@ class BlockletManager extends BaseBlockletManager {
423
442
 
424
443
  await this.deleteProcess({ did });
425
444
  const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.error);
426
- this.emit(BlockletEvents.startFailed, res);
445
+ this.emit(BlockletEvents.startFailed, { ...res, error: { message: error.message } });
427
446
 
428
447
  if (throwOnError) {
429
448
  throw new Error(description);
@@ -601,7 +620,7 @@ class BlockletManager extends BaseBlockletManager {
601
620
  async deleteComponent({ did, rootDid, keepData, keepState }, context) {
602
621
  logger.info('delete blocklet component', { did, rootDid, keepData });
603
622
 
604
- const blocklet = await this.ensureBlocklet(rootDid);
623
+ const blocklet = await this.ensureBlocklet(rootDid, { validateEnv: false });
605
624
  const child = blocklet.children.find((x) => x.meta.did === did);
606
625
  if (!child) {
607
626
  throw new Error('Component does not exist');
@@ -851,6 +870,37 @@ class BlockletManager extends BaseBlockletManager {
851
870
  return newState;
852
871
  }
853
872
 
873
+ async configPublicToStore({ did, publicToStore = false }) {
874
+ const blocklet = await this.ensureBlocklet(did);
875
+ // publicToStore 由用户传入
876
+ // handleInstanceInStore 方法写在前面,保证向 store 操作成功后才会更改 blocklet 中的 publicToStore值
877
+ // handleInstanceInStore 中会校验修改 publicToStore字段 的条件,不符合则会抛错,就不会执行下面更新 publicToStore 的逻辑
878
+ await handleInstanceInStore(blocklet, { publicToStore });
879
+ await states.blockletExtras.setSettings(did, { publicToStore });
880
+
881
+ const newState = await this.ensureBlocklet(did);
882
+ return newState;
883
+ }
884
+
885
+ async updateWhoCanAccess({ did, whoCanAccess }) {
886
+ if (!(await this.hasBlocklet({ did }))) {
887
+ throw new Error('The blocklet does not exist');
888
+ }
889
+
890
+ if (!Object.values(WHO_CAN_ACCESS).includes(whoCanAccess)) {
891
+ logger.error(`The value of whoCanAccess is invalid: ${whoCanAccess}`);
892
+ throw new Error('the value is invalid');
893
+ }
894
+
895
+ await states.blockletExtras.setSettings(did, { whoCanAccess });
896
+
897
+ const blocklet = await this.ensureBlocklet(did);
898
+
899
+ this.emit(BlockletEvents.updated, { meta: { did: blocklet.meta.did } });
900
+
901
+ return blocklet;
902
+ }
903
+
854
904
  /**
855
905
  * upgrade blocklet from registry
856
906
  */
@@ -1275,6 +1325,10 @@ class BlockletManager extends BaseBlockletManager {
1275
1325
  return blocklet;
1276
1326
  }
1277
1327
 
1328
+ async hasBlocklet({ did }) {
1329
+ return states.blocklet.hasBlocklet(did);
1330
+ }
1331
+
1278
1332
  async setInitialized({ did, owner }) {
1279
1333
  if (!validateOwner(owner)) {
1280
1334
  throw new Error('Blocklet owner is invalid');
@@ -1495,7 +1549,12 @@ class BlockletManager extends BaseBlockletManager {
1495
1549
 
1496
1550
  preDownloadLock.release();
1497
1551
 
1498
- this.emit(BlockletEvents.downloadFailed, { meta: did });
1552
+ this.emit(BlockletEvents.downloadFailed, {
1553
+ meta: { did },
1554
+ error: {
1555
+ message: err.message,
1556
+ },
1557
+ });
1499
1558
  states.notification.create({
1500
1559
  title: 'Blocklet Download Failed',
1501
1560
  description: `Blocklet ${name}@${version} download failed with error: ${err.message}`,
@@ -1786,10 +1845,12 @@ class BlockletManager extends BaseBlockletManager {
1786
1845
  throw new Error('Root blocklet does not exist');
1787
1846
  }
1788
1847
 
1848
+ const { inStore } = await parseSourceUrl(url);
1849
+
1789
1850
  const meta = await getBlockletMetaFromUrl(url);
1790
1851
 
1791
- // 如果是一个付费的blocklet,需要携带token才能下载成功
1792
- if (!isFreeBlocklet(meta)) {
1852
+ // 如果是一个付费的blocklet,并且url来源为Store, 需要携带token才能下载成功
1853
+ if (!isFreeBlocklet(meta) && inStore) {
1793
1854
  const info = await states.node.read();
1794
1855
 
1795
1856
  // eslint-disable-next-line no-param-reassign
@@ -1798,7 +1859,9 @@ class BlockletManager extends BaseBlockletManager {
1798
1859
  headers: {
1799
1860
  'x-server-did': info.did,
1800
1861
  'x-download-token': downloadToken,
1862
+ // FIXME: 先保证兼容性,后续删除
1801
1863
  'x-server-publick-key': info.pk,
1864
+ 'x-server-public-key': info.pk,
1802
1865
  'x-server-signature': sign(info.did, info.sk, {
1803
1866
  exp: (Date.now() + 5 * 60 * 1000) / 1000,
1804
1867
  }),
@@ -2366,7 +2429,9 @@ class BlockletManager extends BaseBlockletManager {
2366
2429
  headers: {
2367
2430
  'x-server-did': info.did,
2368
2431
  'x-download-token': blocklet?.tokens?.paidBlockletDownloadToken,
2432
+ // FIXME: 先保证兼容性,后续删除
2369
2433
  'x-server-publick-key': info.pk,
2434
+ 'x-server-public-key': info.pk,
2370
2435
  'x-server-signature': sign(info.did, info.sk, {
2371
2436
  exp: (Date.now() + 5 * 60 * 1000) / 1000,
2372
2437
  }),
@@ -2400,7 +2465,11 @@ class BlockletManager extends BaseBlockletManager {
2400
2465
  ticket.on('failed', async (err) => {
2401
2466
  logger.error('queue failed', { entity: 'blocklet', action, did, version, name, error: err });
2402
2467
  await this._rollback(action, did, oldBlocklet);
2403
- this.emit(`blocklet.${action}.failed`, { did, version, err });
2468
+ const eventNames = {
2469
+ upgrade: BlockletEvents.upgradeFailed,
2470
+ downgrade: BlockletEvents.downgradeFailed,
2471
+ };
2472
+ this.emit(eventNames[action], { blocklet: oldBlocklet, context });
2404
2473
  states.notification.create({
2405
2474
  title: `Blocklet ${capitalize(action)} Failed`,
2406
2475
  description: `Blocklet ${name}@${version} ${action} failed with error: ${err.message || 'queue exception'}`,
@@ -2625,7 +2694,12 @@ class BlockletManager extends BaseBlockletManager {
2625
2694
  logger.error('failed to install blocklet', { name, did, version, error: err });
2626
2695
  try {
2627
2696
  await this._rollback('install', did, oldBlocklet);
2628
- this.emit(BlockletEvents.installFailed, { meta: { did } });
2697
+ this.emit(BlockletEvents.installFailed, {
2698
+ meta: { did },
2699
+ error: {
2700
+ message: err.message,
2701
+ },
2702
+ });
2629
2703
  states.notification.create({
2630
2704
  title: 'Blocklet Install Failed',
2631
2705
  description: `Blocklet ${meta.name}@${meta.version} install failed with error: ${err.message}`,
@@ -2766,7 +2840,15 @@ class BlockletManager extends BaseBlockletManager {
2766
2840
  } catch (err) {
2767
2841
  const b = await this._rollback(action, did, oldBlocklet);
2768
2842
  logger.error(`failed to ${action} blocklet`, { did, version, name, error: err });
2843
+
2769
2844
  this.emit(BlockletEvents.updated, b);
2845
+
2846
+ const eventNames = {
2847
+ upgrade: BlockletEvents.upgradeFailed,
2848
+ downgrade: BlockletEvents.downgradeFailed,
2849
+ };
2850
+ this.emit(eventNames[action], { blocklet: oldBlocklet, context });
2851
+
2770
2852
  states.notification.create({
2771
2853
  title: `Blocklet ${capitalize(action)} Failed`,
2772
2854
  description: `Blocklet ${name}@${version} ${action} failed with error: ${err.message}`,
package/lib/cert.js CHANGED
@@ -103,14 +103,16 @@ class Cert extends EventEmitter {
103
103
 
104
104
  async add(data) {
105
105
  if (!data.certificate || !data.privateKey) {
106
- throw new Error('certificate and privateKey is required');
106
+ throw new Error('certificate and privateKey are required');
107
107
  }
108
108
 
109
109
  Cert.fixCertificate(data);
110
110
 
111
- await this.manager.add(data);
112
- logger.info('add certificate result', { name: data.name });
113
- this.emit('cert.added', data);
111
+ const result = await this.manager.add(data);
112
+ logger.info('add certificate result', { name: result.name });
113
+ this.emit('cert.added', result);
114
+
115
+ return result;
114
116
  }
115
117
 
116
118
  async issue({ domain }) {
@@ -120,7 +122,10 @@ class Cert extends EventEmitter {
120
122
  }
121
123
 
122
124
  async upsertByDomain(data) {
123
- return this.manager.upsertByDomain(data);
125
+ const result = await this.manager.upsertByDomain(data);
126
+ this.emit('cert.updated', result);
127
+
128
+ return result;
124
129
  }
125
130
 
126
131
  async update(data) {
package/lib/event.js CHANGED
@@ -5,6 +5,7 @@ const { wipeSensitiveData } = require('@blocklet/meta/lib/util');
5
5
  const logger = require('@abtnode/logger')('@abtnode/core:event');
6
6
  const { BLOCKLET_MODES, BlockletStatus, BlockletSource, BlockletEvents } = require('@blocklet/meta/lib/constants');
7
7
  const { EVENTS } = require('@abtnode/constant');
8
+ const handleInstanceInStore = require('./util/public-to-store');
8
9
 
9
10
  const eventHub =
10
11
  process.env.NODE_ENV === 'test' ? require('@arcblock/event-hub/single') : require('@arcblock/event-hub');
@@ -26,6 +27,7 @@ module.exports = ({
26
27
  domainStatus,
27
28
  teamAPI,
28
29
  certManager,
30
+ node,
29
31
  }) => {
30
32
  const notificationState = states.notification;
31
33
  const nodeState = states.node;
@@ -154,12 +156,66 @@ module.exports = ({
154
156
  };
155
157
 
156
158
  const handleBlockletEvent = async (eventName, payload) => {
159
+ const blocklet = payload.blocklet || payload;
160
+
157
161
  if ([BlockletEvents.deployed, BlockletEvents.installed].includes(eventName)) {
158
162
  await handleBlockletAdd(eventName, payload);
163
+
164
+ try {
165
+ await node.createAuditLog({
166
+ action: 'installBlocklet',
167
+ args: {
168
+ did: blocklet.meta.did,
169
+ },
170
+ context: payload.context || {},
171
+ result: blocklet,
172
+ });
173
+ } catch (error) {
174
+ logger.error('Failed to createAuditLog for installBlocklet', { error });
175
+ }
159
176
  } else if ([BlockletEvents.upgraded, BlockletEvents.downgraded].includes(eventName)) {
160
177
  await handleBlockletUpgrade(eventName, payload);
178
+
179
+ try {
180
+ await node.createAuditLog({
181
+ action: 'upgradeBlocklet',
182
+ args: {
183
+ did: blocklet.meta.did,
184
+ },
185
+ context: payload.context || {},
186
+ result: blocklet,
187
+ });
188
+ } catch (error) {
189
+ logger.error('Failed to createAuditLog for upgradeBlocklet', { error });
190
+ }
161
191
  } else if ([BlockletEvents.removed].includes(eventName)) {
162
192
  await handleBlockletRemove(eventName, payload);
193
+ } else if ([BlockletEvents.started].includes(eventName)) {
194
+ try {
195
+ const { publicToStore } = blocklet.settings || {};
196
+ // 如果一个 blocklet 没有设置 publicToStore,启动成功后不应给 store 发请求
197
+ if (publicToStore) {
198
+ await handleInstanceInStore(blocklet, { publicToStore });
199
+ }
200
+ } catch (error) {
201
+ logger.error('handleInstanceInStore failed', { error });
202
+ }
203
+ } else if ([BlockletEvents.upgradeFailed, BlockletEvents.downgradeFailed].includes(eventName)) {
204
+ try {
205
+ await node.createAuditLog({
206
+ action: 'upgradeBlocklet',
207
+ args: {
208
+ did: blocklet.meta.did,
209
+ },
210
+ context: payload.context || {},
211
+ result: {
212
+ ...blocklet,
213
+ resultStatus: 'failed',
214
+ },
215
+ });
216
+ } catch (error) {
217
+ logger.error('Failed to createAuditLog for upgradeBlocklet failed', { error });
218
+ }
163
219
  }
164
220
 
165
221
  if (payload.blocklet && !payload.meta) {
package/lib/index.js CHANGED
@@ -174,19 +174,6 @@ function ABTNode(options) {
174
174
  onStatesReady(createStateReadyHandler(routingSnapshot));
175
175
  const domainStatus = new DomainStatus(routerManager);
176
176
 
177
- const events = createEvents({
178
- blockletManager,
179
- blockletRegistry,
180
- ensureBlockletRouting,
181
- ensureBlockletRoutingForUpgrade,
182
- removeBlockletRouting,
183
- takeRoutingSnapshot,
184
- handleRouting,
185
- domainStatus,
186
- teamAPI,
187
- certManager,
188
- });
189
-
190
177
  const isInitialized = async () => {
191
178
  const state = await states.node.read();
192
179
  return states.node.isInitialized(state);
@@ -226,8 +213,9 @@ function ABTNode(options) {
226
213
  getLatestBlockletVersion: blockletManager.getLatestBlockletVersion.bind(blockletManager),
227
214
  getBlockletMetaFromUrl: blockletManager.getMetaFromUrl.bind(blockletManager),
228
215
  resetBlocklet: blockletManager.reset.bind(blockletManager),
229
-
230
216
  deleteBlockletProcess: blockletManager.deleteProcess.bind(blockletManager),
217
+ configPublicToStore: blockletManager.configPublicToStore.bind(blockletManager),
218
+ updateWhoCanAccess: blockletManager.updateWhoCanAccess.bind(blockletManager),
231
219
 
232
220
  // For diagnose purpose
233
221
  syncBlockletStatus: blockletManager.status.bind(blockletManager),
@@ -237,6 +225,7 @@ function ABTNode(options) {
237
225
  getBlocklets: blockletManager.list.bind(blockletManager),
238
226
  getBlocklet: blockletManager.detail.bind(blockletManager),
239
227
  getBlockletDiff: blockletManager.diff.bind(blockletManager),
228
+ hasBlocklet: blockletManager.hasBlocklet.bind(blockletManager),
240
229
  updateAllBlockletEnvironment: blockletManager.updateAllBlockletEnvironment.bind(blockletManager),
241
230
  setBlockletInitialized: blockletManager.setInitialized.bind(blockletManager),
242
231
 
@@ -317,7 +306,6 @@ function ABTNode(options) {
317
306
  processPassportIssuance: teamAPI.processPassportIssuance.bind(teamAPI),
318
307
  configTrustedPassports: teamAPI.configTrustedPassports.bind(teamAPI),
319
308
  configPassportIssuance: teamAPI.configPassportIssuance.bind(teamAPI),
320
- configWhoCanAccess: teamAPI.configWhoCanAccess.bind(teamAPI),
321
309
 
322
310
  // Challenge
323
311
  generateChallenge: states.challenge.generate.bind(states.challenge),
@@ -403,6 +391,20 @@ function ABTNode(options) {
403
391
  getRouterProvider,
404
392
  };
405
393
 
394
+ const events = createEvents({
395
+ blockletManager,
396
+ blockletRegistry,
397
+ ensureBlockletRouting,
398
+ ensureBlockletRoutingForUpgrade,
399
+ removeBlockletRouting,
400
+ takeRoutingSnapshot,
401
+ handleRouting,
402
+ domainStatus,
403
+ teamAPI,
404
+ certManager,
405
+ node: instance,
406
+ });
407
+
406
408
  const webhook = WebHook({ events, dataDirs, instance });
407
409
 
408
410
  const initCron = () => {
@@ -434,7 +434,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
434
434
  isProtected: true,
435
435
  });
436
436
 
437
- logger.info('dashboard certificate updated');
437
+ logger.info('dashboard certificate updated', { domain });
438
438
  } catch (error) {
439
439
  logger.error('update dashboard certificate failed', { error });
440
440
  throw error;
@@ -1103,6 +1103,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
1103
1103
  certManager.on('cert.added', () => providers[providerName].reload());
1104
1104
  certManager.on('cert.removed', () => providers[providerName].reload());
1105
1105
  certManager.on('cert.issued', () => providers[providerName].reload());
1106
+ certManager.on('cert.updated', () => providers[providerName].reload());
1106
1107
 
1107
1108
  await providers[providerName].start();
1108
1109
  }
@@ -8,7 +8,6 @@ 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');
12
11
  const { EventEmitter } = require('events');
13
12
  const uuid = require('uuid');
14
13
  const isUrl = require('is-url');
@@ -564,39 +563,6 @@ class RouterManager extends EventEmitter {
564
563
  }
565
564
  }
566
565
 
567
- validateCertificate(cert, domain) {
568
- const certificate = Certificate.fromPEM(cert.certificate);
569
- const privateKey = PrivateKey.fromPEM(cert.privateKey);
570
-
571
- const data = Buffer.allocUnsafe(100);
572
- const signature = privateKey.sign(data, 'sha512');
573
- if (!certificate.publicKey.verify(data, signature, 'sha512')) {
574
- throw new Error('Invalid certificate: signature verify failed');
575
- }
576
-
577
- const certDomain = get(certificate, 'subject.commonName', '');
578
- if (domain && domain !== certDomain) {
579
- throw new Error('Invalid certificate: domain does not match');
580
- }
581
-
582
- const validFrom = get(certificate, 'validFrom', '');
583
- if (!validFrom || new Date(validFrom).getTime() > Date.now()) {
584
- throw new Error('Invalid certificate: not in valid period');
585
- }
586
- const validTo = get(certificate, 'validTo', '');
587
- if (!validTo || new Date(validTo).getTime() < Date.now()) {
588
- throw new Error('Invalid certificate: not in valid period');
589
- }
590
-
591
- return certificate;
592
- }
593
-
594
- fixCertificate(entity) {
595
- // Hack: this logic exists because gql does not allow line breaks in arg values
596
- entity.privateKey = entity.privateKey.split('|').join('\n');
597
- entity.certificate = entity.certificate.split('|').join('\n');
598
- }
599
-
600
566
  fixRootBlockletRule(rule) {
601
567
  if (!rule.id) {
602
568
  rule.id = uuid.v4();
@@ -111,10 +111,17 @@ const getLogContent = async (action, args, context, result, info, node) => {
111
111
  case 'configBlocklet':
112
112
  return `updated following config for blocklet ${getBlockletInfo(result, info)}:\n${args.configs.map(x => `- ${x.key}: ${x.value}\n`)}`; // prettier-ignore
113
113
  case 'upgradeBlocklet':
114
+ if (result.resultStatus === 'failed') {
115
+ return `upgrade blocklet failed: ${getBlockletInfo(result, info)}`;
116
+ }
114
117
  return `upgraded blocklet ${getBlockletInfo(result, info)} to v${result.meta.version}`;
115
118
  case 'updateChildBlocklets':
116
119
  return `upgraded components for blocklet ${getBlockletInfo(result, info)}`;
117
-
120
+ case 'configPublicToStore':
121
+ if (args.publicToStore) {
122
+ return `set publicToStore to true for blocklet ${getBlockletInfo(result, info)}`;
123
+ }
124
+ return `set publicToStore to false for blocklet ${getBlockletInfo(result, info)}`;
118
125
  // store
119
126
  case 'addBlockletStore':
120
127
  return `added blocklet store ${args.url}`;
@@ -137,7 +144,7 @@ const getLogContent = async (action, args, context, result, info, node) => {
137
144
  return `switched passport to ${args.passport.name} for ${team}`;
138
145
  case 'login':
139
146
  return `${user} logged in to ${team} with passport ${args.passport.name}`;
140
- case 'configWhoCanAccess':
147
+ case 'updateWhoCanAccess':
141
148
  return `updated access control policy to **${args.value}** for ${team} when ${args.reason}`;
142
149
  case 'configPassportIssuance':
143
150
  return `${args.enabled ? 'enabled' : 'disabled'} passport issuance for ${team}`;
@@ -210,6 +217,14 @@ const getLogContent = async (action, args, context, result, info, node) => {
210
217
  return `added extra domain **${args.domainAlias}** to ${site}`; // prettier-ignore
211
218
  case 'deleteDomainAlias':
212
219
  return `removed extra domain **${args.domainAlias}** from ${site}`; // prettier-ignore
220
+ case 'updateRoutingSite':
221
+ return `updated site from ${site}`; // prettier-ignore
222
+ case 'addRoutingRule':
223
+ return `added routing rule **${args.rule?.from?.pathPrefix}** from ${site}`; // prettier-ignore
224
+ case 'updateRoutingRule':
225
+ return `updated routing rule **${args.rule?.from?.pathPrefix}** from ${site}`; // prettier-ignore
226
+ case 'deleteRoutingRule':
227
+ return `deleted routing rule from ${site}`; // prettier-ignore
213
228
  case 'updateGateway': {
214
229
  let message = args.requestLimit.enabled ? `status: enabled, rate: ${args.requestLimit.rate}` : 'status: disabled';
215
230
  message = `update gateway. ${message}`;
@@ -237,6 +252,7 @@ const getLogCategory = (action) => {
237
252
  case 'configBlocklet':
238
253
  case 'upgradeBlocklet':
239
254
  case 'updateChildBlocklets':
255
+ case 'configPublicToStore':
240
256
  return 'blocklet';
241
257
 
242
258
  // store
@@ -307,16 +323,21 @@ const getScope = (args = {}) => {
307
323
  return args.teamDid;
308
324
  }
309
325
 
326
+ // this param usually means mutating an blockle application
327
+ if (args.did) {
328
+ // this param usually means mutating a nested child component
329
+ if (Array.isArray(args.did)) {
330
+ return args.did[0];
331
+ }
332
+
333
+ return args.did;
334
+ }
335
+
310
336
  // this param usually means mutating a child component
311
337
  if (args.rootDid) {
312
338
  return args.rootDid;
313
339
  }
314
340
 
315
- // this param usually means mutating a nested child component
316
- if (Array.isArray(args.did)) {
317
- return args.did[0];
318
- }
319
-
320
341
  return null;
321
342
  };
322
343
 
@@ -102,6 +102,22 @@ class BlockletState extends BaseState {
102
102
  });
103
103
  }
104
104
 
105
+ hasBlocklet(did) {
106
+ return new Promise((resolve, reject) => {
107
+ if (!did) {
108
+ resolve(false);
109
+ }
110
+
111
+ this.db.count({ $or: [{ 'meta.did': did }, { appDid: did }] }, (err, count) => {
112
+ if (err) {
113
+ return reject(err);
114
+ }
115
+
116
+ return resolve(!!count);
117
+ });
118
+ });
119
+ }
120
+
105
121
  getBlocklets(query = {}, projection) {
106
122
  return new Promise((resolve, reject) => {
107
123
  this.db
@@ -283,6 +283,14 @@ class NodeState extends BaseState {
283
283
  return this.updateNodeInfo({ previousMode: '', mode: info.previousMode });
284
284
  }
285
285
 
286
+ async setMode(mode) {
287
+ if (Object.values(NODE_MODES).includes(mode) === false) {
288
+ throw new Error(`Can not update server to unsupported mode: ${mode}`);
289
+ }
290
+
291
+ return this.updateNodeInfo({ previousMode: '', mode });
292
+ }
293
+
286
294
  async getEnvironments() {
287
295
  return this.read().then((info) => ({
288
296
  ABT_NODE: info.version,
@@ -10,7 +10,13 @@ class SessionState extends BaseState {
10
10
 
11
11
  this.db.ensureIndex({ fieldName: 'createdAt' }, (error) => {
12
12
  if (error) {
13
- logger.error('ensure index failed', { error });
13
+ logger.error('ensure createdAt index failed', { error });
14
+ }
15
+ });
16
+
17
+ this.db.ensureIndex({ fieldName: 'expireDate', expireAfterSeconds: 0 }, (error) => {
18
+ if (error) {
19
+ logger.error('ensure expireDate index failed', { error });
14
20
  }
15
21
  });
16
22
  }
@@ -309,8 +309,10 @@ const getComponentSystemEnvironments = (blocklet) => {
309
309
  if (ports) {
310
310
  Object.assign(portEnvironments, ports);
311
311
  }
312
+
312
313
  return {
313
314
  BLOCKLET_REAL_DID: blocklet.env.id,
315
+ BLOCKLET_REAL_NAME: blocklet.env.name,
314
316
  BLOCKLET_DATA_DIR: blocklet.env.dataDir,
315
317
  BLOCKLET_LOG_DIR: blocklet.env.logsDir,
316
318
  BLOCKLET_CACHE_DIR: blocklet.env.cacheDir,
@@ -328,10 +330,29 @@ const getRuntimeEnvironments = (blocklet, nodeEnvironments, ancestors) => {
328
330
  return o;
329
331
  }, {});
330
332
 
333
+ // get devEnvironments, when blocklet is in dev mode
334
+ const devEnvironments =
335
+ blocklet.mode === BLOCKLET_MODES.DEVELOPMENT
336
+ ? {
337
+ BLOCKLET_DEV_MOUNT_POINT: blocklet?.mountPoint || '',
338
+ }
339
+ : {};
340
+
341
+ const root = (ancestors || [])[0] || blocklet;
342
+ const ports = {};
343
+ forEachBlockletSync(root, (x) => {
344
+ const webInterface = findWebInterface(x);
345
+ if (webInterface && x.environmentObj[webInterface.port]) {
346
+ ports[x.environmentObj.BLOCKLET_REAL_NAME] = x.environmentObj[webInterface.port];
347
+ }
348
+ });
349
+
331
350
  return {
332
351
  ...blocklet.configObj,
333
352
  ...getSharedConfigObj(blocklet, ancestors),
334
353
  ...blocklet.environmentObj,
354
+ ...devEnvironments,
355
+ BLOCKLET_WEB_PORTS: JSON.stringify(ports),
335
356
  ...nodeEnvironments,
336
357
  ...safeNodeEnvironments,
337
358
  };
@@ -405,7 +426,7 @@ const getBlockletMetaFromUrls = async (urls) => {
405
426
  const meta = await any(urls.map(getBlockletMetaFromUrl));
406
427
  return meta;
407
428
  } catch (err) {
408
- logger.error('failed get blocklet meta', { urls });
429
+ logger.error('failed get blocklet meta', { urls, error: err });
409
430
  throw new Error('Failed get blocklet meta');
410
431
  }
411
432
  };
@@ -934,11 +955,6 @@ const pruneBlockletBundle = async ({ blocklets, installDir, blockletSettings })
934
955
 
935
956
  const fillAppDirs = async (dir, level = 'root') => {
936
957
  if (level === 'version') {
937
- if (!fs.existsSync(path.join(dir, 'blocklet.yml'))) {
938
- logger.error('blocklet.yml does not exist in blocklet bundle dir', { dir });
939
- return;
940
- }
941
-
942
958
  appDirs.push({
943
959
  key: path.relative(installDir, dir),
944
960
  dir,
package/lib/util/index.js CHANGED
@@ -398,7 +398,10 @@ const validateUrl = async (url, expectedHttpResTypes = ['application/json', 'tex
398
398
  throw new Error(`Cannot get content-type from ${url}: ${err.message}`);
399
399
  }
400
400
 
401
- if (expectedHttpResTypes.some((x) => res.headers['content-type'].includes(x)) === false) {
401
+ if (
402
+ res.headers['content-type'] &&
403
+ expectedHttpResTypes.some((x) => res.headers['content-type'].includes(x)) === false
404
+ ) {
402
405
  throw new Error(`Unexpected content-type from ${url}: ${res.headers['content-type']}`);
403
406
  }
404
407
 
@@ -0,0 +1,85 @@
1
+ const { sign } = require('@arcblock/jwt');
2
+ const { BlockletSource } = require('@blocklet/meta/lib/constants');
3
+ const { WHO_CAN_ACCESS } = require('@abtnode/constant');
4
+ const logger = require('@abtnode/logger')('@abtnode/util:public-to-store');
5
+
6
+ const getWallet = require('@abtnode/util/lib/get-app-wallet');
7
+ const axios = require('@abtnode/util/lib/axios');
8
+
9
+ const getAppToken = (blocklet) =>
10
+ sign(blocklet.environmentObj?.BLOCKLET_APP_ID, blocklet.environmentObj?.BLOCKLET_APP_SK);
11
+
12
+ const getAppId = (blocklet) => blocklet.environmentObj?.BLOCKLET_APP_ID;
13
+
14
+ const getAppSK = (blocklet) => blocklet.environmentObj?.BLOCKLET_APP_SK;
15
+
16
+ const getBlockletDid = (blocklet) => blocklet.meta?.bundleDid;
17
+
18
+ const getAppUrl = (blocklet) => blocklet.environmentObj?.BLOCKLET_APP_URL;
19
+
20
+ /**
21
+ * verify manages the permissions of blocklet public instance
22
+ * @param {*} blocklet
23
+ * @returns bool
24
+ */
25
+ const verifyPublicInstance = (blocklet) => {
26
+ const { whoCanAccess = WHO_CAN_ACCESS.ALL } = blocklet.settings;
27
+ return blocklet.source === BlockletSource.registry && whoCanAccess === WHO_CAN_ACCESS.ALL;
28
+ };
29
+
30
+ /**
31
+ *
32
+ * @param {*} blocklet
33
+ * @param {*} {userDid publicToStore}
34
+ * @returns bool
35
+ */
36
+ async function handleInstanceInStore(blocklet, { userDid = null, publicToStore = false } = {}) {
37
+ if (!blocklet) {
38
+ logger.error('blocklet argument is required');
39
+ throw new Error('blocklet argument is required');
40
+ }
41
+
42
+ const ownerDid = userDid || blocklet.settings?.owner?.did;
43
+ if (!ownerDid) {
44
+ return false;
45
+ }
46
+
47
+ if (!verifyPublicInstance(blocklet)) {
48
+ logger.error('no permission to set publicInstance');
49
+ throw new Error('no permission to set publicInstance');
50
+ }
51
+
52
+ const appToken = getAppToken(blocklet);
53
+ const blockletDid = getBlockletDid(blocklet);
54
+ const appId = getAppId(blocklet);
55
+ const appSK = getAppSK(blocklet);
56
+ const wallet = getWallet(appSK);
57
+ const body = {
58
+ appToken,
59
+ appId,
60
+ appPK: wallet.publicKey,
61
+ ownerDid,
62
+ appUrl: getAppUrl(blocklet),
63
+ blockletDid,
64
+ };
65
+
66
+ const api = axios.create({ baseURL: blocklet.deployedFrom, timeout: 1000 * 10 });
67
+ if (!publicToStore) {
68
+ try {
69
+ await api.delete(`/api/blocklet-instances/${appId}`, { data: body });
70
+ } catch (error) {
71
+ logger.error('failed to delete blocklet instance', { error });
72
+ throw new Error('failed to delete blocklet instance');
73
+ }
74
+ } else {
75
+ try {
76
+ await api.put(`/api/blocklet-instances/${appId}`, body);
77
+ } catch (error) {
78
+ logger.error('failed to update blocklet instance', { error });
79
+ throw new Error('failed to delete blocklet instance');
80
+ }
81
+ }
82
+ return true;
83
+ }
84
+
85
+ module.exports = handleInstanceInStore;
@@ -126,7 +126,7 @@ const updateSite = Joi.object({
126
126
  )
127
127
  .optional()
128
128
  .messages(domainMessages),
129
- });
129
+ }).unknown();
130
130
 
131
131
  const addRuleSchema = Joi.object({
132
132
  id: Joi.string().required(),
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.8.0",
6
+ "version": "1.8.3",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,31 +19,31 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@abtnode/certificate-manager": "1.8.0",
23
- "@abtnode/constant": "1.8.0",
24
- "@abtnode/cron": "1.8.0",
25
- "@abtnode/db": "1.8.0",
26
- "@abtnode/logger": "1.8.0",
27
- "@abtnode/queue": "1.8.0",
28
- "@abtnode/rbac": "1.8.0",
29
- "@abtnode/router-provider": "1.8.0",
30
- "@abtnode/static-server": "1.8.0",
31
- "@abtnode/timemachine": "1.8.0",
32
- "@abtnode/util": "1.8.0",
33
- "@arcblock/did": "1.17.0",
22
+ "@abtnode/certificate-manager": "1.8.3",
23
+ "@abtnode/constant": "1.8.3",
24
+ "@abtnode/cron": "1.8.3",
25
+ "@abtnode/db": "1.8.3",
26
+ "@abtnode/logger": "1.8.3",
27
+ "@abtnode/queue": "1.8.3",
28
+ "@abtnode/rbac": "1.8.3",
29
+ "@abtnode/router-provider": "1.8.3",
30
+ "@abtnode/static-server": "1.8.3",
31
+ "@abtnode/timemachine": "1.8.3",
32
+ "@abtnode/util": "1.8.3",
33
+ "@arcblock/did": "1.17.5",
34
34
  "@arcblock/did-motif": "^1.1.10",
35
- "@arcblock/did-util": "1.17.0",
36
- "@arcblock/event-hub": "1.17.0",
37
- "@arcblock/jwt": "^1.17.0",
35
+ "@arcblock/did-util": "1.17.5",
36
+ "@arcblock/event-hub": "1.17.5",
37
+ "@arcblock/jwt": "^1.17.5",
38
38
  "@arcblock/pm2-events": "^0.0.5",
39
- "@arcblock/vc": "1.17.0",
40
- "@blocklet/meta": "1.8.0",
39
+ "@arcblock/vc": "1.17.5",
40
+ "@blocklet/meta": "1.8.3",
41
41
  "@fidm/x509": "^1.2.1",
42
- "@nedb/core": "^1.2.2",
43
- "@nedb/multi": "^1.2.2",
44
- "@ocap/mcrypto": "1.17.0",
45
- "@ocap/util": "1.17.0",
46
- "@ocap/wallet": "1.17.0",
42
+ "@nedb/core": "^1.3.1",
43
+ "@nedb/multi": "^1.3.1",
44
+ "@ocap/mcrypto": "1.17.5",
45
+ "@ocap/util": "1.17.5",
46
+ "@ocap/wallet": "1.17.5",
47
47
  "@slack/webhook": "^5.0.3",
48
48
  "axios": "^0.27.2",
49
49
  "axon": "^2.0.3",
@@ -81,5 +81,5 @@
81
81
  "express": "^4.17.1",
82
82
  "jest": "^27.4.5"
83
83
  },
84
- "gitHead": "6446a85fb33721abc24bb1045d59158e6b96c241"
84
+ "gitHead": "c734aca7bf1fc03378c3b082d0622b6a540a8bd3"
85
85
  }