@abtnode/core 1.8.1 → 1.8.4

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.
@@ -22,8 +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 handleInstanceInStore = require('@abtnode/util/lib/public-to-store');
26
- const { VC_TYPE_BLOCKLET_PURCHASE } = require('@abtnode/constant');
25
+ const { VC_TYPE_BLOCKLET_PURCHASE, WHO_CAN_ACCESS } = require('@abtnode/constant');
27
26
 
28
27
  const getBlockletEngine = require('@blocklet/meta/lib/engine');
29
28
  const {
@@ -116,6 +115,7 @@ const { getFactoryState } = require('../../util/chain');
116
115
  const runMigrationScripts = require('../migration');
117
116
  const hooks = require('../hooks');
118
117
  const { formatName } = require('../../util/get-domain-for-blocklet');
118
+ const handleInstanceInStore = require('../../util/public-to-store');
119
119
 
120
120
  const {
121
121
  isInProgress,
@@ -128,7 +128,7 @@ const {
128
128
  validateOwner,
129
129
  } = util;
130
130
 
131
- const preDownloadLock = new Lock('pre-download-lock');
131
+ const statusLock = new Lock('blocklet-status-lock');
132
132
 
133
133
  const asyncFs = fs.promises;
134
134
 
@@ -234,7 +234,9 @@ class BlockletManager extends BaseBlockletManager {
234
234
  context.headers = Object.assign(context?.headers || {}, {
235
235
  'x-server-did': info.did,
236
236
  'x-download-token': params.downloadToken,
237
+ // FIXME: 先保证兼容性,后续删除
237
238
  'x-server-publick-key': info.pk,
239
+ 'x-server-public-key': info.pk,
238
240
  'x-server-signature': sign(info.did, info.sk, {
239
241
  exp: (Date.now() + 5 * 60 * 1000) / 1000,
240
242
  }),
@@ -427,6 +429,12 @@ class BlockletManager extends BaseBlockletManager {
427
429
 
428
430
  return blocklet;
429
431
  } catch (err) {
432
+ const status = await states.blocklet.getBlockletStatus(did);
433
+ if ([BlockletStatus.stopping, BlockletStatus.stopped].includes(status)) {
434
+ logger.info('Failed to start blocklet maybe due to manually stopped');
435
+ return states.blocklet.getBlocklet(did);
436
+ }
437
+
430
438
  const error = Array.isArray(err) ? err[0] : err;
431
439
  logger.error('Failed to start blocklet', { error, did, name: blocklet.meta.name });
432
440
  const description = `Start blocklet ${blocklet.meta.name} failed with error: ${error.message}`;
@@ -677,26 +685,38 @@ class BlockletManager extends BaseBlockletManager {
677
685
  return newBlocklet;
678
686
  }
679
687
 
680
- async cancelDownload({ did }, context) {
681
- await preDownloadLock.acquire();
688
+ async cancelDownload({ did: inputDid }, context) {
682
689
  try {
683
- const blocklet = await states.blocklet.getBlocklet(did);
690
+ await statusLock.acquire();
691
+ const blocklet = await states.blocklet.getBlocklet(inputDid);
684
692
  if (!blocklet) {
685
- throw new Error('Can not cancel download for non-exist blocklet in database.', { did });
693
+ throw new Error('Can not cancel download for non-exist blocklet in database.', { inputDid });
694
+ }
695
+
696
+ const { name, did, version } = blocklet.meta;
697
+
698
+ if (![BlockletStatus.downloading, BlockletStatus.waiting].includes(blocklet.status)) {
699
+ throw new Error(`Can not cancel blocklet that status is ${fromBlockletStatus(blocklet.status)}`);
700
+ }
701
+
702
+ const job = await this.installQueue.get(did);
703
+ if (job) {
704
+ const { postAction, oldBlocklet } = job;
705
+ await this._rollback(postAction, did, oldBlocklet);
686
706
  }
687
707
 
688
708
  if (blocklet.status === BlockletStatus.downloading) {
689
709
  await this._cancelDownload(blocklet.meta, context);
690
710
  } else if (blocklet.status === BlockletStatus.waiting) {
691
711
  await this._cancelWaiting(blocklet.meta, context);
692
- } else {
693
- throw new Error(`Can not cancel blocklet that status is ${fromBlockletStatus(blocklet.status)}`);
694
712
  }
695
713
 
696
- preDownloadLock.release();
714
+ logger.info('cancel download blocklet', { did, name, version, status: fromBlockletStatus(blocklet.status) });
715
+
716
+ statusLock.release();
697
717
  return blocklet;
698
718
  } catch (error) {
699
- preDownloadLock.release();
719
+ statusLock.release();
700
720
  throw error;
701
721
  }
702
722
  }
@@ -870,7 +890,9 @@ class BlockletManager extends BaseBlockletManager {
870
890
 
871
891
  async configPublicToStore({ did, publicToStore = false }) {
872
892
  const blocklet = await this.ensureBlocklet(did);
873
-
893
+ // publicToStore 由用户传入
894
+ // handleInstanceInStore 方法写在前面,保证向 store 操作成功后才会更改 blocklet 中的 publicToStore值
895
+ // handleInstanceInStore 中会校验修改 publicToStore字段 的条件,不符合则会抛错,就不会执行下面更新 publicToStore 的逻辑
874
896
  await handleInstanceInStore(blocklet, { publicToStore });
875
897
  await states.blockletExtras.setSettings(did, { publicToStore });
876
898
 
@@ -878,6 +900,25 @@ class BlockletManager extends BaseBlockletManager {
878
900
  return newState;
879
901
  }
880
902
 
903
+ async updateWhoCanAccess({ did, whoCanAccess }) {
904
+ if (!(await this.hasBlocklet({ did }))) {
905
+ throw new Error('The blocklet does not exist');
906
+ }
907
+
908
+ if (!Object.values(WHO_CAN_ACCESS).includes(whoCanAccess)) {
909
+ logger.error(`The value of whoCanAccess is invalid: ${whoCanAccess}`);
910
+ throw new Error('the value is invalid');
911
+ }
912
+
913
+ await states.blockletExtras.setSettings(did, { whoCanAccess });
914
+
915
+ const blocklet = await this.ensureBlocklet(did);
916
+
917
+ this.emit(BlockletEvents.updated, { meta: { did: blocklet.meta.did } });
918
+
919
+ return blocklet;
920
+ }
921
+
881
922
  /**
882
923
  * upgrade blocklet from registry
883
924
  */
@@ -1302,6 +1343,10 @@ class BlockletManager extends BaseBlockletManager {
1302
1343
  return blocklet;
1303
1344
  }
1304
1345
 
1346
+ async hasBlocklet({ did }) {
1347
+ return states.blocklet.hasBlocklet(did);
1348
+ }
1349
+
1305
1350
  async setInitialized({ did, owner }) {
1306
1351
  if (!validateOwner(owner)) {
1307
1352
  throw new Error('Blocklet owner is invalid');
@@ -1489,39 +1534,45 @@ class BlockletManager extends BaseBlockletManager {
1489
1534
  const { meta } = blocklet;
1490
1535
  const { name, did, version } = meta;
1491
1536
 
1537
+ // check status
1492
1538
  try {
1493
- await preDownloadLock.acquire();
1539
+ await statusLock.acquire();
1494
1540
 
1495
1541
  const b0 = await states.blocklet.getBlocklet(did);
1496
- if (!b0 || ![BlockletStatus.waiting, BlockletStatus.downloading].includes(b0.status)) {
1542
+ if (!b0 || ![BlockletStatus.waiting].includes(b0.status)) {
1497
1543
  if (!b0) {
1498
- logger.error('blocklet does not exist before downloading', { name, did });
1544
+ throw new Error('blocklet does not exist before downloading');
1499
1545
  } else {
1500
- logger.error('blocklet status is invalid before downloading', {
1501
- name,
1502
- did,
1503
- status: fromBlockletStatus(b0.status),
1504
- });
1546
+ throw new Error(`blocklet status is invalid before downloading: ${fromBlockletStatus(b0.status)}`);
1505
1547
  }
1506
- preDownloadLock.release();
1507
- await this._rollback(postAction, did, oldBlocklet);
1508
- return;
1509
1548
  }
1549
+ statusLock.release();
1550
+ } catch (error) {
1551
+ statusLock.release();
1552
+ logger.error('Check blocklet status failed before downloading', {
1553
+ name,
1554
+ did,
1555
+ error,
1556
+ });
1557
+ await this._rollback(postAction, did, oldBlocklet);
1558
+ return;
1559
+ }
1510
1560
 
1511
- preDownloadLock.release();
1512
-
1561
+ // download bundle
1562
+ try {
1513
1563
  const { isCancelled } = await this._downloadBlocklet(blocklet, oldBlocklet, context);
1514
1564
 
1515
1565
  if (isCancelled) {
1516
- logger.info('Download was canceled manually', { name, did, version });
1517
- await this._rollback(postAction, did, oldBlocklet);
1566
+ logger.info('Download was canceled', { name, did, version });
1567
+
1568
+ if ((await states.blocklet.getBlockletStatus(did)) === BlockletStatus.downloading) {
1569
+ await this._rollback(postAction, did, oldBlocklet);
1570
+ }
1518
1571
  return;
1519
1572
  }
1520
1573
  } catch (err) {
1521
1574
  logger.error('Download blocklet tarball failed', { name, did, version });
1522
1575
 
1523
- preDownloadLock.release();
1524
-
1525
1576
  this.emit(BlockletEvents.downloadFailed, {
1526
1577
  meta: { did },
1527
1578
  error: {
@@ -1549,6 +1600,31 @@ class BlockletManager extends BaseBlockletManager {
1549
1600
  return;
1550
1601
  }
1551
1602
 
1603
+ // update status
1604
+ try {
1605
+ await statusLock.acquire();
1606
+
1607
+ if ((await states.blocklet.getBlockletStatus(did)) !== BlockletStatus.downloading) {
1608
+ throw new Error('blocklet status changed durning download');
1609
+ }
1610
+
1611
+ if (postAction === 'install') {
1612
+ const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.installing);
1613
+ this.emit(BlockletEvents.statusChange, state);
1614
+ }
1615
+
1616
+ if (['upgrade', 'downgrade'].includes(postAction)) {
1617
+ const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.upgrading);
1618
+ this.emit(BlockletEvents.statusChange, state);
1619
+ }
1620
+
1621
+ statusLock.release();
1622
+ } catch (error) {
1623
+ logger.error(error.message);
1624
+ statusLock.release();
1625
+ }
1626
+
1627
+ // install
1552
1628
  try {
1553
1629
  // install blocklet
1554
1630
  if (postAction === 'install') {
@@ -1572,9 +1648,6 @@ class BlockletManager extends BaseBlockletManager {
1572
1648
  const { did, version } = meta;
1573
1649
  logger.info('do install blocklet', { did, version });
1574
1650
 
1575
- const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.installing);
1576
- this.emit(BlockletEvents.statusChange, state);
1577
-
1578
1651
  try {
1579
1652
  const installedBlocklet = await this._installBlocklet({
1580
1653
  did,
@@ -1602,9 +1675,6 @@ class BlockletManager extends BaseBlockletManager {
1602
1675
  const { version, did } = newBlocklet.meta;
1603
1676
  logger.info(`do ${action} blocklet`, { did, version });
1604
1677
 
1605
- const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.upgrading);
1606
- this.emit(BlockletEvents.statusChange, state);
1607
-
1608
1678
  try {
1609
1679
  await this._upgradeBlocklet({
1610
1680
  newBlocklet,
@@ -1636,6 +1706,12 @@ class BlockletManager extends BaseBlockletManager {
1636
1706
  const res = await this.status(did, { forceSync: true });
1637
1707
  this.emit(BlockletEvents.started, res);
1638
1708
  } catch (error) {
1709
+ const status = await states.blocklet.getBlockletStatus(did);
1710
+ if ([BlockletStatus.stopping, BlockletStatus.stopped].includes(status)) {
1711
+ logger.info(`Check blocklet healthy failing because blocklet is ${fromBlockletStatus(status)}`);
1712
+ return;
1713
+ }
1714
+
1639
1715
  logger.error('check blocklet if started failed', { did, name, context, timeout, error });
1640
1716
 
1641
1717
  await this.deleteProcess({ did }, context);
@@ -1832,7 +1908,9 @@ class BlockletManager extends BaseBlockletManager {
1832
1908
  headers: {
1833
1909
  'x-server-did': info.did,
1834
1910
  'x-download-token': downloadToken,
1911
+ // FIXME: 先保证兼容性,后续删除
1835
1912
  'x-server-publick-key': info.pk,
1913
+ 'x-server-public-key': info.pk,
1836
1914
  'x-server-signature': sign(info.did, info.sk, {
1837
1915
  exp: (Date.now() + 5 * 60 * 1000) / 1000,
1838
1916
  }),
@@ -2400,7 +2478,9 @@ class BlockletManager extends BaseBlockletManager {
2400
2478
  headers: {
2401
2479
  'x-server-did': info.did,
2402
2480
  'x-download-token': blocklet?.tokens?.paidBlockletDownloadToken,
2481
+ // FIXME: 先保证兼容性,后续删除
2403
2482
  'x-server-publick-key': info.pk,
2483
+ 'x-server-public-key': info.pk,
2404
2484
  'x-server-signature': sign(info.did, info.sk, {
2405
2485
  exp: (Date.now() + 5 * 60 * 1000) / 1000,
2406
2486
  }),
@@ -2434,7 +2514,11 @@ class BlockletManager extends BaseBlockletManager {
2434
2514
  ticket.on('failed', async (err) => {
2435
2515
  logger.error('queue failed', { entity: 'blocklet', action, did, version, name, error: err });
2436
2516
  await this._rollback(action, did, oldBlocklet);
2437
- this.emit(`blocklet.${action}.failed`, { did, version, err });
2517
+ const eventNames = {
2518
+ upgrade: BlockletEvents.upgradeFailed,
2519
+ downgrade: BlockletEvents.downgradeFailed,
2520
+ };
2521
+ this.emit(eventNames[action], { blocklet: oldBlocklet, context });
2438
2522
  states.notification.create({
2439
2523
  title: `Blocklet ${capitalize(action)} Failed`,
2440
2524
  description: `Blocklet ${name}@${version} ${action} failed with error: ${err.message || 'queue exception'}`,
@@ -2805,7 +2889,15 @@ class BlockletManager extends BaseBlockletManager {
2805
2889
  } catch (err) {
2806
2890
  const b = await this._rollback(action, did, oldBlocklet);
2807
2891
  logger.error(`failed to ${action} blocklet`, { did, version, name, error: err });
2892
+
2808
2893
  this.emit(BlockletEvents.updated, b);
2894
+
2895
+ const eventNames = {
2896
+ upgrade: BlockletEvents.upgradeFailed,
2897
+ downgrade: BlockletEvents.downgradeFailed,
2898
+ };
2899
+ this.emit(eventNames[action], { blocklet: oldBlocklet, context });
2900
+
2809
2901
  states.notification.create({
2810
2902
  title: `Blocklet ${capitalize(action)} Failed`,
2811
2903
  description: `Blocklet ${name}@${version} ${action} failed with error: ${err.message}`,
@@ -3114,26 +3206,19 @@ class BlockletManager extends BaseBlockletManager {
3114
3206
 
3115
3207
  // eslint-disable-next-line no-unused-vars
3116
3208
  async _cancelDownload(blockletMeta, context) {
3117
- const { did, name, version } = blockletMeta;
3209
+ const { did } = blockletMeta;
3118
3210
 
3119
3211
  if (this.downloadCtrls[did]) {
3120
3212
  for (const cancelCtrl of this.downloadCtrls[did].values()) {
3121
3213
  cancelCtrl.cancel();
3122
3214
  }
3123
- logger.info('cancel download blocklet', { did, name, version });
3124
3215
  }
3125
3216
  }
3126
3217
 
3127
3218
  // eslint-disable-next-line no-unused-vars
3128
3219
  async _cancelWaiting(blockletMeta, context) {
3129
- const { did, name, version } = blockletMeta;
3130
-
3131
- const {
3132
- job: { postAction, oldBlocklet },
3133
- } = await this.installQueue.cancel(did);
3134
- await this._rollback(postAction, did, oldBlocklet);
3135
-
3136
- logger.info('cancel waiting blocklet', { did, name, version });
3220
+ const { did } = blockletMeta;
3221
+ return this.installQueue.cancel(did);
3137
3222
  }
3138
3223
 
3139
3224
  /**
package/lib/event.js CHANGED
@@ -5,7 +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('@abtnode/util/lib/public-to-store');
8
+ const handleInstanceInStore = require('./util/public-to-store');
9
9
 
10
10
  const eventHub =
11
11
  process.env.NODE_ENV === 'test' ? require('@arcblock/event-hub/single') : require('@arcblock/event-hub');
@@ -27,6 +27,7 @@ module.exports = ({
27
27
  domainStatus,
28
28
  teamAPI,
29
29
  certManager,
30
+ node,
30
31
  }) => {
31
32
  const notificationState = states.notification;
32
33
  const nodeState = states.node;
@@ -155,19 +156,66 @@ module.exports = ({
155
156
  };
156
157
 
157
158
  const handleBlockletEvent = async (eventName, payload) => {
159
+ const blocklet = payload.blocklet || payload;
160
+
158
161
  if ([BlockletEvents.deployed, BlockletEvents.installed].includes(eventName)) {
159
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
+ }
160
176
  } else if ([BlockletEvents.upgraded, BlockletEvents.downgraded].includes(eventName)) {
161
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
+ }
162
191
  } else if ([BlockletEvents.removed].includes(eventName)) {
163
192
  await handleBlockletRemove(eventName, payload);
164
193
  } else if ([BlockletEvents.started].includes(eventName)) {
165
194
  try {
166
- const blocklet = await blockletManager.ensureBlocklet(payload.meta.did);
167
- await handleInstanceInStore(blocklet);
195
+ const { publicToStore } = blocklet.settings || {};
196
+ // 如果一个 blocklet 没有设置 publicToStore,启动成功后不应给 store 发请求
197
+ if (publicToStore) {
198
+ await handleInstanceInStore(blocklet, { publicToStore });
199
+ }
168
200
  } catch (error) {
169
201
  logger.error('handleInstanceInStore failed', { error });
170
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
+ }
171
219
  }
172
220
 
173
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,9 +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
- configPublicToStore: blockletManager.configPublicToStore.bind(blockletManager),
230
-
231
216
  deleteBlockletProcess: blockletManager.deleteProcess.bind(blockletManager),
217
+ configPublicToStore: blockletManager.configPublicToStore.bind(blockletManager),
218
+ updateWhoCanAccess: blockletManager.updateWhoCanAccess.bind(blockletManager),
232
219
 
233
220
  // For diagnose purpose
234
221
  syncBlockletStatus: blockletManager.status.bind(blockletManager),
@@ -238,6 +225,7 @@ function ABTNode(options) {
238
225
  getBlocklets: blockletManager.list.bind(blockletManager),
239
226
  getBlocklet: blockletManager.detail.bind(blockletManager),
240
227
  getBlockletDiff: blockletManager.diff.bind(blockletManager),
228
+ hasBlocklet: blockletManager.hasBlocklet.bind(blockletManager),
241
229
  updateAllBlockletEnvironment: blockletManager.updateAllBlockletEnvironment.bind(blockletManager),
242
230
  setBlockletInitialized: blockletManager.setInitialized.bind(blockletManager),
243
231
 
@@ -318,7 +306,6 @@ function ABTNode(options) {
318
306
  processPassportIssuance: teamAPI.processPassportIssuance.bind(teamAPI),
319
307
  configTrustedPassports: teamAPI.configTrustedPassports.bind(teamAPI),
320
308
  configPassportIssuance: teamAPI.configPassportIssuance.bind(teamAPI),
321
- configWhoCanAccess: teamAPI.configWhoCanAccess.bind(teamAPI),
322
309
 
323
310
  // Challenge
324
311
  generateChallenge: states.challenge.generate.bind(states.challenge),
@@ -404,6 +391,20 @@ function ABTNode(options) {
404
391
  getRouterProvider,
405
392
  };
406
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
+
407
408
  const webhook = WebHook({ events, dataDirs, instance });
408
409
 
409
410
  const initCron = () => {
@@ -111,6 +111,9 @@ 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)}`;
@@ -141,7 +144,7 @@ const getLogContent = async (action, args, context, result, info, node) => {
141
144
  return `switched passport to ${args.passport.name} for ${team}`;
142
145
  case 'login':
143
146
  return `${user} logged in to ${team} with passport ${args.passport.name}`;
144
- case 'configWhoCanAccess':
147
+ case 'updateWhoCanAccess':
145
148
  return `updated access control policy to **${args.value}** for ${team} when ${args.reason}`;
146
149
  case 'configPassportIssuance':
147
150
  return `${args.enabled ? 'enabled' : 'disabled'} passport issuance for ${team}`;
@@ -214,6 +217,14 @@ const getLogContent = async (action, args, context, result, info, node) => {
214
217
  return `added extra domain **${args.domainAlias}** to ${site}`; // prettier-ignore
215
218
  case 'deleteDomainAlias':
216
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
217
228
  case 'updateGateway': {
218
229
  let message = args.requestLimit.enabled ? `status: enabled, rate: ${args.requestLimit.rate}` : 'status: disabled';
219
230
  message = `update gateway. ${message}`;
@@ -312,16 +323,21 @@ const getScope = (args = {}) => {
312
323
  return args.teamDid;
313
324
  }
314
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
+
315
336
  // this param usually means mutating a child component
316
337
  if (args.rootDid) {
317
338
  return args.rootDid;
318
339
  }
319
340
 
320
- // this param usually means mutating a nested child component
321
- if (Array.isArray(args.did)) {
322
- return args.did[0];
323
- }
324
-
325
341
  return null;
326
342
  };
327
343
 
@@ -102,6 +102,38 @@ class BlockletState extends BaseState {
102
102
  });
103
103
  }
104
104
 
105
+ async getBlockletStatus(did) {
106
+ return new Promise((resolve, reject) => {
107
+ if (!did) {
108
+ resolve(null);
109
+ }
110
+
111
+ this.db.findOne({ $or: [{ 'meta.did': did }, { appDid: did }] }, { status: 1 }, (err, doc) => {
112
+ if (err) {
113
+ return reject(err);
114
+ }
115
+
116
+ return resolve(doc ? doc.status : null);
117
+ });
118
+ });
119
+ }
120
+
121
+ hasBlocklet(did) {
122
+ return new Promise((resolve, reject) => {
123
+ if (!did) {
124
+ resolve(false);
125
+ }
126
+
127
+ this.db.count({ $or: [{ 'meta.did': did }, { appDid: did }] }, (err, count) => {
128
+ if (err) {
129
+ return reject(err);
130
+ }
131
+
132
+ return resolve(!!count);
133
+ });
134
+ });
135
+ }
136
+
105
137
  getBlocklets(query = {}, projection) {
106
138
  return new Promise((resolve, reject) => {
107
139
  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,
@@ -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
  };
@@ -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(),
@@ -4,6 +4,7 @@ const { evaluateURLs } = require('@abtnode/util/lib/url-evaluation');
4
4
  const checkURLAccessible = require('@abtnode/util/lib/url-evaluation/check-accessible-node');
5
5
  const { EVENTS } = require('@abtnode/constant');
6
6
  const WebHookSender = require('./sender');
7
+ const WalletSender = require('./sender/wallet');
7
8
  const createQueue = require('../queue');
8
9
  const IP = require('../util/ip');
9
10
  const states = require('../states');
@@ -72,6 +73,11 @@ module.exports = ({ events, dataDirs, instance }) => {
72
73
  const baseUrls = await getBaseUrls(instance, [external, internal]);
73
74
  const senderFns = {};
74
75
 
76
+ // Always send message to wallet
77
+ webhookList.push({
78
+ type: WalletSender.type,
79
+ });
80
+
75
81
  if (webhookList.length) {
76
82
  for (let i = 0; i < webhookList.length; i++) {
77
83
  const item = webhookList[i];
@@ -79,8 +85,7 @@ module.exports = ({ events, dataDirs, instance }) => {
79
85
  const senderInstance = WebHookSender.getMessageSender(item.type);
80
86
  senderFns[item.type] = senderInstance.send.bind(senderInstance);
81
87
  }
82
- const { name } = nodeInfo;
83
- const options = { ...message, nodeInfo: `*${name}*` };
88
+ const options = { ...message, nodeInfo, node: instance };
84
89
  if (item.type === 'slack') {
85
90
  // eslint-disable-next-line
86
91
  options.urlInfo = await getSlackUrlInfo(message.action, baseUrls);
@@ -3,6 +3,7 @@ const logger = require('@abtnode/logger')('@abtnode/core:sender');
3
3
 
4
4
  const Slack = require('./slack');
5
5
  const Api = require('./api');
6
+ const Wallet = require('./wallet');
6
7
 
7
8
  const SenderMap = new Map([
8
9
  [Slack.type, Slack],
@@ -12,6 +13,10 @@ const SenderMap = new Map([
12
13
  const getSenderNames = () => [...SenderMap.keys()];
13
14
 
14
15
  const getSender = (name) => {
16
+ if (name === Wallet.type) {
17
+ return Wallet;
18
+ }
19
+
15
20
  if (!SenderMap.has(name)) {
16
21
  logger.error(`getSender:sender name [${name}] does not exist`);
17
22
  return null;
@@ -29,7 +29,7 @@ class SlackSender extends BaseSender {
29
29
  type: 'section',
30
30
  text: {
31
31
  type: 'mrkdwn',
32
- text: nodeInfo,
32
+ text: `*${nodeInfo.name}*`,
33
33
  },
34
34
  },
35
35
  {
@@ -0,0 +1,45 @@
1
+ const logger = require('@abtnode/logger')('@abtnode/core:sender:api');
2
+ const { sendToUser } = require('@blocklet/sdk/lib/util/send-notification');
3
+ const { ROLES, PASSPORT_STATUS } = require('@abtnode/constant');
4
+ const BaseSender = require('../base');
5
+
6
+ class WalletSender extends BaseSender {
7
+ async send(params, data = {}) {
8
+ const { title, description, nodeInfo, node } = data;
9
+
10
+ try {
11
+ const sender = {
12
+ appDid: nodeInfo.did,
13
+ appSk: nodeInfo.sk,
14
+ };
15
+
16
+ const message = {
17
+ title,
18
+ body: description,
19
+ };
20
+
21
+ const { users } = await node.getUsers({ teamDid: nodeInfo.did, paging: { pageSize: 100 } });
22
+ const adminUsers = users
23
+ .filter(
24
+ (x) =>
25
+ x.approved &&
26
+ (x.passports || []).some(
27
+ (y) => [ROLES.OWNER, ROLES.ADMIN].includes(y.name) && y.status === PASSPORT_STATUS.VALID
28
+ )
29
+ )
30
+ .map((x) => x.did);
31
+
32
+ if (!adminUsers.length) {
33
+ return;
34
+ }
35
+
36
+ await sendToUser(adminUsers, message, sender, process.env.ABT_NODE_SERVICE_PORT);
37
+ } catch (error) {
38
+ logger.error('failed to push notification to wallet', { error });
39
+ }
40
+ }
41
+ }
42
+
43
+ WalletSender.type = 'wallet';
44
+
45
+ module.exports = WalletSender;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.8.1",
6
+ "version": "1.8.4",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,31 +19,32 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@abtnode/certificate-manager": "1.8.1",
23
- "@abtnode/constant": "1.8.1",
24
- "@abtnode/cron": "1.8.1",
25
- "@abtnode/db": "1.8.1",
26
- "@abtnode/logger": "1.8.1",
27
- "@abtnode/queue": "1.8.1",
28
- "@abtnode/rbac": "1.8.1",
29
- "@abtnode/router-provider": "1.8.1",
30
- "@abtnode/static-server": "1.8.1",
31
- "@abtnode/timemachine": "1.8.1",
32
- "@abtnode/util": "1.8.1",
33
- "@arcblock/did": "1.17.0",
22
+ "@abtnode/certificate-manager": "1.8.4",
23
+ "@abtnode/constant": "1.8.4",
24
+ "@abtnode/cron": "1.8.4",
25
+ "@abtnode/db": "1.8.4",
26
+ "@abtnode/logger": "1.8.4",
27
+ "@abtnode/queue": "1.8.4",
28
+ "@abtnode/rbac": "1.8.4",
29
+ "@abtnode/router-provider": "1.8.4",
30
+ "@abtnode/static-server": "1.8.4",
31
+ "@abtnode/timemachine": "1.8.4",
32
+ "@abtnode/util": "1.8.4",
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.1",
39
+ "@arcblock/vc": "1.17.5",
40
+ "@blocklet/meta": "1.8.4",
41
+ "@blocklet/sdk": "1.8.4",
41
42
  "@fidm/x509": "^1.2.1",
42
- "@nedb/core": "^1.3.1",
43
- "@nedb/multi": "^1.3.1",
44
- "@ocap/mcrypto": "1.17.0",
45
- "@ocap/util": "1.17.0",
46
- "@ocap/wallet": "1.17.0",
43
+ "@nedb/core": "^1.3.2",
44
+ "@nedb/multi": "^1.3.2",
45
+ "@ocap/mcrypto": "1.17.5",
46
+ "@ocap/util": "1.17.5",
47
+ "@ocap/wallet": "1.17.5",
47
48
  "@slack/webhook": "^5.0.3",
48
49
  "axios": "^0.27.2",
49
50
  "axon": "^2.0.3",
@@ -81,5 +82,5 @@
81
82
  "express": "^4.17.1",
82
83
  "jest": "^27.4.5"
83
84
  },
84
- "gitHead": "c970b8a386bebd7fe6dbc8b8eedf8bd8328b4bb5"
85
+ "gitHead": "c42fb1bb84c5eef0f753fd5397d8007c7a6eee19"
85
86
  }