@abtnode/core 1.8.64-beta-b3d407e0 → 1.8.64-beta-0b5ede51

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/team.js CHANGED
@@ -408,6 +408,26 @@ class TeamAPI extends EventEmitter {
408
408
 
409
409
  // Invite member
410
410
 
411
+ /**
412
+ * @type InvitationSession {
413
+ * type: 'invite';
414
+ * role: string;
415
+ * remark: string;
416
+ * expireDate: number;
417
+ * inviter: {
418
+ * did: string;
419
+ * email?: string;
420
+ * fullName?: string;
421
+ * role?: string;
422
+ * };
423
+ * teamDid: string;
424
+ * status: '' | 'success';
425
+ * receiver: {
426
+ * did: string;
427
+ * };
428
+ * }
429
+ * @returns
430
+ */
411
431
  async createMemberInvitation({ teamDid, role, expireTime, remark }, context) {
412
432
  await this.teamManager.checkEnablePassportIssuance(teamDid);
413
433
 
@@ -484,15 +504,17 @@ class TeamAPI extends EventEmitter {
484
504
  expireDate: new Date(invitation.expireDate).toString(),
485
505
  inviter: invitation.inviter,
486
506
  teamDid: invitation.teamDid,
507
+ status: invitation.status,
508
+ receiver: invitation.receiver,
487
509
  };
488
510
  }
489
511
 
490
- async getInvitations({ teamDid }) {
512
+ async getInvitations({ teamDid, filter }) {
491
513
  const state = await this.getSessionState(teamDid);
492
514
 
493
515
  const invitations = await state.find({ type: 'invite' });
494
516
 
495
- return invitations.map((d) => ({
517
+ return invitations.filter(filter || ((x) => x.status !== 'success')).map((d) => ({
496
518
  // eslint-disable-next-line no-underscore-dangle
497
519
  inviteId: d._id,
498
520
  role: d.role,
@@ -500,6 +522,8 @@ class TeamAPI extends EventEmitter {
500
522
  expireDate: new Date(d.expireDate).toString(),
501
523
  inviter: d.inviter,
502
524
  teamDid: d.teamDid,
525
+ status: d.status,
526
+ receiver: d.receiver,
503
527
  }));
504
528
  }
505
529
 
@@ -516,7 +540,7 @@ class TeamAPI extends EventEmitter {
516
540
  return true;
517
541
  }
518
542
 
519
- async processInvitation({ teamDid, inviteId }) {
543
+ async checkInvitation({ teamDid, inviteId }) {
520
544
  const state = await this.getSessionState(teamDid);
521
545
 
522
546
  const invitation = await state.read(inviteId);
@@ -538,16 +562,33 @@ class TeamAPI extends EventEmitter {
538
562
  throw new Error(`Role does not exist: ${role}`);
539
563
  }
540
564
 
541
- await state.end(inviteId);
542
-
543
- logger.info('Invitation session completed successfully', { inviteId, role });
544
-
545
565
  return {
546
566
  role,
547
567
  remark,
548
568
  };
549
569
  }
550
570
 
571
+ async closeInvitation({ teamDid, inviteId, status, receiver, timeout = 30 * 1000 }) {
572
+ const state = await this.getSessionState(teamDid);
573
+
574
+ const invitation = await state.read(inviteId);
575
+
576
+ if (!invitation) {
577
+ throw new Error(`The invitation does not exist: ${inviteId}`);
578
+ }
579
+
580
+ await state.update(inviteId, { status, receiver });
581
+
582
+ setTimeout(async () => {
583
+ try {
584
+ logger.info('Invitation session closed', { inviteId });
585
+ await state.end(inviteId);
586
+ } catch (error) {
587
+ logger.error('close invitation failed', { error });
588
+ }
589
+ }, timeout);
590
+ }
591
+
551
592
  // Issue Passport
552
593
 
553
594
  async createPassportIssuance({ teamDid, ownerDid, name }) {
@@ -134,6 +134,8 @@ const handleInstanceInStore = require('../../util/public-to-store');
134
134
  const { getNFTState, getServerDidDomain } = require('../../util');
135
135
  const { BlockletRuntimeMonitor } = require('../../monitor/blocklet-runtime-monitor');
136
136
  const getHistoryList = require('../../monitor/get-history-list');
137
+ const { SpacesBackup } = require('../storage/backup/spaces');
138
+ const { SpacesRestore } = require('../storage/restore/spaces');
137
139
  const installFromBackup = require('./helper/install-from-backup');
138
140
 
139
141
  const {
@@ -609,6 +611,35 @@ class BlockletManager extends BaseBlockletManager {
609
611
  return blocklet;
610
612
  }
611
613
 
614
+ /**
615
+ *
616
+ *
617
+ * @param {import('@abtnode/client').BackupToSpacesParams} input
618
+ * @memberof BlockletManager
619
+ */
620
+ async backupToSpaces(input) {
621
+ const spacesBackup = new SpacesBackup(input);
622
+ await spacesBackup.backup();
623
+ }
624
+
625
+ /**
626
+ *
627
+ *
628
+ * @param {import('@abtnode/client').RequestRestoreFromSpacesInput} input
629
+ * @memberof BlockletManager
630
+ */
631
+ async restoreFromSpaces(input) {
632
+ const spacesRestore = new SpacesRestore(input);
633
+ await spacesRestore.restore();
634
+
635
+ // FIXME: 需要改成队列执行,本次失败,下次还需要重试,页面刷新后也能知道最新的状态
636
+ await this._installFromBackup({
637
+ url: `file://${spacesRestore.blockletRestoreDir}`,
638
+ blockletSecretKey: spacesRestore.blockletWallet.secretKey,
639
+ moveDir: true,
640
+ });
641
+ }
642
+
612
643
  async restart({ did }, context) {
613
644
  logger.info('restart blocklet', { did });
614
645
 
@@ -1617,6 +1648,11 @@ class BlockletManager extends BaseBlockletManager {
1617
1648
  * /data
1618
1649
  * /blocklet.json
1619
1650
  * /blocklet_extras.json
1651
+ * @see Blocklet 端到端备份方案的设计与实现(一期) https://team.arcblock.io/comment/discussions/e62084d5-fafa-489e-91d5-defcd6e93223
1652
+ * @param {{ url: string, blockletSecretKey: string, moveDir: boolean}} [{ url }={}]
1653
+ * @param {Record<string, string>} [context={}]
1654
+ * @return {Promise<any>}
1655
+ * @memberof BlockletManager
1620
1656
  */
1621
1657
  async _installFromBackup({ url, blockletSecretKey, moveDir } = {}, context = {}) {
1622
1658
  return installFromBackup({ url, blockletSecretKey, moveDir, context, manager: this, states });
@@ -2090,10 +2126,9 @@ class BlockletManager extends BaseBlockletManager {
2090
2126
 
2091
2127
  if (controller?.nftId) {
2092
2128
  // sometimes nedb will throw error if use states.blocklet.count({ ['controller.nftId']: controller.nftId })
2093
- const blocklets = await states.blocklet.find({}, { controller: 1 });
2094
- const count = blocklets.filter((x) => x.controller?.nftId === controller.nftId).length;
2129
+ const installedCount = await states.blockletExtras.count({ 'controller.nftId': controller.nftId });
2095
2130
 
2096
- if (count >= (controller.appMaxCount || 1)) {
2131
+ if (installedCount >= (controller.appMaxCount || 1)) {
2097
2132
  throw new Error(
2098
2133
  `You can only install ${controller.appMaxCount} blocklet with this credential: ${controller.nftId}`
2099
2134
  );
@@ -2691,7 +2726,6 @@ class BlockletManager extends BaseBlockletManager {
2691
2726
  source,
2692
2727
  deployedFrom,
2693
2728
  children,
2694
- controller,
2695
2729
  });
2696
2730
 
2697
2731
  await validateBlocklet(blocklet);
@@ -3798,7 +3832,8 @@ class BlockletManager extends BaseBlockletManager {
3798
3832
 
3799
3833
  async _createNotification(did, notification) {
3800
3834
  try {
3801
- const isExternal = await states.blocklet.isExternalBlocklet(did);
3835
+ const extra = await states.blockletExtras.getMeta(did);
3836
+ const isExternal = !!extra?.controller;
3802
3837
 
3803
3838
  if (isExternal) {
3804
3839
  return;
@@ -0,0 +1,27 @@
1
+ const { removeSync, outputJsonSync } = require('fs-extra');
2
+ const { join } = require('path');
3
+ const states = require('../../../states');
4
+ const { BaseBackup } = require('./base');
5
+
6
+ /**
7
+ *
8
+ *
9
+ * @class AuditLogBackup
10
+ */
11
+ class AuditLogBackup extends BaseBackup {
12
+ filename = 'audit-log.json';
13
+
14
+ async export() {
15
+ /**
16
+ * @type {import('@abtnode/client').AuditLog}
17
+ */
18
+ const auditLogs = await states.auditLog.find({
19
+ scope: this.blocklet.meta.did,
20
+ });
21
+
22
+ removeSync(join(this.blockletBackupDir, this.filename));
23
+ outputJsonSync(join(this.blockletBackupDir, this.filename), auditLogs);
24
+ }
25
+ }
26
+
27
+ module.exports = { AuditLogBackup };
@@ -0,0 +1,63 @@
1
+ class BaseBackup {
2
+ /**
3
+ *
4
+ * @type {import('./spaces').SpaceBackupInput}
5
+ * @memberof BaseBackup
6
+ */
7
+ input;
8
+
9
+ /**
10
+ * @description blocklet state 对象
11
+ * @type {import('@abtnode/client').BlockletState}
12
+ * @memberof BaseBackup
13
+ */
14
+ blocklet;
15
+
16
+ /**
17
+ * @description 当前 blocklet 的数据目录
18
+ * @type {string}
19
+ * @memberof BaseBackup
20
+ */
21
+ blockletBackupDir;
22
+
23
+ /**
24
+ *
25
+ * @description spaces 的 endpoint
26
+ * @type {import('@ocap/wallet').WalletObject}
27
+ * @memberof BaseBackup
28
+ */
29
+ blockletWallet;
30
+
31
+ /**
32
+ *
33
+ * @description server 的数据目录
34
+ * @type {string}
35
+ * @memberof BaseBackup
36
+ */
37
+ serverDataDir;
38
+
39
+ constructor(input) {
40
+ this.input = input;
41
+ }
42
+
43
+ /**
44
+ *
45
+ *
46
+ * @param {import('./spaces').SpacesBackup} spacesBackup
47
+ * @memberof BaseBackup
48
+ */
49
+ ensureParams(spacesBackup) {
50
+ this.blocklet = spacesBackup.blocklet;
51
+ this.serverDataDir = spacesBackup.serverDataDir;
52
+ this.blockletBackupDir = spacesBackup.blockletBackupDir;
53
+ this.blockletWallet = spacesBackup.blockletWallet;
54
+ }
55
+
56
+ async export() {
57
+ throw new Error('not implemented');
58
+ }
59
+ }
60
+
61
+ module.exports = {
62
+ BaseBackup,
63
+ };
@@ -0,0 +1,105 @@
1
+ const { removeSync, outputJsonSync, readFileSync } = require('fs-extra');
2
+ const { isEmpty, cloneDeep } = require('lodash');
3
+ const { join } = require('path');
4
+ const security = require('@abtnode/util/lib/security');
5
+ const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
6
+ const states = require('../../../states');
7
+ const { BaseBackup } = require('./base');
8
+
9
+ class BlockletExtrasBackup extends BaseBackup {
10
+ filename = 'blocklet-extras.json';
11
+
12
+ async export() {
13
+ const blockletExtra = await this.getBlockletExtra();
14
+ removeSync(join(this.blockletBackupDir, this.filename));
15
+ outputJsonSync(join(this.blockletBackupDir, this.filename), blockletExtra);
16
+ }
17
+
18
+ /**
19
+ *
20
+ * @description
21
+ * @return {Promise<import('@abtnode/client').BlockletState>}
22
+ * @memberof BlockletExtrasBackup
23
+ */
24
+ async getBlockletExtra() {
25
+ /**
26
+ * @type {import('@abtnode/client').BlockletState}
27
+ */
28
+ const blockletExtra = await states.blockletExtras.findOne({
29
+ did: this.blocklet.meta.did,
30
+ });
31
+ if (isEmpty(blockletExtra)) {
32
+ throw new Error('blockletExtra cannot be empty');
33
+ }
34
+
35
+ return this.cleanDate(blockletExtra);
36
+ }
37
+
38
+ /**
39
+ *
40
+ * @description 清理数据并加密
41
+ * @param {import('@abtnode/client').BlockletState} blockletExtraInput
42
+ * @return {Promise<void>}
43
+ * @memberof BlockletExtrasBackup
44
+ */
45
+ async cleanDate(blockletExtraInput) {
46
+ const blockletExtra = cloneDeep(blockletExtraInput);
47
+
48
+ const queue = [blockletExtra];
49
+ while (queue.length) {
50
+ const currentBlockletExtra = queue.pop();
51
+
52
+ // 删除父 blocklet 的某些数据
53
+ if (currentBlockletExtra._id) {
54
+ delete currentBlockletExtra._id;
55
+ delete currentBlockletExtra.createdAt;
56
+ delete currentBlockletExtra.updatedAt;
57
+ }
58
+
59
+ // 加解密
60
+ this.useBlockletEncryptConfigs(currentBlockletExtra.configs);
61
+
62
+ if (currentBlockletExtra?.children) {
63
+ queue.push(...currentBlockletExtra.children);
64
+ }
65
+ }
66
+
67
+ return blockletExtra;
68
+ }
69
+
70
+ /**
71
+ *
72
+ * @description 清理数据并加密
73
+ * @param {import('@abtnode/client').ConfigEntry[]} configs
74
+ * @return {void}
75
+ * @memberof BlockletExtrasBackup
76
+ */
77
+ useBlockletEncryptConfigs(configs) {
78
+ // 准备加解密所需的参数
79
+ // @see: https://github.com/ArcBlock/blocklet-server/blob/f561ba7290285f2e23dccb6d5323eb4d43c3cc3e/core/state/lib/index.js#L59
80
+ const dek = readFileSync(join(this.serverDataDir, '.sock'));
81
+
82
+ for (const config of configs) {
83
+ // 置空 blocklet 的密钥,但是保留其他属性,这很重要
84
+ if (config.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK) {
85
+ config.value = '';
86
+ } else if (config.secure) {
87
+ // secure 为 true 的配置才需要被加密保存上次到 did spaces
88
+
89
+ const encryptByServer = config.value;
90
+ // 先从 blocklet server 解密
91
+ // @see https://github.com/ArcBlock/blocklet-server/blob/f40338168a66893f325464cea79ae54c43f623b1/core/state/lib/blocklet/extras.js#L139
92
+ const decryptByServer = security.decrypt(encryptByServer, this.blocklet.meta.did, dek);
93
+ // 再用 blocklet secret 加密,然后才可以上传到 spaces
94
+ const encryptByBlocklet = security.encrypt(
95
+ decryptByServer,
96
+ this.blockletWallet.address,
97
+ Buffer.from(this.blockletWallet.secretKey)
98
+ );
99
+ config.value = encryptByBlocklet;
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ module.exports = { BlockletExtrasBackup };
@@ -0,0 +1,53 @@
1
+ const { removeSync, outputJsonSync } = require('fs-extra');
2
+ const { cloneDeep } = require('lodash');
3
+ const { join } = require('path');
4
+ const { BaseBackup } = require('./base');
5
+
6
+ class BlockletBackup extends BaseBackup {
7
+ filename = 'blocklet.json';
8
+
9
+ async export() {
10
+ const blocklet = await this.cleanData();
11
+ removeSync(join(this.blockletBackupDir, this.filename));
12
+ outputJsonSync(join(this.blockletBackupDir, this.filename), blocklet);
13
+ }
14
+
15
+ /**
16
+ * @description 清理数据
17
+ * @see blocklet.db 中需要删除哪些字段呢? https://github.com/ArcBlock/blocklet-server/issues/6120#issuecomment-1383798348
18
+ * @return {Promise<void>}
19
+ * @memberof BlockletBackup
20
+ */
21
+ async cleanData() {
22
+ const originalBlocklet = cloneDeep(this.blocklet);
23
+
24
+ /** @type {import('@abtnode/client').ComponentState[]} */
25
+ const queue = [originalBlocklet];
26
+
27
+ // 广度优先遍历
28
+ while (queue.length) {
29
+ const currentBlocklet = queue.pop();
30
+
31
+ // 父组件才需要删除的属性
32
+ if (currentBlocklet._id) {
33
+ delete currentBlocklet._id;
34
+ delete currentBlocklet.createdAt;
35
+ delete currentBlocklet.startedAt;
36
+ delete currentBlocklet.installedAt;
37
+ }
38
+
39
+ // 子组件和父组件都需要删除的属性
40
+ delete currentBlocklet.status;
41
+ delete currentBlocklet.ports;
42
+ delete currentBlocklet.environments;
43
+
44
+ if (currentBlocklet.children) {
45
+ queue.push(...currentBlocklet.children);
46
+ }
47
+ }
48
+
49
+ return originalBlocklet;
50
+ }
51
+ }
52
+
53
+ module.exports = { BlockletBackup };
@@ -0,0 +1,80 @@
1
+ const { removeSync, existsSync, ensureDirSync, createWriteStream } = require('fs-extra');
2
+ const { join, dirname } = require('path');
3
+ const archiver = require('archiver');
4
+ const { BaseBackup } = require('./base');
5
+
6
+ class BlockletsBackup extends BaseBackup {
7
+ /**
8
+ *
9
+ * @returns {Promise<void>}
10
+ * @memberof BlockletsBackup
11
+ */
12
+ async export() {
13
+ const blockletMetas = this.getBlockletMetas(this.blocklet);
14
+ const serverBlockletsDir = join(this.serverDataDir, 'blocklets');
15
+
16
+ const dirs = [];
17
+ for (const blockletMeta of blockletMetas) {
18
+ const sourceDir = join(serverBlockletsDir, blockletMeta.name, blockletMeta.version);
19
+ const destDir = join(blockletMeta.name, blockletMeta.version);
20
+ dirs.push({ sourceDir, destDir });
21
+ }
22
+
23
+ await this.dirsToZip(dirs, join(this.blockletBackupDir, 'blocklets.zip'));
24
+ }
25
+
26
+ /**
27
+ *
28
+ *
29
+ * @param {import('@abtnode/client').BlockletState} blocklet
30
+ * @returns {Array<{name: string, version: string}>}
31
+ * @memberof BlockletsBackup
32
+ */
33
+ getBlockletMetas(blocklet) {
34
+ if (!blocklet?.meta?.bundleName || !blocklet?.meta?.version) {
35
+ return [];
36
+ }
37
+
38
+ const metas = [];
39
+ metas.push({
40
+ name: blocklet.meta.bundleName,
41
+ version: blocklet.meta.version,
42
+ });
43
+
44
+ for (const child of blocklet.children) {
45
+ metas.push(...this.getBlockletMetas(child));
46
+ }
47
+
48
+ return metas;
49
+ }
50
+
51
+ /**
52
+ * @param {Array<{sourceDir: string, destDir: string}>} dirs: /some/folder/to/compress
53
+ * @param {String} zipPath: /path/to/created.zip
54
+ * @returns {Promise}
55
+ * @memberof BlockletsBackup
56
+ */
57
+ async dirsToZip(dirs, zipPath) {
58
+ ensureDirSync(dirname(zipPath));
59
+ if (existsSync(zipPath)) {
60
+ removeSync(zipPath);
61
+ }
62
+
63
+ const archive = archiver('zip', { zlib: { level: 9 } });
64
+ const stream = createWriteStream(zipPath);
65
+
66
+ return new Promise((resolve, reject) => {
67
+ archive.on('error', (err) => reject(err));
68
+ stream.on('close', () => resolve());
69
+
70
+ for (const dir of dirs) {
71
+ archive.directory(dir.sourceDir, dir.destDir);
72
+ }
73
+
74
+ archive.pipe(stream);
75
+ archive.finalize();
76
+ });
77
+ }
78
+ }
79
+
80
+ module.exports = { BlockletsBackup };
@@ -0,0 +1,19 @@
1
+ const { copySync } = require('fs-extra');
2
+ const { join } = require('path');
3
+ const { BaseBackup } = require('./base');
4
+
5
+ class DataBackup extends BaseBackup {
6
+ /**
7
+ *
8
+ * @returns {Promise<void>}
9
+ * @memberof BlockletsBackup
10
+ */
11
+ async export() {
12
+ const blockletDataDir = join(this.serverDataDir, 'data', this.blocklet.meta.name);
13
+ const blockletBackupDataDir = join(this.blockletBackupDir, 'data');
14
+
15
+ copySync(blockletDataDir, blockletBackupDataDir, { overwrite: true });
16
+ }
17
+ }
18
+
19
+ module.exports = { DataBackup };
@@ -0,0 +1,23 @@
1
+ const { ensureDirSync, copySync, statSync } = require('fs-extra');
2
+ const { join } = require('path');
3
+ const { BaseBackup } = require('./base');
4
+
5
+ class LogsBackup extends BaseBackup {
6
+ async export() {
7
+ const sourceLogsDir = join(this.serverDataDir, 'logs', this.blocklet.meta.name);
8
+ ensureDirSync(sourceLogsDir);
9
+
10
+ const targetLogsDir = join(this.blockletBackupDir, 'logs');
11
+
12
+ copySync(sourceLogsDir, targetLogsDir, {
13
+ overwrite: true,
14
+ filter: (src) => {
15
+ return !statSync(src).isSymbolicLink();
16
+ },
17
+ });
18
+ }
19
+ }
20
+
21
+ module.exports = {
22
+ LogsBackup,
23
+ };
@@ -0,0 +1,19 @@
1
+ const { removeSync, outputJsonSync } = require('fs-extra');
2
+ const { join } = require('path');
3
+ const states = require('../../../states');
4
+ const { BaseBackup } = require('./base');
5
+
6
+ class RoutingRuleBackup extends BaseBackup {
7
+ filename = 'routing_rule.json';
8
+
9
+ async export() {
10
+ const routingRule = await states.site.findOne({
11
+ domain: `${this.blocklet.meta.did}.blocklet-domain-group`,
12
+ });
13
+
14
+ removeSync(join(this.blockletBackupDir, this.filename));
15
+ outputJsonSync(join(this.blockletBackupDir, this.filename), routingRule);
16
+ }
17
+ }
18
+
19
+ module.exports = { RoutingRuleBackup };
@@ -0,0 +1,189 @@
1
+ /**
2
+ * @typedef {{
3
+ * did?: string
4
+ * }} SpaceBackupInput
5
+ */
6
+
7
+ const { isValid } = require('@arcblock/did');
8
+ const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
9
+ const { getBlockletInfo } = require('@blocklet/meta');
10
+ const { SpaceClient, SyncFolderPushCommand } = require('@did-space/client');
11
+ const { ensureDirSync, existsSync, rmdirSync, removeSync } = require('fs-extra');
12
+ const { isEmpty } = require('lodash');
13
+ const { join } = require('path');
14
+ const states = require('../../../states');
15
+ const { AuditLogBackup } = require('./audit-log');
16
+ const { BlockletBackup } = require('./blocklet');
17
+ const { BlockletExtrasBackup } = require('./blocklet-extras');
18
+ const { BlockletsBackup } = require('./blocklets');
19
+ const { DataBackup } = require('./data');
20
+ const { LogsBackup } = require('./logs');
21
+ const { RoutingRuleBackup } = require('./routing-rule');
22
+
23
+ class SpacesBackup {
24
+ /**
25
+ *
26
+ * @type {SpaceBackupInput}
27
+ * @memberof SpacesBackup
28
+ */
29
+ input;
30
+
31
+ /**
32
+ * @description blocklet state 对象
33
+ * @type {import('@abtnode/client').BlockletState}
34
+ * @memberof SpacesBackup
35
+ */
36
+ blocklet;
37
+
38
+ /**
39
+ * @description 当前 blocklet 的数据目录
40
+ * @type {string}
41
+ * @memberof SpacesBackup
42
+ */
43
+ blockletBackupDir;
44
+
45
+ /**
46
+ *
47
+ * @description server 的数据目录
48
+ * @type {string}
49
+ * @memberof SpacesBackup
50
+ */
51
+ serverDataDir;
52
+
53
+ /**
54
+ *
55
+ * @description spaces 的 endpoint
56
+ * @type {string}
57
+ * @memberof SpacesBackup
58
+ */
59
+ spacesEndpoint;
60
+
61
+ /**
62
+ *
63
+ * @description spaces 的 endpoint
64
+ * @type {import('@ocap/wallet').WalletObject}
65
+ * @memberof SpacesBackup
66
+ */
67
+ blockletWallet;
68
+
69
+ storages;
70
+
71
+ /**
72
+ *
73
+ * @param {SpaceBackupInput} input
74
+ * @memberof SpacesBackup
75
+ */
76
+ constructor(input) {
77
+ this.verify(input);
78
+ this.input = input;
79
+ this.storages = [
80
+ new AuditLogBackup(this.input),
81
+ new LogsBackup(this.input),
82
+ new BlockletBackup(this.input),
83
+ new BlockletsBackup(this.input),
84
+ new BlockletExtrasBackup(this.input),
85
+ new RoutingRuleBackup(this.input),
86
+ new DataBackup(this.input),
87
+ ];
88
+ }
89
+
90
+ /**
91
+ * @param {SpaceBackupInput} input
92
+ * @returns {void}
93
+ * @memberof SpacesBackup
94
+ */
95
+ verify(input) {
96
+ if (isEmpty(input?.did) || !isValid(input?.did)) {
97
+ throw new Error(`input.did(${input?.did}) is not a valid did`);
98
+ }
99
+ }
100
+
101
+ /**
102
+ *
103
+ * @returns {Promise<void>}
104
+ * @memberof SpacesBackup
105
+ */
106
+ async backup() {
107
+ await this.initialize();
108
+ await this.export();
109
+ await this.syncToSpaces();
110
+ await this.destroy();
111
+ }
112
+
113
+ async initialize() {
114
+ this.blocklet = await states.blocklet.findOne({
115
+ appDid: this.input.did,
116
+ });
117
+ if (isEmpty(this.blocklet)) {
118
+ throw new Error('blocklet cannot be empty');
119
+ }
120
+
121
+ this.serverDataDir = process.env.ABT_NODE_DATA_DIR;
122
+
123
+ this.blockletBackupDir = join(process.env.ABT_NODE_DATA_DIR, 'tmp/backup', this.blocklet.meta.did);
124
+ if (existsSync(this.blockletBackupDir)) {
125
+ rmdirSync(this.blockletBackupDir, { recursive: true });
126
+ }
127
+ ensureDirSync(this.blockletBackupDir);
128
+
129
+ this.spacesEndpoint = this.blocklet.environments.find(
130
+ (e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT
131
+ )?.value;
132
+ if (isEmpty(this.spacesEndpoint)) {
133
+ throw new Error('spacesEndpoint cannot be empty');
134
+ }
135
+
136
+ this.blockletWallet = await this.getBlockletWallet();
137
+ }
138
+
139
+ async export() {
140
+ await Promise.all(
141
+ this.storages.map((storage) => {
142
+ storage.ensureParams(this);
143
+ return storage.export();
144
+ })
145
+ );
146
+ }
147
+
148
+ async syncToSpaces() {
149
+ const wallet = await this.getBlockletWallet();
150
+ const spaceClient = new SpaceClient({
151
+ endpoint: this.spacesEndpoint,
152
+ wallet,
153
+ });
154
+
155
+ // FIXME: 在 Spaces 里面预览出 blocklet 的样式,需要规划一个好的数据结构
156
+ const { errorCount } = await spaceClient.send(
157
+ new SyncFolderPushCommand({
158
+ source: join(this.blockletBackupDir, '/'),
159
+ target: join('.did-objects', this.blocklet.appDid),
160
+ debug: true,
161
+ retryCount: 3,
162
+ filter: (object) => {
163
+ return object.name !== '.DS_Store';
164
+ },
165
+ })
166
+ );
167
+
168
+ if (errorCount !== 0) {
169
+ throw new Error(`Sync to spaces encountered ${errorCount} error`);
170
+ }
171
+ }
172
+
173
+ async getBlockletWallet() {
174
+ const blockletInfo = await states.blocklet.getBlocklet(this.input.did);
175
+ const nodeInfo = await states.node.read();
176
+ const { wallet } = getBlockletInfo(blockletInfo, nodeInfo.sk);
177
+ return wallet;
178
+ }
179
+
180
+ async destroy() {
181
+ if (existsSync(this.blockletBackupDir)) {
182
+ removeSync(this.blockletBackupDir);
183
+ }
184
+ }
185
+ }
186
+
187
+ module.exports = {
188
+ SpacesBackup,
189
+ };
@@ -0,0 +1,55 @@
1
+ class BaseRestore {
2
+ /**
3
+ *
4
+ * @type {import('./spaces').SpaceRestoreInput}
5
+ * @memberof BaseRestore
6
+ */
7
+ input;
8
+
9
+ /**
10
+ * @description 当前 blocklet 的数据目录
11
+ * @type {string}
12
+ * @memberof BaseRestore
13
+ */
14
+ blockletRestoreDir;
15
+
16
+ /**
17
+ *
18
+ * @description spaces 的 endpoint
19
+ * @type {import('@ocap/wallet').WalletObject}
20
+ * @memberof BaseRestore
21
+ */
22
+ blockletWallet;
23
+
24
+ /**
25
+ *
26
+ * @description server 的数据目录
27
+ * @type {string}
28
+ * @memberof BaseRestore
29
+ */
30
+ serverDataDir;
31
+
32
+ constructor(input) {
33
+ this.input = input;
34
+ }
35
+
36
+ /**
37
+ *
38
+ *
39
+ * @param {import('./spaces').SpacesRestore} spaces
40
+ * @memberof BaseRestore
41
+ */
42
+ ensureParams(spaces) {
43
+ this.blockletRestoreDir = spaces.blockletRestoreDir;
44
+ this.blockletWallet = spaces.blockletWallet;
45
+ this.serverDataDir = spaces.serverDataDir;
46
+ }
47
+
48
+ async import() {
49
+ throw new Error('not implemented');
50
+ }
51
+ }
52
+
53
+ module.exports = {
54
+ BaseRestore,
55
+ };
@@ -0,0 +1,84 @@
1
+ const { removeSync, outputJsonSync, readJSONSync } = require('fs-extra');
2
+ const { cloneDeep } = require('lodash');
3
+ const { join } = require('path');
4
+ const security = require('@abtnode/util/lib/security');
5
+ const { BaseRestore } = require('./base');
6
+
7
+ class BlockletExtrasRestore extends BaseRestore {
8
+ filename = 'blocklet-extras.json';
9
+
10
+ async import() {
11
+ const blockletExtra = await this.getBlockletExtra();
12
+ removeSync(join(this.blockletRestoreDir, this.filename));
13
+ outputJsonSync(join(this.blockletRestoreDir, this.filename), blockletExtra);
14
+ }
15
+
16
+ /**
17
+ *
18
+ * @description
19
+ * @return {Promise<import('@abtnode/client').BlockletState>}
20
+ * @memberof BlockletExtrasRestore
21
+ */
22
+ async getBlockletExtra() {
23
+ /**
24
+ * @type {import('@abtnode/client').BlockletState}
25
+ */
26
+ const blockletExtra = readJSONSync(join(this.blockletRestoreDir, this.filename));
27
+
28
+ return this.cleanData(blockletExtra);
29
+ }
30
+
31
+ /**
32
+ *
33
+ * @description 清理数据并加密
34
+ * @param {import('@abtnode/client').BlockletState} blockletExtraInput
35
+ * @return {Promise<void>}
36
+ * @memberof BlockletExtrasRestore
37
+ */
38
+ async cleanData(blockletExtraInput) {
39
+ const blockletExtra = cloneDeep(blockletExtraInput);
40
+
41
+ const queue = [blockletExtra];
42
+ while (queue.length) {
43
+ const currentBlockletExtra = queue.pop();
44
+
45
+ // 加解密
46
+ this.useBlockletDecryptConfigs(currentBlockletExtra.configs);
47
+
48
+ if (currentBlockletExtra?.children) {
49
+ queue.push(...currentBlockletExtra.children);
50
+ }
51
+ }
52
+
53
+ return blockletExtra;
54
+ }
55
+
56
+ /**
57
+ *
58
+ * @description 清理数据并加密
59
+ * @param {import('@abtnode/client').ConfigEntry[]} configs
60
+ * @return {void}
61
+ * @memberof BlockletExtrasRestore
62
+ */
63
+ useBlockletDecryptConfigs(configs) {
64
+ for (const config of configs) {
65
+ // secure 为 true 的配置才需要被加密保存上次到 did spaces
66
+ if (config.secure) {
67
+ const encryptByBlocklet = config.value;
68
+
69
+ // 再用 blocklet secret 加密,然后才可以上传到 spaces
70
+ const decryptByBlocklet = security.decrypt(
71
+ encryptByBlocklet,
72
+ this.blockletWallet.address,
73
+ Buffer.from(this.blockletWallet.secretKey)
74
+ );
75
+
76
+ config.value = decryptByBlocklet;
77
+ }
78
+ }
79
+
80
+ return configs;
81
+ }
82
+ }
83
+
84
+ module.exports = { BlockletExtrasRestore };
@@ -0,0 +1,24 @@
1
+ const { existsSync, removeSync } = require('fs-extra');
2
+ const { join } = require('path');
3
+ const StreamZip = require('node-stream-zip');
4
+ const { BaseRestore } = require('./base');
5
+
6
+ class BlockletsRestore extends BaseRestore {
7
+ filename = 'blocklets.zip';
8
+
9
+ async import() {
10
+ const blockletZipPath = join(this.blockletRestoreDir, this.filename);
11
+
12
+ if (!existsSync(blockletZipPath)) {
13
+ throw new Error(`file not found: ${blockletZipPath}`);
14
+ }
15
+
16
+ // eslint-disable-next-line new-cap
17
+ const zip = new StreamZip.async({ file: blockletZipPath });
18
+ await zip.extract(null, join(this.blockletRestoreDir, 'blocklets'));
19
+ await zip.close();
20
+ removeSync(blockletZipPath);
21
+ }
22
+ }
23
+
24
+ module.exports = { BlockletsRestore };
@@ -0,0 +1,137 @@
1
+ /**
2
+ * @typedef {{
3
+ * endpoint: string;
4
+ * blockletSecretKey: string;
5
+ * }} SpaceRestoreInput
6
+ */
7
+
8
+ const { SpaceClient, SyncFolderPullCommand } = require('@did-space/client');
9
+ const { types } = require('@ocap/mcrypto');
10
+ const { fromSecretKey, WalletType } = require('@ocap/wallet');
11
+ const { ensureDirSync, existsSync, rmdirSync } = require('fs-extra');
12
+ const { join } = require('path');
13
+ const validUrl = require('valid-url');
14
+ const { BlockletExtrasRestore } = require('./blocklet-extras');
15
+ const { BlockletsRestore } = require('./blocklets');
16
+
17
+ class SpacesRestore {
18
+ /**
19
+ *
20
+ * @type {SpaceRestoreInput}
21
+ * @memberof SpacesRestore
22
+ */
23
+ input;
24
+
25
+ /**
26
+ * @description 当前 blocklet 的数据目录
27
+ * @type {string}
28
+ * @memberof SpacesRestore
29
+ */
30
+ blockletRestoreDir;
31
+
32
+ /**
33
+ *
34
+ * @description server 的数据目录
35
+ * @type {string}
36
+ * @memberof SpacesRestore
37
+ */
38
+ serverDataDir;
39
+
40
+ /**
41
+ *
42
+ * @description spaces 的 endpoint
43
+ * @type {import('@ocap/wallet').WalletObject}
44
+ * @memberof SpacesRestore
45
+ */
46
+ blockletWallet;
47
+
48
+ storages;
49
+
50
+ /**
51
+ *
52
+ * @param {SpaceRestoreInput} input
53
+ * @memberof SpacesRestore
54
+ */
55
+ constructor(input) {
56
+ this.verify(input);
57
+ this.input = input;
58
+ this.storages = [new BlockletExtrasRestore(this.input), new BlockletsRestore(this.input)];
59
+ }
60
+
61
+ /**
62
+ *
63
+ * @param {SpaceRestoreInput} input
64
+ * @returns {void}
65
+ * @memberof SpacesRestore
66
+ */
67
+ verify(input) {
68
+ if (!validUrl.isWebUri(input.endpoint)) {
69
+ throw new Error(`endpoint(${input.endpoint}) must be a WebUri`);
70
+ }
71
+ }
72
+
73
+ async initialize() {
74
+ this.serverDataDir = process.env.ABT_NODE_DATA_DIR;
75
+ this.blockletWallet = await this.getBlockletWallet();
76
+
77
+ this.blockletRestoreDir = join(process.env.ABT_NODE_DATA_DIR, 'tmp/restore', this.blockletWallet.address);
78
+ if (existsSync(this.blockletRestoreDir)) {
79
+ rmdirSync(this.blockletRestoreDir, { recursive: true });
80
+ }
81
+ ensureDirSync(this.blockletRestoreDir);
82
+ }
83
+
84
+ /**
85
+ *
86
+ * @returns {Promise<void>}
87
+ * @memberof SpacesRestore
88
+ */
89
+ async restore() {
90
+ await this.initialize();
91
+ await this.syncFromSpaces();
92
+ await this.import();
93
+ }
94
+
95
+ async getBlockletWallet() {
96
+ // @FIXME: blocklet 钱包类型如何得知呢?
97
+ const wallet = fromSecretKey(this.input.blockletSecretKey, WalletType({ role: types.RoleType.ROLE_APPLICATION }));
98
+
99
+ return wallet;
100
+ }
101
+
102
+ async syncFromSpaces() {
103
+ const { endpoint } = this.input;
104
+ const wallet = await this.getBlockletWallet();
105
+
106
+ const spaceClient = new SpaceClient({
107
+ endpoint,
108
+ wallet,
109
+ });
110
+
111
+ const { errorCount } = await spaceClient.send(
112
+ new SyncFolderPullCommand({
113
+ source: join('.did-objects', this.blockletWallet.address, '/'),
114
+ target: this.blockletRestoreDir,
115
+ debug: true,
116
+ retryCount: 3,
117
+ })
118
+ );
119
+
120
+ if (errorCount !== 0) {
121
+ throw new Error(`Sync from spaces encountered ${errorCount} error`);
122
+ }
123
+ }
124
+
125
+ async import() {
126
+ await Promise.all(
127
+ this.storages.map((storage) => {
128
+ storage.ensureParams(this);
129
+ return storage.import();
130
+ })
131
+ );
132
+ }
133
+ }
134
+
135
+ module.exports = {
136
+ SpacesRestore,
137
+ };
package/lib/index.js CHANGED
@@ -216,6 +216,8 @@ function ABTNode(options) {
216
216
  updateWhoCanAccess: blockletManager.updateWhoCanAccess.bind(blockletManager),
217
217
  updateComponentTitle: blockletManager.updateComponentTitle.bind(blockletManager),
218
218
  updateComponentMountPoint: blockletManager.updateComponentMountPoint.bind(blockletManager),
219
+ backupToSpaces: blockletManager.backupToSpaces.bind(blockletManager),
220
+ restoreFromSpaces: blockletManager.restoreFromSpaces.bind(blockletManager),
219
221
 
220
222
  // For diagnose purpose
221
223
  syncBlockletStatus: blockletManager.status.bind(blockletManager),
@@ -259,8 +261,9 @@ function ABTNode(options) {
259
261
  createTransferInvitation: teamAPI.createTransferInvitation.bind(teamAPI),
260
262
  getInvitation: teamAPI.getInvitation.bind(teamAPI),
261
263
  getInvitations: teamAPI.getInvitations.bind(teamAPI),
262
- processInvitation: teamAPI.processInvitation.bind(teamAPI),
264
+ checkInvitation: teamAPI.checkInvitation.bind(teamAPI),
263
265
  deleteInvitation: teamAPI.deleteInvitation.bind(teamAPI),
266
+ closeInvitation: teamAPI.closeInvitation.bind(teamAPI),
264
267
 
265
268
  // Account
266
269
  getUsers: teamAPI.getUsers.bind(teamAPI),
@@ -199,6 +199,10 @@ class BlockletExtrasState extends BaseState {
199
199
  return super.update({ did }, { $set: entity }, { upsert: true });
200
200
  }
201
201
 
202
+ async getMeta(did) {
203
+ return super.findOne({ did }, { did: 1, controller: 1, meta: 1 });
204
+ }
205
+
202
206
  async updateExpireInfo({ did, expiredAt } = {}) {
203
207
  const entity = { did, expiredAt };
204
208
  await validateExpiredInfo(entity);
@@ -23,7 +23,6 @@ const logger = require('@abtnode/logger')('state-blocklet');
23
23
  const BaseState = require('./base');
24
24
  const { checkDuplicateComponents, ensureMeta } = require('../util/blocklet');
25
25
  const { validateBlockletMeta } = require('../util');
26
- const { validateBlockletController } = require('../validators/blocklet');
27
26
 
28
27
  const lock = new Lock('blocklet-port-assign-lock');
29
28
 
@@ -122,21 +121,6 @@ class BlockletState extends BaseState {
122
121
  });
123
122
  }
124
123
 
125
- async isExternalBlocklet(did) {
126
- if (!did) {
127
- return false;
128
- }
129
-
130
- const exist = await this.findOne({
131
- $or: [{ 'meta.did': did }, { appDid: did }],
132
- controller: {
133
- $exists: true,
134
- },
135
- });
136
-
137
- return !!exist;
138
- }
139
-
140
124
  async getBlockletStatus(did) {
141
125
  return new Promise((resolve, reject) => {
142
126
  if (!did) {
@@ -212,7 +196,6 @@ class BlockletState extends BaseState {
212
196
  deployedFrom = '',
213
197
  mode = BLOCKLET_MODES.PRODUCTION,
214
198
  children: rawChildren = [],
215
- controller,
216
199
  } = {}) {
217
200
  return this.getBlocklet(meta.did).then(
218
201
  (exist) =>
@@ -251,13 +234,8 @@ class BlockletState extends BaseState {
251
234
  ports,
252
235
  environments: [],
253
236
  children,
254
- controller,
255
237
  };
256
238
 
257
- if (controller) {
258
- data.controller = await validateBlockletController(controller);
259
- }
260
-
261
239
  // add to db
262
240
  this.insert(data, (err, doc) => {
263
241
  lock.release();
@@ -88,6 +88,7 @@ const {
88
88
  findInterfacePortByName,
89
89
  validateBlockletMeta,
90
90
  prettyURL,
91
+ getNFTState,
91
92
  } = require('./index');
92
93
 
93
94
  const getComponentConfig = (meta) => meta.components || meta.children;
@@ -1392,6 +1393,11 @@ const getBlocklet = async ({
1392
1393
  blocklet.enablePassportIssuance = get(settings, 'enablePassportIssuance', true);
1393
1394
  blocklet.settings = settings || {};
1394
1395
 
1396
+ const extrasMeta = await states.blockletExtras.getMeta(blocklet.meta.did);
1397
+ if (extrasMeta) {
1398
+ blocklet.controller = extrasMeta.controller;
1399
+ }
1400
+
1395
1401
  blocklet.settings.storeList = blocklet.settings.storeList || [];
1396
1402
 
1397
1403
  if (!blocklet.settings.storeList.find((x) => x.url === BLOCKLET_STORE.url)) {
@@ -1491,7 +1497,11 @@ const getConfigFromPreferences = (blocklet) => {
1491
1497
 
1492
1498
  const consumeServerlessNFT = async ({ nftId, nodeInfo, blocklet }) => {
1493
1499
  try {
1494
- const { url } = nodeInfo.launcher;
1500
+ const state = await getNFTState(blocklet.controller.chainHost, nftId);
1501
+ if (!state) {
1502
+ throw new Error(`get nft state failed, chainHost: ${blocklet.controller.chainHost}, nftId: ${nftId}`);
1503
+ }
1504
+
1495
1505
  const type = WalletType({
1496
1506
  role: types.RoleType.ROLE_APPLICATION,
1497
1507
  pk: types.KeyType.ED25519,
@@ -1502,7 +1512,8 @@ const consumeServerlessNFT = async ({ nftId, nodeInfo, blocklet }) => {
1502
1512
 
1503
1513
  const body = { nftId, appURL };
1504
1514
 
1505
- const { data } = await axios.post(joinURL(url, '/api/serverless/consume'), body, {
1515
+ const { launcherUrl } = state.data.value;
1516
+ const { data } = await axios.post(joinURL(launcherUrl, '/api/serverless/consume'), body, {
1506
1517
  headers: {
1507
1518
  'x-sig': toBase58(wallet.sign(stableStringify(body))),
1508
1519
  },
@@ -1618,6 +1629,16 @@ const validateAppConfig = async (config, blockletDid, states) => {
1618
1629
  throw new Error(`${x.key}(${x.value}) is not a valid http address`);
1619
1630
  }
1620
1631
  }
1632
+
1633
+ if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACES_URL) {
1634
+ if (isEmpty(x.value)) {
1635
+ throw new Error(`${x.key} can not be empty`);
1636
+ }
1637
+
1638
+ if (!isUrl(x.value)) {
1639
+ throw new Error(`${x.key}(${x.value}) is not a valid http address`);
1640
+ }
1641
+ }
1621
1642
  };
1622
1643
 
1623
1644
  const checkDuplicateAppSk = async ({ sk, did, states }) => {
@@ -13,6 +13,7 @@ const getMultipleLangParams = (context) => ({
13
13
  const blockletController = Joi.object({
14
14
  nftId: Joi.DID().required(),
15
15
  nftOwner: Joi.DID().required(),
16
+ chainHost: Joi.string().uri().required(),
16
17
  appMaxCount: Joi.number().required().min(1),
17
18
  }).options({ stripUnknown: true });
18
19
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.8.64-beta-b3d407e0",
6
+ "version": "1.8.64-beta-0b5ede51",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,32 +19,33 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@abtnode/auth": "1.8.64-beta-b3d407e0",
23
- "@abtnode/certificate-manager": "1.8.64-beta-b3d407e0",
24
- "@abtnode/constant": "1.8.64-beta-b3d407e0",
25
- "@abtnode/cron": "1.8.64-beta-b3d407e0",
26
- "@abtnode/db": "1.8.64-beta-b3d407e0",
27
- "@abtnode/logger": "1.8.64-beta-b3d407e0",
28
- "@abtnode/queue": "1.8.64-beta-b3d407e0",
29
- "@abtnode/rbac": "1.8.64-beta-b3d407e0",
30
- "@abtnode/router-provider": "1.8.64-beta-b3d407e0",
31
- "@abtnode/static-server": "1.8.64-beta-b3d407e0",
32
- "@abtnode/timemachine": "1.8.64-beta-b3d407e0",
33
- "@abtnode/util": "1.8.64-beta-b3d407e0",
34
- "@arcblock/did": "1.18.36",
22
+ "@abtnode/auth": "1.8.64-beta-0b5ede51",
23
+ "@abtnode/certificate-manager": "1.8.64-beta-0b5ede51",
24
+ "@abtnode/constant": "1.8.64-beta-0b5ede51",
25
+ "@abtnode/cron": "1.8.64-beta-0b5ede51",
26
+ "@abtnode/db": "1.8.64-beta-0b5ede51",
27
+ "@abtnode/logger": "1.8.64-beta-0b5ede51",
28
+ "@abtnode/queue": "1.8.64-beta-0b5ede51",
29
+ "@abtnode/rbac": "1.8.64-beta-0b5ede51",
30
+ "@abtnode/router-provider": "1.8.64-beta-0b5ede51",
31
+ "@abtnode/static-server": "1.8.64-beta-0b5ede51",
32
+ "@abtnode/timemachine": "1.8.64-beta-0b5ede51",
33
+ "@abtnode/util": "1.8.64-beta-0b5ede51",
34
+ "@arcblock/did": "1.18.37",
35
35
  "@arcblock/did-motif": "^1.1.10",
36
- "@arcblock/did-util": "1.18.36",
37
- "@arcblock/event-hub": "1.18.36",
38
- "@arcblock/jwt": "^1.18.36",
36
+ "@arcblock/did-util": "1.18.37",
37
+ "@arcblock/event-hub": "1.18.37",
38
+ "@arcblock/jwt": "^1.18.37",
39
39
  "@arcblock/pm2-events": "^0.0.5",
40
- "@arcblock/vc": "1.18.36",
41
- "@blocklet/constant": "1.8.64-beta-b3d407e0",
42
- "@blocklet/meta": "1.8.64-beta-b3d407e0",
43
- "@blocklet/sdk": "1.8.64-beta-b3d407e0",
40
+ "@arcblock/vc": "1.18.37",
41
+ "@blocklet/constant": "1.8.64-beta-0b5ede51",
42
+ "@blocklet/meta": "1.8.64-beta-0b5ede51",
43
+ "@blocklet/sdk": "1.8.64-beta-0b5ede51",
44
+ "@did-space/client": "^0.1.66",
44
45
  "@fidm/x509": "^1.2.1",
45
- "@ocap/mcrypto": "1.18.36",
46
- "@ocap/util": "1.18.36",
47
- "@ocap/wallet": "1.18.36",
46
+ "@ocap/mcrypto": "1.18.37",
47
+ "@ocap/util": "1.18.37",
48
+ "@ocap/wallet": "1.18.37",
48
49
  "@slack/webhook": "^5.0.4",
49
50
  "archiver": "^5.3.1",
50
51
  "axios": "^0.27.2",
@@ -55,7 +56,7 @@
55
56
  "detect-port": "^1.5.1",
56
57
  "escape-string-regexp": "^4.0.0",
57
58
  "flat": "^5.0.2",
58
- "fs-extra": "^10.1.0",
59
+ "fs-extra": "^11.1.0",
59
60
  "get-port": "^5.1.1",
60
61
  "is-base64": "^1.1.0",
61
62
  "is-ip": "^3.1.0",
@@ -64,6 +65,7 @@
64
65
  "js-yaml": "^4.1.0",
65
66
  "lodash": "^4.17.21",
66
67
  "lru-cache": "^6.0.0",
68
+ "node-stream-zip": "^1.15.0",
67
69
  "p-limit": "^3.1.0",
68
70
  "pm2": "^5.2.0",
69
71
  "semver": "^7.3.8",
@@ -77,7 +79,8 @@
77
79
  "ua-parser-js": "^1.0.2",
78
80
  "unzipper": "^0.10.11",
79
81
  "url-join": "^4.0.1",
80
- "uuid": "7.0.3"
82
+ "uuid": "7.0.3",
83
+ "valid-url": "^1.0.9"
81
84
  },
82
85
  "devDependencies": {
83
86
  "compression": "^1.7.4",
@@ -85,5 +88,5 @@
85
88
  "express": "^4.18.2",
86
89
  "jest": "^27.5.1"
87
90
  },
88
- "gitHead": "e076a9cab4c15cfafb82d0aef9a66dd1b825ec75"
91
+ "gitHead": "fbb72fdf88ea44c949ea951b84ab0506805bda7d"
89
92
  }
@@ -1,5 +0,0 @@
1
- const { blockletController } = require('./util');
2
-
3
- module.exports = {
4
- validateBlockletController: (entity) => blockletController.validateAsync(entity),
5
- };