@abtnode/core 1.16.18-beta-7aa6be5a → 1.16.18-beta-bb4ebacc

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
@@ -42,7 +42,7 @@ const { validateCreatePermission, validateUpdatePermission } = require('../valid
42
42
  const { getBlocklet } = require('../util/blocklet');
43
43
  const StoreUtil = require('../util/store');
44
44
  const { profileSchema } = require('../validators/user');
45
- const { callFederated } = require('../util/federated');
45
+ const { callFederated, getFederatedMaster, findFederatedSite } = require('../util/federated');
46
46
 
47
47
  const sanitizeUrl = (url) => {
48
48
  if (!url) {
@@ -1409,7 +1409,20 @@ class TeamAPI extends EventEmitter {
1409
1409
  return result;
1410
1410
  }
1411
1411
 
1412
- async upsertUserSession({ teamDid, appPid, userDid, visitorId, ua, lastLoginIp, sourceAppPid, passportId, status }) {
1412
+ /**
1413
+ * 新增|更新一个 userSession
1414
+ * @param {string} params.teamDid - 当前更新的这个应用的 teamDid
1415
+ * @param {string} params.appPid - 记录的 userSession 中应用的 appPid
1416
+ * @param {string} params.userDid - 记录的 userSession 中当前用户的 did
1417
+ * @param {string} params.visitorId - 记录的 userSession 中当前用户所使用设备的 visitorId
1418
+ * @param {string} params.ua - 记录的 userSession 中当前用户所使用设备的 ua
1419
+ * @param {string} params.lastLoginIp - 记录的 userSession 中当前用户的最后登录 ip
1420
+ * @param {string} params.passportId - 记录的 userSession 中当前用户登录所使用的 passportId
1421
+ * @param {string} params.status - 记录的 userSession 的状态
1422
+ * @returns
1423
+ */
1424
+
1425
+ async upsertUserSession({ teamDid, appPid, userDid, visitorId, ua, lastLoginIp, passportId, status }) {
1413
1426
  if (!userDid) {
1414
1427
  throw new Error('userDid are required');
1415
1428
  }
@@ -1443,62 +1456,88 @@ class TeamAPI extends EventEmitter {
1443
1456
  }
1444
1457
  }
1445
1458
 
1446
- if (sourceAppPid) {
1447
- const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
1448
- const nodeInfo = await this.node.read();
1449
- const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
1450
- const federated = defaults(cloneDeep(blocklet.settings.federated || {}), {
1451
- config: {},
1452
- sites: [],
1453
- });
1454
- const masterSite = federated.sites[0];
1455
- if (masterSite && masterSite.isMaster !== false && masterSite.appPid === sourceAppPid) {
1459
+ return data;
1460
+ }
1461
+
1462
+ /**
1463
+ * 同步一个 userSession(userSession 只能向 master 同步,或 master 向 member 同步,不能由 member 向 member 同步)
1464
+ * @param {string} params.teamDid - 当前更新的这个应用的 teamDid
1465
+ * @param {string} params.targetAppPid - 同步的 userSession 的目标站点 appPid
1466
+ * @param {string} params.userDid - 同步的 userSession 中当前用户的 did
1467
+ * @param {string} params.visitorId - 同步的 userSession 中当前用户所使用设备的 visitorId
1468
+ * @param {string} params.ua - 同步的 userSession 中当前用户所使用设备的 ua
1469
+ * @param {string} params.lastLoginIp - 同步的 userSession 中当前用户的最后登录 ip
1470
+ * @param {string} params.passportId - 同步的 userSession 中当前用户登录所使用的 passportId
1471
+ * @returns
1472
+ */
1473
+ async syncUserSession({ teamDid, targetAppPid, userDid, visitorId, ua, lastLoginIp, passportId }) {
1474
+ if (!targetAppPid) return;
1475
+
1476
+ const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
1477
+ const nodeInfo = await this.node.read();
1478
+ const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
1479
+
1480
+ const targetSite = findFederatedSite(blocklet, targetAppPid);
1481
+ const masterSite = getFederatedMaster(blocklet);
1482
+
1483
+ let syncSite = null;
1484
+ let appPid;
1485
+ if (masterSite && targetSite) {
1486
+ if (targetSite.appPid === masterSite.appPid) {
1487
+ if (teamDid !== masterSite.appPid) {
1488
+ syncSite = masterSite;
1489
+ appPid = teamDid;
1490
+ }
1491
+ } else if (teamDid === masterSite.appPid) {
1492
+ syncSite = targetSite;
1493
+ appPid = targetAppPid;
1494
+ }
1495
+
1496
+ if (syncSite) {
1456
1497
  try {
1498
+ const userSession = {
1499
+ action: 'login',
1500
+ userDid,
1501
+ visitorId,
1502
+ ua,
1503
+ lastLoginIp,
1504
+ appPid,
1505
+ passportId,
1506
+ };
1457
1507
  await callFederated({
1458
1508
  action: 'sync',
1459
1509
  permanentWallet,
1460
- site: masterSite,
1510
+ site: targetSite,
1461
1511
  data: {
1462
- userSessions: [
1463
- {
1464
- action: 'login',
1465
- userDid,
1466
- visitorId: data.visitorId,
1467
- ua,
1468
- lastLoginIp,
1469
- appPid: teamDid,
1470
- passportId,
1471
- },
1472
- ],
1512
+ userSessions: [userSession],
1473
1513
  },
1474
1514
  });
1475
1515
  logger.info('sync userSession to federated site successfully', {
1476
1516
  userDid,
1477
- visitorId: data.visitorId,
1517
+ visitorId,
1478
1518
  ua,
1479
1519
  lastLoginIp,
1480
- appPid: teamDid,
1520
+ appPid,
1481
1521
  passportId,
1522
+ targetSite,
1482
1523
  });
1483
1524
  } catch (err) {
1484
1525
  logger.info(
1485
1526
  'sync userSession to federated site failed',
1486
1527
  {
1487
1528
  userDid,
1488
- visitorId: data.visitorId,
1529
+ visitorId,
1489
1530
  ua,
1490
1531
  lastLoginIp,
1491
- appPid: teamDid,
1532
+ appPid,
1492
1533
  passportId,
1534
+ targetSite,
1493
1535
  },
1494
1536
  err
1495
1537
  );
1496
- throw err;
1497
1538
  }
1498
1539
  }
1499
1540
  }
1500
-
1501
- return data;
1502
1541
  }
1503
1542
 
1504
1543
  async logoutUser({ teamDid, userDid, visitorId, appPid }) {
@@ -63,7 +63,7 @@ const mergeConfigs = ({ old: oldConfigs, cur: newConfigs = [], did = '', dek = '
63
63
 
64
64
  if (enableSecurity) {
65
65
  newConfig.forEach((x) => {
66
- if (x.secure) {
66
+ if (x.secure || oldConfig[x.key]?.secure) {
67
67
  x.value = security.encrypt(x.value, did, dek);
68
68
  if (x.default) {
69
69
  x.default = security.encrypt(x.default, did, dek);
@@ -359,23 +359,9 @@ class DiskBlockletManager extends BaseBlockletManager {
359
359
  * @param {ConfigEntry} configs pre configs
360
360
  */
361
361
  async installComponent(
362
- {
363
- rootDid,
364
- mountPoint: tmpMountPoint,
365
- url,
366
- file,
367
- did,
368
- diffVersion,
369
- deleteSet,
370
- title,
371
- name,
372
- configs,
373
- sync,
374
- downloadTokenList,
375
- },
362
+ { rootDid, mountPoint, url, file, did, diffVersion, deleteSet, title, name, configs, sync, downloadTokenList },
376
363
  context = {}
377
364
  ) {
378
- const mountPoint = await updateMountPointSchema.validateAsync(tmpMountPoint);
379
365
  logger.debug('start install component', { rootDid, mountPoint, url });
380
366
 
381
367
  if (file) {
@@ -1968,8 +1954,8 @@ class DiskBlockletManager extends BaseBlockletManager {
1968
1954
  return createProject({ did, ...params, manager: this });
1969
1955
  }
1970
1956
 
1971
- getProjects({ did } = {}) {
1972
- return getProjects({ did, manager: this });
1957
+ getProjects({ did, componentDid } = {}) {
1958
+ return getProjects({ did, componentDid, manager: this });
1973
1959
  }
1974
1960
 
1975
1961
  getProject({ did, projectId } = {}) {
@@ -4072,7 +4058,7 @@ class FederatedBlockletManager extends DiskBlockletManager {
4072
4058
  * @param {{users?:Array; sites?:Array}} options.data - 需要同步的数据
4073
4059
  * @returns
4074
4060
  */
4075
- async syncFederated({ did, data = {} } = {}) {
4061
+ async syncFederated({ did, data = {}, syncSites } = {}) {
4076
4062
  const blocklet = await this.getBlocklet(did);
4077
4063
 
4078
4064
  const federated = defaults(cloneDeep(blocklet.settings.federated || {}), {
@@ -4116,7 +4102,18 @@ class FederatedBlockletManager extends DiskBlockletManager {
4116
4102
  const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
4117
4103
 
4118
4104
  const waitingList = federated.sites
4119
- .filter((item) => item.appId !== federated.config.appId)
4105
+ .filter((item) => {
4106
+ // 未指定 syncSites 向全员通知(除了自己)
4107
+ if (isUndefined(syncSites)) {
4108
+ // 排除通知自己
4109
+ return item.appPid !== blocklet.appPid;
4110
+ }
4111
+ // 如果指定了要通知的站点,则按指定的站点来过滤
4112
+ if (Array.isArray(syncSites)) {
4113
+ return syncSites.some((x) => x.appPid === item.appPid);
4114
+ }
4115
+ return false;
4116
+ })
4120
4117
  .map((item) => {
4121
4118
  return limitSync(async () => {
4122
4119
  try {
@@ -2,10 +2,11 @@ const path = require('path');
2
2
 
3
3
  const logger = require('@abtnode/logger')('@abtnode/core:install-component-upload');
4
4
  const getComponentProcessId = require('@blocklet/meta/lib/get-component-process-id');
5
- const { isInProgress } = require('@blocklet/meta/lib/util');
5
+ const { isInProgress, hasStartEngine } = require('@blocklet/meta/lib/util');
6
6
 
7
7
  const { BlockletSource, BlockletGroup, fromBlockletStatus } = require('@blocklet/constant');
8
8
  const { INSTALL_ACTIONS } = require('@abtnode/constant');
9
+ const { updateMountPointSchema } = require('@blocklet/meta/lib/schema');
9
10
  const {
10
11
  parseComponents,
11
12
  filterDuplicateComponents,
@@ -20,7 +21,7 @@ const { resolveDownload, resolveDiffDownload, downloadFromUpload } = require('..
20
21
 
21
22
  const installComponentFromUpload = async ({
22
23
  rootDid,
23
- mountPoint,
24
+ mountPoint: tmpMountPoint,
24
25
  file,
25
26
  did,
26
27
  diffVersion,
@@ -83,11 +84,15 @@ const installComponentFromUpload = async ({
83
84
 
84
85
  const newChild = {
85
86
  meta: ensureMeta(meta),
86
- mountPoint,
87
87
  source: BlockletSource.upload,
88
88
  deployedFrom: `Upload by ${context.user.fullName}`,
89
89
  bundleSource: null,
90
90
  };
91
+ if (hasStartEngine(newChild.meta)) {
92
+ const mountPoint = await updateMountPointSchema.validateAsync(tmpMountPoint);
93
+ newChild.mountPoint = mountPoint;
94
+ }
95
+
91
96
  const index = newBlocklet.children.findIndex((child) => child.meta.did === meta.did);
92
97
  if (index >= 0) {
93
98
  // if upgrade, do not update mountPoint
@@ -3,7 +3,7 @@ const { sign } = require('@arcblock/jwt');
3
3
  const logger = require('@abtnode/logger')('@abtnode/core:install-component-url');
4
4
 
5
5
  const { isFreeBlocklet, hasStartEngine } = require('@blocklet/meta/lib/util');
6
- const { titleSchema } = require('@blocklet/meta/lib/schema');
6
+ const { titleSchema, updateMountPointSchema } = require('@blocklet/meta/lib/schema');
7
7
  const hasReservedKey = require('@blocklet/meta/lib/has-reserved-key');
8
8
 
9
9
  const { BlockletStatus, BlockletEvents, BlockletGroup } = require('@blocklet/constant');
@@ -23,7 +23,7 @@ const { formatName } = require('../../../util/get-domain-for-blocklet');
23
23
 
24
24
  const installComponentFromUrl = async ({
25
25
  rootDid,
26
- mountPoint,
26
+ mountPoint: tmpMountPoint,
27
27
  url,
28
28
  context,
29
29
  title,
@@ -86,6 +86,7 @@ const installComponentFromUrl = async ({
86
86
  };
87
87
 
88
88
  if (hasStartEngine(newChildMeta)) {
89
+ const mountPoint = await updateMountPointSchema.validateAsync(tmpMountPoint);
89
90
  newChild.mountPoint = mountPoint || formatName(newChildMeta.title) || formatName(newChildMeta.name);
90
91
  }
91
92
 
@@ -156,7 +157,7 @@ const installComponentFromUrl = async ({
156
157
  // backup rollback data
157
158
  await manager._rollbackCache.backup({ did: rootDid, action, oldBlocklet });
158
159
 
159
- logger.info('install component from url', { rootDid, url, mountPoint, sync, componentDids });
160
+ logger.info('install component from url', { rootDid, url, mountPoint: tmpMountPoint, sync, componentDids });
160
161
 
161
162
  if (sync) {
162
163
  await manager._downloadAndInstall({ ...downloadParams, throwOnError: true });
@@ -15,9 +15,11 @@ const { createReleaseSchema } = require('../../validators/project');
15
15
 
16
16
  const createRandomStr = () => Math.random().toString(36).substring(2, 8);
17
17
 
18
+ const COMPONENT_CONFIG_MAP_DIR = '.component_config';
19
+
18
20
  // project
19
21
 
20
- const createProject = async ({ did, type, blockletTitle, blockletDid, manager }) => {
22
+ const createProject = async ({ did, type, blockletTitle, blockletDid, componentDid, manager }) => {
21
23
  await titleSchema.validateAsync(blockletTitle);
22
24
  validateNewDid(blockletDid);
23
25
  if (!PROJECT.TYPES[type]) {
@@ -27,7 +29,7 @@ const createProject = async ({ did, type, blockletTitle, blockletDid, manager })
27
29
  const blocklet = await manager.getBlocklet(did, { useCache: true });
28
30
  const { projectState } = await manager._getProjectState(did);
29
31
 
30
- const project = await projectState.createProject({ blockletTitle, blockletDid, type });
32
+ const project = await projectState.createProject({ blockletTitle, blockletDid, type, componentDid });
31
33
 
32
34
  // create project dir
33
35
  const projectDir = path.join(blocklet.env.dataDir, PROJECT.DIR, `${project.id}`);
@@ -41,9 +43,9 @@ const createProject = async ({ did, type, blockletTitle, blockletDid, manager })
41
43
  return project;
42
44
  };
43
45
 
44
- const getProjects = async ({ did, manager }) => {
46
+ const getProjects = async ({ did, manager, componentDid }) => {
45
47
  const { projectState } = await manager._getProjectState(did);
46
- const projects = await projectState.getProjects();
48
+ const projects = await projectState.getProjects({ componentDid });
47
49
  return { projects };
48
50
  };
49
51
 
@@ -237,22 +239,6 @@ const createRelease = async ({
237
239
  await fs.outputFile(path.join(tmpBundleDir, 'README.md'), blockletIntroduction);
238
240
  }
239
241
 
240
- // create blocklet.yml
241
- const meta = {
242
- did: blockletDid,
243
- name: blockletDid,
244
- title: blockletTitle,
245
- description: blockletDescription,
246
- version: blockletVersion,
247
- logo: logoFileName,
248
- resources: ['/resource'],
249
- components: [],
250
- files: ['screenshots'],
251
- screenshots: project.blockletScreenshots || [],
252
- };
253
-
254
- await updateMetaFile(path.join(tmpBundleDir, BLOCKLET_META_FILE), meta);
255
-
256
242
  // changelog
257
243
  const changelogDistFile = path.join(tmpBundleDir, 'CHANGELOG.md');
258
244
  const releases = await releaseState.getReleases({ projectId, status: PROJECT.RELEASE_STATUS.published });
@@ -270,6 +256,25 @@ const createRelease = async ({
270
256
  await fs.remove(tmpResourceDir);
271
257
  await fs.copy(resourceDir, tmpResourceDir);
272
258
 
259
+ // create blocklet.yml
260
+ const resourceList = await fs.readdir(resourceDir);
261
+ const resourcesPaths = resourceList
262
+ .filter((x) => x !== COMPONENT_CONFIG_MAP_DIR)
263
+ .map((x) => path.join('/resource', x));
264
+ const meta = {
265
+ did: blockletDid,
266
+ name: blockletDid,
267
+ title: blockletTitle,
268
+ description: blockletDescription,
269
+ version: blockletVersion,
270
+ logo: logoFileName,
271
+ resources: resourcesPaths.length ? resourcesPaths : undefined,
272
+ components: [],
273
+ files: ['screenshots'],
274
+ screenshots: project.blockletScreenshots || [],
275
+ };
276
+ await updateMetaFile(path.join(tmpBundleDir, BLOCKLET_META_FILE), meta);
277
+
273
278
  // create release
274
279
  await createBlockletRelease(tmpExportDir, { printError: logger.error, printInfo: logger.info });
275
280
 
@@ -319,7 +324,7 @@ const getSelectedResourcesFile = ({ app, projectId, releaseId, componentDid }) =
319
324
  dirArr.push(PROJECT.RELEASE_DIR, releaseId || '/');
320
325
  }
321
326
  dirArr.push(PROJECT.RESOURCE_DIR);
322
- dirArr.push('.component_config');
327
+ dirArr.push(COMPONENT_CONFIG_MAP_DIR);
323
328
  dirArr.push(`${componentDid}.json`);
324
329
  return path.join(...dirArr);
325
330
  };
@@ -1,7 +1,7 @@
1
1
  const get = require('lodash/get');
2
2
  const cloneDeep = require('lodash/cloneDeep');
3
3
  const { EventEmitter } = require('events');
4
- const { wipeSensitiveData } = require('@blocklet/meta/lib/util');
4
+ const { wipeSensitiveData, getDisplayName } = require('@blocklet/meta/lib/util');
5
5
  const logger = require('@abtnode/logger')('@abtnode/core:event');
6
6
  const {
7
7
  BLOCKLET_MODES,
@@ -308,20 +308,32 @@ module.exports = ({
308
308
  } else if (BlockletEvents.backupProgress === eventName && payload?.completed) {
309
309
  try {
310
310
  const backupEndpoint = getBackupEndpoint(blocklet?.environments);
311
+ const args = {
312
+ did: blocklet.meta.did,
313
+ url: getDIDSpacesUrlFromEndpoint(backupEndpoint),
314
+ backupUrl: getBackupFilesUrlFromEndpoint(backupEndpoint),
315
+ success: payload?.progress === 100,
316
+ errorMessage: payload?.message,
317
+ strategy: payload.backup.strategy,
318
+ };
311
319
 
312
320
  await node.createAuditLog({
313
321
  action: 'backupToSpaces',
314
- args: {
315
- did: blocklet.meta.did,
316
- url: getDIDSpacesUrlFromEndpoint(backupEndpoint),
317
- backupUrl: getBackupFilesUrlFromEndpoint(backupEndpoint),
318
- success: payload?.progress === 100,
319
- errorMessage: payload?.message,
320
- strategy: payload.backup.strategy,
321
- },
322
+ args,
322
323
  context: payload?.context ?? {},
323
324
  result: cloneDeep(blocklet),
324
325
  });
326
+
327
+ if (payload?.progress !== 100) {
328
+ await node.createNotification({
329
+ title: 'App Backup Failed',
330
+ description: `Failed to backup ${getDisplayName(blocklet)} to ${args.url}, due to: ${args.errorMessage}`,
331
+ entityType: 'blocklet',
332
+ entityId: args.did,
333
+ severity: 'error',
334
+ sticky: true,
335
+ });
336
+ }
325
337
  } catch (error) {
326
338
  logger.error('Failed to createAuditLog for backupToSpaces failed', { error });
327
339
  }
package/lib/index.js CHANGED
@@ -385,6 +385,7 @@ function ABTNode(options) {
385
385
  // user-session
386
386
  getUserSession: teamAPI.getUserSession.bind(teamAPI),
387
387
  upsertUserSession: teamAPI.upsertUserSession.bind(teamAPI),
388
+ syncUserSession: teamAPI.syncUserSession.bind(teamAPI),
388
389
  logoutUser: teamAPI.logoutUser.bind(teamAPI),
389
390
  getPassportById: teamAPI.getPassportById.bind(teamAPI),
390
391
  getPassportFromFederated: teamAPI.getPassportFromFederated.bind(teamAPI),
@@ -435,6 +436,7 @@ function ABTNode(options) {
435
436
  deleteBlockletStore: teamAPI.deleteStore.bind(teamAPI),
436
437
 
437
438
  // Notifications
439
+ createNotification: states.notification.create.bind(states.notification),
438
440
  getNotifications: states.notification.findPaginated.bind(states.notification),
439
441
  readNotifications: states.notification.read.bind(states.notification),
440
442
  unreadNotifications: states.notification.unread.bind(states.notification),
@@ -634,6 +636,7 @@ function ABTNode(options) {
634
636
  createWebHook: webhook.create.bind(webhook),
635
637
  deleteWebHook: webhook.delete.bind(webhook),
636
638
  getWebhookSenders: webhook.listSenders.bind(webhook),
639
+ getMessageSender: webhook.getMessageSender.bind(webhook),
637
640
  sendTestMessage: webhook.sendTestMessage.bind(webhook),
638
641
  });
639
642
  }
@@ -4,6 +4,7 @@ const axon = require('axon');
4
4
  const path = require('path');
5
5
  const shell = require('shelljs');
6
6
  const semver = require('semver');
7
+ const { getInstaller, getInstallCommands } = require('@abtnode/util/lib/get-installer');
7
8
 
8
9
  const VERBOSE = '';
9
10
  // const VERBOSE = '--verbose';
@@ -26,30 +27,8 @@ const runAsync = (command, options) =>
26
27
  });
27
28
  });
28
29
 
29
- const getInstaller = () => {
30
- const { stdout: pnpmPath } = shell.which('pnpm') || {};
31
- if (pnpmPath) {
32
- const { stdout: expectedDir } = shell.exec('pnpm bin -g', { silent: true });
33
- const { stdout: binPath } = shell.which(BINARY_NAME);
34
- if (expectedDir && binPath && expectedDir.trim() === path.dirname(binPath)) {
35
- return 'pnpm';
36
- }
37
- }
38
-
39
- const { stdout: yarnPath } = shell.which('yarn') || {};
40
- if (yarnPath) {
41
- const { stdout: binaryDir } = shell.exec(`yarn global bin ${BINARY_NAME}`, { silent: true });
42
- const binaryPath = path.join(binaryDir, BINARY_NAME);
43
- if (fs.existsSync(binaryPath)) {
44
- return 'yarn';
45
- }
46
- }
47
-
48
- return 'npm';
49
- };
50
-
51
30
  const getBinaryDir = () => {
52
- const installer = getInstaller();
31
+ const installer = getInstaller(BINARY_NAME);
53
32
  if (installer === 'pnpm') {
54
33
  const { stdout: binaryDir, code } = shell.exec('pnpm bin -g', { silent: true });
55
34
  if (code === 0) {
@@ -77,12 +56,8 @@ const installBlockletServer = async (version) => {
77
56
  return { code: 1, stderr: 'Abort because you are not using a standard @blocklet/cli setup' };
78
57
  }
79
58
 
80
- const installer = getInstaller();
81
- const commands = {
82
- pnpm: `pnpm add -g ${PACKAGE_NAME}@${version} ${VERBOSE}`,
83
- npm: `npm install -g ${PACKAGE_NAME}@${version} ${VERBOSE}`,
84
- yarn: `yarn global add ${PACKAGE_NAME}@${version} ${VERBOSE}`,
85
- };
59
+ const installer = getInstaller(BINARY_NAME);
60
+ const commands = getInstallCommands({ packageName: PACKAGE_NAME, version, params: VERBOSE });
86
61
  const command = commands[installer];
87
62
  console.info('Installing blocklet server', { version, installer, command });
88
63
  const result = await runAsync(command, {
@@ -145,7 +145,7 @@ const getLogContent = async (action, args, context, result, info, node) => {
145
145
  }
146
146
  return `Automatic backup application to ${args.url} successfully:\n- Backup files have been stored [here](${args.backupUrl})`;
147
147
  }
148
- return `Backup application to ${args.url} failed:\n- The reason for the error is: <span style='color:red'>${args.errorMessage}</span>`;
148
+ return `Backup application to ${args.url} failed:\n- Reason: <span style='color:red'>${args.errorMessage}</span>`;
149
149
  case 'configPublicToStore':
150
150
  return `set publicToStore to ${args.publicToStore ? 'true' : 'false'}`;
151
151
  case 'configNavigations':
@@ -245,7 +245,7 @@ ${JSON.stringify(syncData, null, 2)}
245
245
  case 'switchPassport':
246
246
  return `${args.provider} user ${user} switched passport to ${args.passport.name}`;
247
247
  case 'login':
248
- return `${args.provider} user ${user} logged in with passport ${args.passport.name}`;
248
+ return `${args.provider} user ${user} logged in with passport ${args.passport?.name}`;
249
249
  case 'updateWhoCanAccess':
250
250
  return `updated access control policy of **${
251
251
  args.did[1] ? getComponentNames(result, [args.did[1]]) : 'application'
@@ -4,7 +4,13 @@ const omit = require('lodash/omit');
4
4
  const isEmpty = require('lodash/isEmpty');
5
5
  const security = require('@abtnode/util/lib/security');
6
6
  const { isFromPublicKey } = require('@arcblock/did');
7
- const { NODE_MODES, DISK_ALERT_THRESHOLD_PERCENT, EVENTS, SERVER_STATUS } = require('@abtnode/constant');
7
+ const {
8
+ NODE_MODES,
9
+ DISK_ALERT_THRESHOLD_PERCENT,
10
+ EVENTS,
11
+ SERVER_STATUS,
12
+ PROXY_MAX_MEM_LIMIT_IN_MB,
13
+ } = require('@abtnode/constant');
8
14
  // const logger = require('@abtnode/logger')('@abtnode/core:states:node');
9
15
 
10
16
  const BaseState = require('./base');
@@ -132,6 +138,10 @@ class NodeState extends BaseState {
132
138
  doc.sk = security.decrypt(doc.sk, doc.did, dek);
133
139
  }
134
140
 
141
+ if (doc.runtimeConfig && !doc.runtimeConfig.proxyMaxMemoryLimit) {
142
+ doc.runtimeConfig.proxyMaxMemoryLimit = PROXY_MAX_MEM_LIMIT_IN_MB;
143
+ }
144
+
135
145
  return doc;
136
146
  }
137
147
 
@@ -8,13 +8,22 @@ const isUndefinedOrNull = (x) => x === undefined || x === null;
8
8
  * @extends BaseState<import('@abtnode/models').ProjectState>
9
9
  */
10
10
  class Project extends BaseState {
11
- createProject({ type, blockletDid, blockletTitle }) {
12
- return this.insert({ id: blockletDid, type, blockletDid, blockletTitle });
11
+ createProject({ type, blockletDid, blockletTitle, componentDid }) {
12
+ const doc = { id: blockletDid, type, blockletDid, blockletTitle };
13
+ if (componentDid) {
14
+ doc.componentDid = componentDid;
15
+ }
16
+
17
+ return this.insert(doc);
13
18
  }
14
19
 
15
- getProjects() {
20
+ getProjects({ componentDid }) {
21
+ const query = {};
22
+ if (componentDid) {
23
+ query.componentDid = componentDid;
24
+ }
16
25
  return this.find(
17
- {},
26
+ query,
18
27
  {},
19
28
  {
20
29
  createdAt: -1,
@@ -5,6 +5,22 @@ const joinUrl = require('url-join');
5
5
 
6
6
  const request = require('./request');
7
7
 
8
+ function isMaster(site) {
9
+ return site?.isMaster !== false;
10
+ }
11
+
12
+ function getFederatedMaster(blocklet) {
13
+ const { sites } = blocklet?.settings?.federated || {};
14
+ const masterSite = (sites || []).find((item) => isMaster(item));
15
+ return masterSite || null;
16
+ }
17
+
18
+ function findFederatedSite(blocklet, targetAppPid) {
19
+ const { sites } = blocklet?.settings?.federated || {};
20
+ const targetSite = (sites || []).find((item) => item.appPid === targetAppPid);
21
+ return targetSite || null;
22
+ }
23
+
8
24
  async function callFederated({ site, permanentWallet, data, action }) {
9
25
  const url = new URL(site.appUrl);
10
26
  url.pathname = joinUrl(WELLKNOWN_SERVICE_PATH_PREFIX, `/api/federated/${action}`);
@@ -21,4 +37,7 @@ async function callFederated({ site, permanentWallet, data, action }) {
21
37
 
22
38
  module.exports = {
23
39
  callFederated,
40
+ getFederatedMaster,
41
+ findFederatedSite,
42
+ isMaster,
24
43
  };
package/lib/util/log.js CHANGED
@@ -117,6 +117,7 @@ const getLogFiles = async ({ name, node }) => {
117
117
  const access = path.join(logDir, 'access.log');
118
118
  const stdout = path.join(logDir, 'daemon.stdout.log');
119
119
  const stderr = path.join(logDir, 'daemon.stderr.log');
120
+ const pm2 = path.join(process.env.PM2_HOME, 'pm2.log');
120
121
 
121
122
  createFile({
122
123
  info,
@@ -124,6 +125,7 @@ const getLogFiles = async ({ name, node }) => {
124
125
  access,
125
126
  stdout,
126
127
  stderr,
128
+ pm2,
127
129
  });
128
130
 
129
131
  return {
@@ -132,6 +134,7 @@ const getLogFiles = async ({ name, node }) => {
132
134
  access,
133
135
  stdout,
134
136
  stderr,
137
+ pm2,
135
138
  };
136
139
  }
137
140
 
@@ -30,7 +30,7 @@ const nodeInfoSchema = Joi.object({
30
30
  }),
31
31
  nftDomainUrl: Joi.string()
32
32
  .uri({ scheme: [/https?/] })
33
- .label('web wallet url')
33
+ .label('nft domain url')
34
34
  .allow('')
35
35
  .optional()
36
36
  .messages({
@@ -173,7 +173,7 @@ module.exports = ({ events, dataDirs, instance }) => {
173
173
  const sendTestMessage = async (msg) => {
174
174
  const { webhookId, message } = msg;
175
175
  try {
176
- const webhook = await webhookState.findOne(webhookId);
176
+ const webhook = webhookId ? await webhookState.findOne(webhookId) : msg.webhook;
177
177
  await sentTextMessage(webhook, message);
178
178
 
179
179
  notification.create({
@@ -195,6 +195,7 @@ module.exports = ({ events, dataDirs, instance }) => {
195
195
 
196
196
  return {
197
197
  listSenders: WebHookSender.listSenders,
198
+ getMessageSender: WebHookSender.getMessageSender,
198
199
  create: createWebhook,
199
200
  delete: deleteWebhook,
200
201
  sendTestMessage,
@@ -9,12 +9,16 @@ class APISender extends BaseSender {
9
9
  const { status, name, title, description } = message;
10
10
 
11
11
  try {
12
- await axios.post(url, {
13
- status,
14
- name,
15
- title,
16
- description,
17
- });
12
+ await axios.post(
13
+ url,
14
+ {
15
+ status,
16
+ name,
17
+ title,
18
+ description,
19
+ },
20
+ { timeout: 10000 }
21
+ );
18
22
  } catch (error) {
19
23
  logger.error('failed to push notification to api', { url, message: JSON.stringify(message), error });
20
24
  }
@@ -28,6 +32,14 @@ class APISender extends BaseSender {
28
32
  title: 'Test Message',
29
33
  });
30
34
  }
35
+
36
+ async sendNotification(url, notification) {
37
+ try {
38
+ await axios.post(url, notification, { timeout: 10000 });
39
+ } catch (error) {
40
+ logger.error('failed to push notification to api', { url, error });
41
+ }
42
+ }
31
43
  }
32
44
 
33
45
  APISender.type = 'api';
@@ -59,8 +59,9 @@ class SlackSender extends BaseSender {
59
59
  username: 'Blocklet Server',
60
60
  icon_url: 'https://releases.arcblockio.cn/arcblock.png',
61
61
  });
62
- } catch (error) {
63
- logger.error('failed to push notification to slack', { api: url, message: JSON.stringify(message), error });
62
+ } catch (err) {
63
+ console.error(err);
64
+ logger.error('failed to push notification to slack', { api: url, message: JSON.stringify(message), error: err });
64
65
  }
65
66
  }
66
67
 
@@ -73,6 +74,66 @@ class SlackSender extends BaseSender {
73
74
  'text'
74
75
  );
75
76
  }
77
+
78
+ async sendNotification(url, message) {
79
+ const { title, body, sender, attachments, actions = [] } = message;
80
+ try {
81
+ const blocks = [];
82
+ if (title) {
83
+ blocks.push({
84
+ type: 'header',
85
+ text: {
86
+ type: 'plain_text',
87
+ text: title,
88
+ },
89
+ });
90
+ }
91
+ if (body) {
92
+ blocks.push({
93
+ type: 'section',
94
+ text: {
95
+ type: 'plain_text',
96
+ text: body,
97
+ },
98
+ });
99
+ }
100
+
101
+ // FIXME: support more attachment types: nft, image, token, vc
102
+ (attachments || [])
103
+ .filter((x) => x.type === 'section')
104
+ .forEach((x) => {
105
+ blocks.push({
106
+ type: x.type,
107
+ fields: x.fields.map((f) => ({ type: 'plain_text', text: f.data.text })),
108
+ });
109
+ });
110
+ if (actions.length) {
111
+ blocks.push({
112
+ type: 'actions',
113
+ elements: actions.map((x) => ({
114
+ type: 'button',
115
+ text: {
116
+ type: 'plain_text',
117
+ text: x.name,
118
+ },
119
+ style: 'primary',
120
+ value: x.name,
121
+ url: x.link,
122
+ })),
123
+ });
124
+ }
125
+
126
+ const webhook = new IncomingWebhook(url);
127
+ await webhook.send({
128
+ blocks,
129
+ username: sender.name,
130
+ icon_url: sender.logo,
131
+ });
132
+ } catch (err) {
133
+ console.error(err);
134
+ logger.error('failed to push notification to slack', { api: url, error: err });
135
+ }
136
+ }
76
137
  }
77
138
 
78
139
  SlackSender.type = 'slack';
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.18-beta-7aa6be5a",
6
+ "version": "1.16.18-beta-bb4ebacc",
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.18-beta-7aa6be5a",
23
- "@abtnode/auth": "1.16.18-beta-7aa6be5a",
24
- "@abtnode/certificate-manager": "1.16.18-beta-7aa6be5a",
25
- "@abtnode/constant": "1.16.18-beta-7aa6be5a",
26
- "@abtnode/cron": "1.16.18-beta-7aa6be5a",
27
- "@abtnode/logger": "1.16.18-beta-7aa6be5a",
28
- "@abtnode/models": "1.16.18-beta-7aa6be5a",
29
- "@abtnode/queue": "1.16.18-beta-7aa6be5a",
30
- "@abtnode/rbac": "1.16.18-beta-7aa6be5a",
31
- "@abtnode/router-provider": "1.16.18-beta-7aa6be5a",
32
- "@abtnode/static-server": "1.16.18-beta-7aa6be5a",
33
- "@abtnode/timemachine": "1.16.18-beta-7aa6be5a",
34
- "@abtnode/util": "1.16.18-beta-7aa6be5a",
35
- "@arcblock/did": "1.18.93",
36
- "@arcblock/did-auth": "1.18.93",
37
- "@arcblock/did-ext": "^1.18.93",
22
+ "@abtnode/analytics": "1.16.18-beta-bb4ebacc",
23
+ "@abtnode/auth": "1.16.18-beta-bb4ebacc",
24
+ "@abtnode/certificate-manager": "1.16.18-beta-bb4ebacc",
25
+ "@abtnode/constant": "1.16.18-beta-bb4ebacc",
26
+ "@abtnode/cron": "1.16.18-beta-bb4ebacc",
27
+ "@abtnode/logger": "1.16.18-beta-bb4ebacc",
28
+ "@abtnode/models": "1.16.18-beta-bb4ebacc",
29
+ "@abtnode/queue": "1.16.18-beta-bb4ebacc",
30
+ "@abtnode/rbac": "1.16.18-beta-bb4ebacc",
31
+ "@abtnode/router-provider": "1.16.18-beta-bb4ebacc",
32
+ "@abtnode/static-server": "1.16.18-beta-bb4ebacc",
33
+ "@abtnode/timemachine": "1.16.18-beta-bb4ebacc",
34
+ "@abtnode/util": "1.16.18-beta-bb4ebacc",
35
+ "@arcblock/did": "1.18.95",
36
+ "@arcblock/did-auth": "1.18.95",
37
+ "@arcblock/did-ext": "^1.18.95",
38
38
  "@arcblock/did-motif": "^1.1.13",
39
- "@arcblock/did-util": "1.18.93",
40
- "@arcblock/event-hub": "1.18.93",
41
- "@arcblock/jwt": "^1.18.93",
39
+ "@arcblock/did-util": "1.18.95",
40
+ "@arcblock/event-hub": "1.18.95",
41
+ "@arcblock/jwt": "^1.18.95",
42
42
  "@arcblock/pm2-events": "^0.0.5",
43
- "@arcblock/validator": "^1.18.93",
44
- "@arcblock/vc": "1.18.93",
45
- "@blocklet/constant": "1.16.18-beta-7aa6be5a",
46
- "@blocklet/env": "1.16.18-beta-7aa6be5a",
47
- "@blocklet/meta": "1.16.18-beta-7aa6be5a",
48
- "@blocklet/resolver": "1.16.18-beta-7aa6be5a",
49
- "@blocklet/sdk": "1.16.18-beta-7aa6be5a",
50
- "@did-space/client": "^0.3.12",
43
+ "@arcblock/validator": "^1.18.95",
44
+ "@arcblock/vc": "1.18.95",
45
+ "@blocklet/constant": "1.16.18-beta-bb4ebacc",
46
+ "@blocklet/env": "1.16.18-beta-bb4ebacc",
47
+ "@blocklet/meta": "1.16.18-beta-bb4ebacc",
48
+ "@blocklet/resolver": "1.16.18-beta-bb4ebacc",
49
+ "@blocklet/sdk": "1.16.18-beta-bb4ebacc",
50
+ "@did-space/client": "^0.3.22",
51
51
  "@fidm/x509": "^1.2.1",
52
- "@ocap/mcrypto": "1.18.93",
53
- "@ocap/util": "1.18.93",
54
- "@ocap/wallet": "1.18.93",
52
+ "@ocap/mcrypto": "1.18.95",
53
+ "@ocap/util": "1.18.95",
54
+ "@ocap/wallet": "1.18.95",
55
55
  "@slack/webhook": "^5.0.4",
56
56
  "archiver": "^5.3.1",
57
57
  "axios": "^0.27.2",
@@ -69,7 +69,7 @@
69
69
  "hasha": "^5.2.2",
70
70
  "is-base64": "^1.1.0",
71
71
  "is-url": "^1.2.4",
72
- "joi": "17.7.0",
72
+ "joi": "17.11.0",
73
73
  "joi-extension-semver": "^5.0.0",
74
74
  "js-yaml": "^4.1.0",
75
75
  "kill-port": "^2.0.1",
@@ -101,5 +101,5 @@
101
101
  "jest": "^27.5.1",
102
102
  "unzipper": "^0.10.11"
103
103
  },
104
- "gitHead": "b2da6c7e30adf84d7eca941ada27427344c49044"
104
+ "gitHead": "aca7ff8eb508ecf586108b99a9e07ceab2a62430"
105
105
  }