@abtnode/core 1.16.20-beta-e363262e → 1.16.20-beta-bb1cd034

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.
@@ -15,6 +15,7 @@ const { sign } = require('@arcblock/jwt');
15
15
  const sleep = require('@abtnode/util/lib/sleep');
16
16
  const getBlockletInfo = require('@blocklet/meta/lib/info');
17
17
  const joinUrl = require('url-join');
18
+ const { sendToUser } = require('@blocklet/sdk/lib/util/send-notification');
18
19
 
19
20
  const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager');
20
21
  const {
@@ -94,6 +95,7 @@ const pRetry = require('p-retry');
94
95
 
95
96
  const isFunction = require('lodash/isFunction');
96
97
  const { encode } = require('@abtnode/util/lib/base32');
98
+ const formatContext = require('@abtnode/util/lib/format-context');
97
99
  const { consumeServerlessNFT, consumeLauncherSession } = require('../../util/launcher');
98
100
  const util = require('../../util');
99
101
  const {
@@ -190,6 +192,7 @@ const {
190
192
  updateSelectedResources,
191
193
  } = require('../project');
192
194
  const { callFederated } = require('../../util/federated');
195
+ const { isDnsIpMappingCorrect } = require('../../util/ip');
193
196
 
194
197
  const { formatEnvironments, getBlockletMeta, validateOwner, isCLI } = util;
195
198
 
@@ -270,6 +273,44 @@ class DiskBlockletManager extends BaseBlockletManager {
270
273
  }
271
274
  }
272
275
 
276
+ async initialize() {
277
+ await this.ensureAutoBackupJobs();
278
+ }
279
+
280
+ async ensureAutoBackupJobs() {
281
+ const blocklets = await states.blockletExtras.find({
282
+ 'settings.autoBackup.enabled': true,
283
+ });
284
+
285
+ const info = await states.node.read();
286
+
287
+ await Promise.all(
288
+ blocklets.map(async (x) => {
289
+ const { did } = x;
290
+ const jobId = getBackupJobId(did);
291
+ const job = await this.backupQueue.get(jobId);
292
+
293
+ if (job) {
294
+ return;
295
+ }
296
+
297
+ this.backupQueue.push(
298
+ {
299
+ entity: 'blocklet',
300
+ action: 'backupToSpaces',
301
+ did,
302
+ context: formatContext({
303
+ user: { did: info.did },
304
+ }),
305
+ },
306
+ jobId,
307
+ true,
308
+ BACKUPS.JOB.INTERVAL
309
+ );
310
+ })
311
+ );
312
+ }
313
+
273
314
  // ============================================================================================
274
315
  // Public API for Installing/Upgrading Application or Components
275
316
  // ============================================================================================
@@ -1395,6 +1436,29 @@ class DiskBlockletManager extends BaseBlockletManager {
1395
1436
  return newState;
1396
1437
  }
1397
1438
 
1439
+ async sendEmail({ did, receiver, email }) {
1440
+ const blocklet = await this.getBlocklet(did);
1441
+ const nodeInfo = await states.node.read();
1442
+ const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
1443
+ // HACK: 因为发送方法是按照数组来处理的
1444
+
1445
+ const [result] = await sendToUser(
1446
+ receiver,
1447
+ JSON.parse(email),
1448
+ {
1449
+ appDid: permanentWallet.address,
1450
+ appSk: permanentWallet.secretKey,
1451
+ },
1452
+ undefined,
1453
+ undefined,
1454
+ 'send-to-mail'
1455
+ );
1456
+ if (result && result.status === 'fulfilled') {
1457
+ return result.value;
1458
+ }
1459
+ throw new Error(result?.reason || 'failed to send email');
1460
+ }
1461
+
1398
1462
  async configNotification({ did, notification = {} }, context) {
1399
1463
  let newConfig = {};
1400
1464
  try {
@@ -2320,7 +2384,31 @@ class DiskBlockletManager extends BaseBlockletManager {
2320
2384
  * }} { blocklet, context }
2321
2385
  * @memberof BlockletManager
2322
2386
  */
2323
- async _onBackupToSpaces({ did, context, backupState }) {
2387
+ async _onBackupToSpaces({
2388
+ did,
2389
+ context,
2390
+ backupState = {
2391
+ strategy: BACKUPS.STRATEGY.AUTO,
2392
+ },
2393
+ }) {
2394
+ const blocklet = await states.blocklet.getBlocklet(did);
2395
+ const {
2396
+ appDid,
2397
+ meta: { did: appPid },
2398
+ } = blocklet;
2399
+
2400
+ if (backupState?.strategy === BACKUPS.STRATEGY.AUTO && blocklet.status === BlockletStatus.stopped) {
2401
+ // 自动备份时,应用停止运行就跳过本次备份
2402
+ logger.warn('Skip automatic backups because the application is not running.', { appPid });
2403
+ return;
2404
+ }
2405
+
2406
+ const correct = await isDnsIpMappingCorrect(blocklet.meta.did); // dns/ip 映射错误就跳过本次备份
2407
+ if (!correct) {
2408
+ logger.warn('Skipping automatic backups due to dns mapping errors', { appPid });
2409
+ return;
2410
+ }
2411
+
2324
2412
  const {
2325
2413
  user: { did: userDid, locale },
2326
2414
  } = context;
@@ -2339,12 +2427,6 @@ class DiskBlockletManager extends BaseBlockletManager {
2339
2427
  );
2340
2428
  }
2341
2429
 
2342
- const blocklet = await states.blocklet.getBlocklet(did);
2343
- const {
2344
- appDid,
2345
- meta: { did: appPid },
2346
- } = blocklet;
2347
-
2348
2430
  let backup = await states.backup.findOne({ appPid }, {}, { createdAt: -1 });
2349
2431
  if (backup?.status !== BACKUPS.STATUS.PROGRESS) {
2350
2432
  // 创建备份记录
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-console */
1
2
  /**
2
3
  * @typedef {{
3
4
  * appDid: string
@@ -117,7 +118,7 @@ class BaseBackup {
117
118
 
118
119
  /**
119
120
  *
120
- * @param {BaseBackup} dataBackup
121
+ * @param {import('./data').DataBackup} dataBackup
121
122
  * @param {Array<BaseBackup>} storages
122
123
  * @returns {Promise<SyncObject[]>}
123
124
  * @memberof BaseBackup
@@ -1,5 +1,6 @@
1
1
  const { join } = require('path');
2
2
  const validUrl = require('valid-url');
3
+ const pAll = require('p-all');
3
4
  const { BaseBackup } = require('./base');
4
5
  const { dirToZip } = require('../utils/zip');
5
6
  const { compareAndMove } = require('../utils/hash');
@@ -35,7 +36,6 @@ class BlockletsBackup extends BaseBackup {
35
36
  const zipPath = join(this.backupDir, 'blocklets', blockletMeta.name, `${blockletMeta.version}.zip`);
36
37
  dirs.push({ sourceDir, zipPath });
37
38
  }
38
-
39
39
  await this.dirsToZip(dirs);
40
40
 
41
41
  return {
@@ -76,12 +76,17 @@ class BlockletsBackup extends BaseBackup {
76
76
  * @memberof BlockletsBackup
77
77
  */
78
78
  async dirsToZip(dirs) {
79
- await Promise.all(
80
- dirs.map(async (dir) => {
81
- const tempZipPath = `${dir.zipPath}.bak`;
82
- await dirToZip(dir.sourceDir, tempZipPath);
83
- await compareAndMove(dir.zipPath, tempZipPath);
84
- })
79
+ await pAll(
80
+ dirs.map((dir) => {
81
+ return async () => {
82
+ const tempZipPath = `${dir.zipPath}.bak`;
83
+ await dirToZip(dir.sourceDir, tempZipPath);
84
+ await compareAndMove(dir.zipPath, tempZipPath);
85
+ };
86
+ }),
87
+ {
88
+ concurrency: 4,
89
+ }
85
90
  );
86
91
  }
87
92
 
@@ -3,10 +3,6 @@ const { join } = require('path');
3
3
  const { BaseBackup } = require('./base');
4
4
  const { getFolderObjects } = require('../utils/disk');
5
5
 
6
- /**
7
- * @typedef {import('./base').FileIncludeStats} FileIncludeStats
8
- */
9
-
10
6
  class DataBackup extends BaseBackup {
11
7
  /**
12
8
  *
@@ -25,15 +21,11 @@ class DataBackup extends BaseBackup {
25
21
  * @return {Promise<import('@did-space/core').Object[]>}
26
22
  * @memberof DataBackup
27
23
  */
28
- collectSyncObjects() {
24
+ async collectSyncObjects() {
29
25
  const blockletDataDir = join(this.serverDir, 'data', this.blocklet.meta.name);
30
26
 
31
- const objects = getFolderObjects(blockletDataDir).map((x) => {
32
- return {
33
- ...x,
34
- key: join('/data', x.key),
35
- };
36
- });
27
+ // @note: 存储到 did-spaces 的应用的 /data 目录下
28
+ const objects = await getFolderObjects(blockletDataDir, '/data');
37
29
 
38
30
  return objects;
39
31
  }
@@ -1,6 +1,11 @@
1
+ /* eslint-disable no-console */
2
+
1
3
  const fs = require('fs-extra');
2
4
  const { join, basename } = require('path');
3
5
  const logger = require('@abtnode/logger')('@abtnode/core:storage:utils:disk');
6
+ const FastGlob = require('fast-glob');
7
+ const mapValues = require('lodash/mapValues');
8
+ const xbytes = require('xbytes');
4
9
 
5
10
  const backupDirName = '_abtnode/backup';
6
11
  const restoreDirName = 'tmp/restore-disk';
@@ -94,38 +99,38 @@ function getFolderSize(folderPath) {
94
99
  /**
95
100
  * @description
96
101
  * @param {string} path
97
- * @return {import('@did-space/core').Object[]}
102
+ * @param {string} [prefix='']
103
+ * @return {Promise<Array<import('@did-space/core').Object>>}
98
104
  */
99
- function getFolderObjects(path) {
100
- /**
101
- * @type {import('@did-space/core').Object[]}
102
- */
103
- const objects = [];
104
- const stack = [path];
105
-
106
- while (stack.length) {
107
- const absolutePath = stack.pop();
108
- const stats = fs.statSync(absolutePath);
105
+ // eslint-disable-next-line require-await
106
+ async function getFolderObjects(path, prefix = '') {
107
+ const stream = FastGlob.stream('**', {
108
+ cwd: path,
109
+ objectMode: true,
110
+ stats: true,
111
+ onlyFiles: true,
112
+ absolute: true,
113
+ dot: true,
114
+ concurrency: 2,
115
+ });
109
116
 
110
- if (stats.isFile()) {
111
- const key = absolutePath.replace(path, '');
112
-
113
- objects.push({
114
- key,
115
- name: basename(key),
116
- isDir: false,
117
- size: stats.size,
118
- lastModified: new Date(stats.mtime).getTime(),
119
- editable: true,
120
- absolutePath,
121
- });
122
- } else if (stats.isDirectory()) {
123
- const files = fs.readdirSync(absolutePath);
124
- files.forEach((file) => {
125
- const filePath = join(absolutePath, file);
126
- stack.push(filePath);
127
- });
128
- }
117
+ const objects = [];
118
+ // eslint-disable-next-line no-unreachable-loop
119
+ for await (const element of stream) {
120
+ /**
121
+ * @type {import('fast-glob').Entry}
122
+ */
123
+ const entry = element;
124
+ const key = entry.path.replace(path, prefix);
125
+ const { stats } = entry;
126
+
127
+ objects.push({
128
+ key,
129
+ name: basename(key),
130
+ size: stats.size,
131
+ lastModified: new Date(stats.mtime).getTime(),
132
+ absolutePath: entry.path,
133
+ });
129
134
  }
130
135
 
131
136
  return objects;
@@ -157,6 +162,11 @@ function getFileObject(absolutePath, prefix = '') {
157
162
  };
158
163
  }
159
164
 
165
+ function formatMemoryUsage() {
166
+ const memoryUsage = process.memoryUsage();
167
+ return mapValues(memoryUsage, (x) => xbytes(x));
168
+ }
169
+
160
170
  module.exports = {
161
171
  getBackupList,
162
172
  removeBackup,
@@ -164,4 +174,5 @@ module.exports = {
164
174
  getFolderSize,
165
175
  getFolderObjects,
166
176
  getFileObject,
177
+ formatMemoryUsage,
167
178
  };
package/lib/index.js CHANGED
@@ -278,6 +278,7 @@ function ABTNode(options) {
278
278
  syncFederated: blockletManager.syncFederated.bind(blockletManager),
279
279
  loginFederated: blockletManager.loginFederated.bind(blockletManager),
280
280
  configNotification: blockletManager.configNotification.bind(blockletManager),
281
+ sendEmail: blockletManager.sendEmail.bind(blockletManager),
281
282
  updateWhoCanAccess: blockletManager.updateWhoCanAccess.bind(blockletManager),
282
283
  updateAppSessionConfig: blockletManager.updateAppSessionConfig.bind(blockletManager),
283
284
  updateComponentTitle: blockletManager.updateComponentTitle.bind(blockletManager),
@@ -628,6 +629,15 @@ function ABTNode(options) {
628
629
  logger.info('Cron jobs start successfully on daemon start');
629
630
  }
630
631
  }, 1000);
632
+
633
+ blockletManager
634
+ .initialize()
635
+ .then(() => {
636
+ logger.info('blockletManager initialized');
637
+ })
638
+ .catch((error) => {
639
+ logger.error('blockletManager initialize failed', { error });
640
+ });
631
641
  }
632
642
  });
633
643
 
@@ -69,6 +69,7 @@ const init = (dataDirs, config = {}) => {
69
69
  * blockletExtras: import('./blocklet-extras'),
70
70
  * notification: import('./notification'),
71
71
  * job: import('./job'),
72
+ * node: import('./node'),
72
73
  * [key: string]: any
73
74
  * }}
74
75
  */
@@ -58,7 +58,7 @@ class NodeState extends BaseState {
58
58
  /**
59
59
  * Ensure we have an node record in the database
60
60
  *
61
- * @returns {object} Node document json
61
+ * @returns {Promise<import('@abtnode/client').NodeState>} Node document json
62
62
  * @memberof NodeState
63
63
  */
64
64
  async read() {
package/lib/util/ip.js CHANGED
@@ -1,5 +1,12 @@
1
+ const getIP = require('@abtnode/util/lib/get-ip');
1
2
  const getIp = require('@abtnode/util/lib/get-ip');
3
+ const { encode } = require('@abtnode/util/lib/base32');
4
+ const { DEFAULT_DID_DOMAIN } = require('@abtnode/constant');
2
5
  const logger = require('@abtnode/logger')('@abtnode/core:ip');
6
+ const dns = require('dns');
7
+ const { promisify } = require('util');
8
+
9
+ const lookup = promisify(dns.lookup);
3
10
 
4
11
  let cache = null;
5
12
  const fetch = async () => {
@@ -23,6 +30,25 @@ const cron = {
23
30
  options: { runOnInit: true, runInService: true },
24
31
  };
25
32
 
33
+ /**
34
+ * @description
35
+ * @param {string} did
36
+ * @return {Promise<boolean>}
37
+ */
38
+ async function isDnsIpMappingCorrect(did) {
39
+ try {
40
+ const { internal, external } = await getIP();
41
+ const didDomain = `${encode(did)}.${DEFAULT_DID_DOMAIN}`;
42
+ const { address } = await lookup(didDomain, { rrtype: 'A' });
43
+
44
+ return internal === address || external === address;
45
+ } catch (error) {
46
+ logger.error(error);
47
+ return false;
48
+ }
49
+ }
50
+
26
51
  module.exports.fetch = fetch;
27
52
  module.exports.get = get;
28
53
  module.exports.cron = cron;
54
+ module.exports.isDnsIpMappingCorrect = isDnsIpMappingCorrect;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.20-beta-e363262e",
6
+ "version": "1.16.20-beta-bb1cd034",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,39 +19,39 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "Apache-2.0",
21
21
  "dependencies": {
22
- "@abtnode/analytics": "1.16.20-beta-e363262e",
23
- "@abtnode/auth": "1.16.20-beta-e363262e",
24
- "@abtnode/certificate-manager": "1.16.20-beta-e363262e",
25
- "@abtnode/constant": "1.16.20-beta-e363262e",
26
- "@abtnode/cron": "1.16.20-beta-e363262e",
27
- "@abtnode/logger": "1.16.20-beta-e363262e",
28
- "@abtnode/models": "1.16.20-beta-e363262e",
29
- "@abtnode/queue": "1.16.20-beta-e363262e",
30
- "@abtnode/rbac": "1.16.20-beta-e363262e",
31
- "@abtnode/router-provider": "1.16.20-beta-e363262e",
32
- "@abtnode/static-server": "1.16.20-beta-e363262e",
33
- "@abtnode/timemachine": "1.16.20-beta-e363262e",
34
- "@abtnode/util": "1.16.20-beta-e363262e",
35
- "@arcblock/did": "1.18.103",
36
- "@arcblock/did-auth": "1.18.103",
37
- "@arcblock/did-ext": "^1.18.103",
22
+ "@abtnode/analytics": "1.16.20-beta-bb1cd034",
23
+ "@abtnode/auth": "1.16.20-beta-bb1cd034",
24
+ "@abtnode/certificate-manager": "1.16.20-beta-bb1cd034",
25
+ "@abtnode/constant": "1.16.20-beta-bb1cd034",
26
+ "@abtnode/cron": "1.16.20-beta-bb1cd034",
27
+ "@abtnode/logger": "1.16.20-beta-bb1cd034",
28
+ "@abtnode/models": "1.16.20-beta-bb1cd034",
29
+ "@abtnode/queue": "1.16.20-beta-bb1cd034",
30
+ "@abtnode/rbac": "1.16.20-beta-bb1cd034",
31
+ "@abtnode/router-provider": "1.16.20-beta-bb1cd034",
32
+ "@abtnode/static-server": "1.16.20-beta-bb1cd034",
33
+ "@abtnode/timemachine": "1.16.20-beta-bb1cd034",
34
+ "@abtnode/util": "1.16.20-beta-bb1cd034",
35
+ "@arcblock/did": "1.18.105",
36
+ "@arcblock/did-auth": "1.18.105",
37
+ "@arcblock/did-ext": "^1.18.105",
38
38
  "@arcblock/did-motif": "^1.1.13",
39
- "@arcblock/did-util": "1.18.103",
40
- "@arcblock/event-hub": "1.18.103",
41
- "@arcblock/jwt": "^1.18.103",
39
+ "@arcblock/did-util": "1.18.105",
40
+ "@arcblock/event-hub": "1.18.105",
41
+ "@arcblock/jwt": "^1.18.105",
42
42
  "@arcblock/pm2-events": "^0.0.5",
43
- "@arcblock/validator": "^1.18.103",
44
- "@arcblock/vc": "1.18.103",
45
- "@blocklet/constant": "1.16.20-beta-e363262e",
46
- "@blocklet/env": "1.16.20-beta-e363262e",
47
- "@blocklet/meta": "1.16.20-beta-e363262e",
48
- "@blocklet/resolver": "1.16.20-beta-e363262e",
49
- "@blocklet/sdk": "1.16.20-beta-e363262e",
43
+ "@arcblock/validator": "^1.18.105",
44
+ "@arcblock/vc": "1.18.105",
45
+ "@blocklet/constant": "1.16.20-beta-bb1cd034",
46
+ "@blocklet/env": "1.16.20-beta-bb1cd034",
47
+ "@blocklet/meta": "1.16.20-beta-bb1cd034",
48
+ "@blocklet/resolver": "1.16.20-beta-bb1cd034",
49
+ "@blocklet/sdk": "1.16.20-beta-bb1cd034",
50
50
  "@did-space/client": "^0.3.41",
51
51
  "@fidm/x509": "^1.2.1",
52
- "@ocap/mcrypto": "1.18.103",
53
- "@ocap/util": "1.18.103",
54
- "@ocap/wallet": "1.18.103",
52
+ "@ocap/mcrypto": "1.18.105",
53
+ "@ocap/util": "1.18.105",
54
+ "@ocap/wallet": "1.18.105",
55
55
  "@slack/webhook": "^5.0.4",
56
56
  "archiver": "^5.3.1",
57
57
  "axios": "^0.27.2",
@@ -76,6 +76,7 @@
76
76
  "lodash": "^4.17.21",
77
77
  "lru-cache": "^6.0.0",
78
78
  "node-stream-zip": "^1.15.0",
79
+ "p-all": "3.0.0",
79
80
  "p-limit": "^3.1.0",
80
81
  "p-retry": "4.6.1",
81
82
  "read-last-lines": "^1.8.0",
@@ -101,5 +102,5 @@
101
102
  "jest": "^27.5.1",
102
103
  "unzipper": "^0.10.11"
103
104
  },
104
- "gitHead": "479cea04dd620d8194d6221b2594b6a0f390ba76"
105
+ "gitHead": "b000d880478911fbad7f5b197daf8a9065463ca8"
105
106
  }