@abtnode/core 1.15.17 → 1.16.0-beta-b16cb035

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.
Files changed (119) hide show
  1. package/lib/api/node.js +67 -69
  2. package/lib/api/team.js +386 -55
  3. package/lib/blocklet/downloader/blocklet-downloader.js +226 -0
  4. package/lib/blocklet/downloader/bundle-downloader.js +272 -0
  5. package/lib/blocklet/downloader/constants.js +3 -0
  6. package/lib/blocklet/downloader/resolve-download.js +199 -0
  7. package/lib/blocklet/extras.js +83 -26
  8. package/lib/blocklet/hooks.js +18 -65
  9. package/lib/blocklet/manager/base.js +10 -16
  10. package/lib/blocklet/manager/disk.js +1679 -1566
  11. package/lib/blocklet/manager/helper/install-application-from-backup.js +177 -0
  12. package/lib/blocklet/manager/helper/install-application-from-dev.js +94 -0
  13. package/lib/blocklet/manager/helper/install-application-from-general.js +188 -0
  14. package/lib/blocklet/manager/helper/install-component-from-dev.js +84 -0
  15. package/lib/blocklet/manager/helper/install-component-from-upload.js +181 -0
  16. package/lib/blocklet/manager/helper/install-component-from-url.js +173 -0
  17. package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +450 -0
  18. package/lib/blocklet/manager/helper/rollback-cache.js +41 -0
  19. package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
  20. package/lib/blocklet/migration.js +30 -52
  21. package/lib/blocklet/storage/backup/audit-log.js +27 -0
  22. package/lib/blocklet/storage/backup/base.js +62 -0
  23. package/lib/blocklet/storage/backup/blocklet-extras.js +92 -0
  24. package/lib/blocklet/storage/backup/blocklet.js +70 -0
  25. package/lib/blocklet/storage/backup/blocklets.js +74 -0
  26. package/lib/blocklet/storage/backup/data.js +19 -0
  27. package/lib/blocklet/storage/backup/logs.js +24 -0
  28. package/lib/blocklet/storage/backup/routing-rule.js +19 -0
  29. package/lib/blocklet/storage/backup/spaces.js +240 -0
  30. package/lib/blocklet/storage/restore/base.js +67 -0
  31. package/lib/blocklet/storage/restore/blocklet-extras.js +86 -0
  32. package/lib/blocklet/storage/restore/blocklet.js +56 -0
  33. package/lib/blocklet/storage/restore/blocklets.js +43 -0
  34. package/lib/blocklet/storage/restore/logs.js +21 -0
  35. package/lib/blocklet/storage/restore/spaces.js +156 -0
  36. package/lib/blocklet/storage/utils/hash.js +51 -0
  37. package/lib/blocklet/storage/utils/zip.js +43 -0
  38. package/lib/cert.js +206 -0
  39. package/lib/event.js +237 -64
  40. package/lib/index.js +191 -83
  41. package/lib/migrations/1.0.21-update-config.js +1 -1
  42. package/lib/migrations/1.0.22-max-memory.js +1 -1
  43. package/lib/migrations/1.0.25.js +1 -1
  44. package/lib/migrations/1.0.32-update-config.js +1 -1
  45. package/lib/migrations/1.0.33-blocklets.js +1 -1
  46. package/lib/migrations/1.5.20-registry.js +15 -0
  47. package/lib/migrations/1.6.17-blocklet-children.js +48 -0
  48. package/lib/migrations/1.6.21-rename-ip-echo-domain.js +35 -0
  49. package/lib/migrations/1.6.4-security.js +59 -0
  50. package/lib/migrations/1.6.5-security.js +60 -0
  51. package/lib/migrations/1.6.9-update-node-info-and-certificate.js +38 -0
  52. package/lib/migrations/1.7.1-blocklet-setup.js +18 -0
  53. package/lib/migrations/1.7.12-blocklet-meta.js +51 -0
  54. package/lib/migrations/1.7.15-blocklet-bundle-source.js +42 -0
  55. package/lib/migrations/1.7.20-blocklet-component.js +41 -0
  56. package/lib/migrations/1.8.33-blocklet-mem-limit.js +20 -0
  57. package/lib/migrations/README.md +1 -1
  58. package/lib/migrations/index.js +6 -2
  59. package/lib/monitor/blocklet-runtime-monitor.js +200 -0
  60. package/lib/monitor/get-history-list.js +37 -0
  61. package/lib/monitor/node-runtime-monitor.js +228 -0
  62. package/lib/router/helper.js +572 -497
  63. package/lib/router/index.js +85 -21
  64. package/lib/router/manager.js +146 -187
  65. package/lib/states/README.md +36 -1
  66. package/lib/states/access-key.js +39 -17
  67. package/lib/states/audit-log.js +462 -0
  68. package/lib/states/base.js +4 -213
  69. package/lib/states/blocklet-extras.js +194 -138
  70. package/lib/states/blocklet.js +361 -104
  71. package/lib/states/cache.js +8 -6
  72. package/lib/states/challenge.js +5 -5
  73. package/lib/states/index.js +19 -36
  74. package/lib/states/migration.js +4 -4
  75. package/lib/states/node.js +135 -46
  76. package/lib/states/notification.js +22 -35
  77. package/lib/states/session.js +17 -9
  78. package/lib/states/site.js +50 -25
  79. package/lib/states/user.js +74 -20
  80. package/lib/states/webhook.js +10 -6
  81. package/lib/team/manager.js +124 -7
  82. package/lib/util/blocklet.js +1223 -246
  83. package/lib/util/chain.js +1 -1
  84. package/lib/util/default-node-config.js +5 -23
  85. package/lib/util/disk-monitor.js +13 -10
  86. package/lib/util/domain-status.js +84 -15
  87. package/lib/util/get-accessible-external-node-ip.js +2 -2
  88. package/lib/util/get-domain-for-blocklet.js +13 -0
  89. package/lib/util/get-meta-from-url.js +33 -0
  90. package/lib/util/index.js +207 -272
  91. package/lib/util/ip.js +6 -0
  92. package/lib/util/maintain.js +233 -0
  93. package/lib/util/public-to-store.js +85 -0
  94. package/lib/util/ready.js +1 -1
  95. package/lib/util/requirement.js +28 -9
  96. package/lib/util/reset-node.js +22 -7
  97. package/lib/util/router.js +13 -0
  98. package/lib/util/rpc.js +16 -0
  99. package/lib/util/store.js +179 -0
  100. package/lib/util/sysinfo.js +44 -0
  101. package/lib/util/ua.js +54 -0
  102. package/lib/validators/blocklet-extra.js +24 -0
  103. package/lib/validators/node.js +25 -12
  104. package/lib/validators/permission.js +16 -1
  105. package/lib/validators/role.js +17 -3
  106. package/lib/validators/router.js +40 -20
  107. package/lib/validators/trusted-passport.js +1 -0
  108. package/lib/validators/util.js +22 -5
  109. package/lib/webhook/index.js +45 -35
  110. package/lib/webhook/sender/index.js +5 -0
  111. package/lib/webhook/sender/slack/index.js +1 -1
  112. package/lib/webhook/sender/wallet/index.js +48 -0
  113. package/package.json +54 -36
  114. package/lib/blocklet/registry.js +0 -205
  115. package/lib/states/https-cert.js +0 -67
  116. package/lib/util/get-ip-dns-domain-for-blocklet.js +0 -19
  117. package/lib/util/service.js +0 -66
  118. package/lib/util/upgrade.js +0 -178
  119. /package/lib/{queue.js → util/queue.js} +0 -0
@@ -0,0 +1,44 @@
1
+ const maxBy = require('lodash/maxBy');
2
+ const SysInfo = require('systeminformation');
3
+
4
+ const getSysInfo = async () => {
5
+ const info = await SysInfo.get({
6
+ cpu: 'physicalCores',
7
+ mem: '*',
8
+ currentLoad: '*',
9
+ fsSize: '*',
10
+ diskLayout: '*',
11
+ osInfo: 'platform',
12
+ });
13
+
14
+ let drives = info.fsSize;
15
+ if (info.osInfo.platform === 'darwin') {
16
+ const systemDrives = (info.fsSize || []).filter((x) => x.type === 'APFS');
17
+ const rootDrive = systemDrives.find((x) => x.mount === '/');
18
+ if (rootDrive) {
19
+ const maxUsedDrive = maxBy(systemDrives, (x) => x.used);
20
+ rootDrive.used = maxUsedDrive.used;
21
+ drives = [rootDrive];
22
+ } else {
23
+ drives = [];
24
+ }
25
+ }
26
+
27
+ return {
28
+ cpu: {
29
+ ...info.currentLoad,
30
+ ...info.cpu,
31
+ },
32
+ mem: info.mem,
33
+ os: info.osInfo,
34
+ disks: drives.map((x) => ({
35
+ device: x.fs,
36
+ mountPoint: x.mount,
37
+ total: x.size,
38
+ used: x.used,
39
+ free: x.size - x.used,
40
+ })),
41
+ };
42
+ };
43
+
44
+ module.exports.getSysInfo = getSysInfo;
package/lib/util/ua.js ADDED
@@ -0,0 +1,54 @@
1
+ const semver = require('semver');
2
+ const parser = require('ua-parser-js');
3
+ const packageJson = require('../../package.json');
4
+
5
+ const { memoizeAsync } = require('./index');
6
+
7
+ const parseWalletUA = (userAgent) => {
8
+ const ua = (userAgent || '').toString().toLowerCase();
9
+ let os = '';
10
+ let version = '';
11
+ if (ua.indexOf('android') > -1) {
12
+ os = 'android';
13
+ } else if (ua.indexOf('darwin') > -1) {
14
+ os = 'ios';
15
+ } else if (ua.indexOf('arcwallet') === 0) {
16
+ os = 'web';
17
+ }
18
+
19
+ const match = ua.split(/\s+/).find((x) => x.startsWith('arcwallet'));
20
+ if (match) {
21
+ const tmp = match.split('/');
22
+ if (tmp.length > 1 && semver.coerce(tmp[1])) {
23
+ version = semver.coerce(tmp[1]).version;
24
+ }
25
+ }
26
+
27
+ return { os, version };
28
+ };
29
+
30
+ const parse = memoizeAsync((ua) => {
31
+ let result = parser(ua);
32
+ if (result.browser.name) {
33
+ return result;
34
+ }
35
+
36
+ result = parseWalletUA(ua);
37
+ if (result.version) {
38
+ return {
39
+ browser: {
40
+ name: `DID Wallet ${result.os.toUpperCase()}`,
41
+ version: result.version,
42
+ },
43
+ };
44
+ }
45
+
46
+ return {
47
+ browser: {
48
+ name: 'CLI',
49
+ version: packageJson.version,
50
+ },
51
+ };
52
+ });
53
+
54
+ module.exports = { parse };
@@ -0,0 +1,24 @@
1
+ const JOI = require('joi');
2
+ const { didExtension } = require('@blocklet/meta/lib/extension');
3
+ const { blockletController, createValidator } = require('./util');
4
+
5
+ const Joi = JOI.extend(didExtension);
6
+
7
+ const addMeta = Joi.object({
8
+ did: Joi.ref('meta.did'),
9
+ meta: Joi.object({
10
+ did: Joi.DID().required(),
11
+ name: Joi.string().required(),
12
+ }).required(),
13
+ controller: blockletController.optional(),
14
+ }).required();
15
+
16
+ const updateExpiredInfo = Joi.object({
17
+ did: Joi.DID().required(),
18
+ expiredAt: Joi.string().required(),
19
+ });
20
+
21
+ module.exports = {
22
+ validateAddMeta: createValidator(addMeta),
23
+ validateExpiredInfo: createValidator(updateExpiredInfo),
24
+ };
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable newline-per-chained-call */
2
+ const { GATEWAY_REQ_LIMIT } = require('@abtnode/constant');
2
3
  const Joi = require('joi');
3
4
  const { getMultipleLangParams } = require('./util');
4
5
 
@@ -9,14 +10,23 @@ const nodeInfoSchema = Joi.object({
9
10
  description: Joi.string()
10
11
  .required()
11
12
  .messages({ zh: { 'string.empty': '描述不能为空' }, en: { 'string.empty': 'Description cannot be empty' } }),
13
+ registerUrl: Joi.string()
14
+ .uri({ scheme: [/https?/] })
15
+ .label('register url')
16
+ .allow('')
17
+ .optional()
18
+ .messages({
19
+ zh: { 'string.uriCustomScheme': '应用启动器必须是合法的 URL' },
20
+ en: { 'string.uriCustomScheme': 'Blocklet Launcher must be a valid URL' },
21
+ }),
12
22
  webWalletUrl: Joi.string()
13
23
  .uri({ scheme: [/https?/] })
14
24
  .label('web wallet url')
15
25
  .allow('')
16
26
  .optional()
17
27
  .messages({
18
- zh: { 'string.uriCustomScheme': 'Web DID Wallet 必须是合法的 URL' },
19
- en: { 'string.uriCustomScheme': 'Web DID Wallet must be a valid URL' },
28
+ zh: { 'string.uriCustomScheme': 'Web Wallet 必须是合法的 URL' },
29
+ en: { 'string.uriCustomScheme': 'Web Wallet must be a valid URL' },
20
30
  }),
21
31
  autoUpgrade: Joi.boolean(),
22
32
  enableWelcomePage: Joi.boolean(),
@@ -31,18 +41,21 @@ const nodeInfoSchema = Joi.object({
31
41
  'number.max': 'Disk usage alert threshold cannot be higher than 99%',
32
42
  },
33
43
  }),
34
- // removed in 1.5.1
35
- registerUrl: Joi.string()
36
- .uri({ scheme: [/https?/] })
37
- .label('register url')
38
- .allow('')
39
- .optional()
40
- .messages({
41
- zh: { 'string.uriCustomScheme': '注册地址必须是合法的 URL' },
42
- en: { 'string.uriCustomScheme': 'Registry URL must be a valid URL' },
43
- }),
44
44
  }).options({ stripUnknown: true });
45
45
 
46
+ const updateGatewaySchema = Joi.object({
47
+ requestLimit: Joi.object({
48
+ enabled: Joi.bool().required(),
49
+ rate: Joi.number()
50
+ .min(GATEWAY_REQ_LIMIT.min)
51
+ .max(GATEWAY_REQ_LIMIT.max)
52
+ .when('requestLimit.enabled', { is: true, then: Joi.required() }),
53
+ ipHeader: Joi.string().allow('').trim(),
54
+ }),
55
+ cacheEnabled: Joi.bool().optional().default(true),
56
+ });
57
+
46
58
  module.exports = {
47
59
  validateNodeInfo: (entity, context) => nodeInfoSchema.validateAsync(entity, getMultipleLangParams(context)),
60
+ validateUpdateGateway: (entity, context) => updateGatewaySchema.validateAsync(entity, getMultipleLangParams(context)),
48
61
  };
@@ -2,7 +2,22 @@
2
2
  const JOI = require('joi');
3
3
  const { getMultipleLangParams } = require('./util');
4
4
 
5
- const nameSchema = JOI.string().trim().max(64);
5
+ const nameSchema = JOI.string()
6
+ .trim()
7
+ .max(64)
8
+ .custom((name) => {
9
+ const arr = name.split('_');
10
+ const formatTip = 'The format of permission name should be "{action}_{resource}", e.g. query_data';
11
+ if (arr.length > 2) {
12
+ throw new Error(`Too much "_". ${formatTip}`);
13
+ }
14
+
15
+ if (arr.length < 2) {
16
+ throw new Error(formatTip);
17
+ }
18
+
19
+ return name;
20
+ });
6
21
  const descriptionSchema = JOI.string().trim().max(600);
7
22
 
8
23
  const createPermissionSchema = JOI.object({
@@ -2,18 +2,32 @@
2
2
  const JOI = require('joi');
3
3
  const { getMultipleLangParams } = require('./util');
4
4
 
5
- const nameSchema = JOI.string().trim().max(64);
5
+ const roleNameSchema = JOI.string()
6
+ .trim()
7
+ .max(64)
8
+ .custom((value) => {
9
+ if (value.startsWith('blocklet')) {
10
+ throw new Error('role name cannot start with "blocklet"');
11
+ }
12
+
13
+ if (/[^a-zA-z0-9]/.test(value)) {
14
+ throw new Error('role name can include only numbers or letters');
15
+ }
16
+
17
+ return value;
18
+ });
19
+
6
20
  const titleSchema = JOI.string().trim().max(25);
7
21
  const descriptionSchema = JOI.string().trim().max(600);
8
22
 
9
23
  const createRoleSchema = JOI.object({
10
- name: nameSchema.required(),
24
+ name: roleNameSchema.required(),
11
25
  title: titleSchema.required(),
12
26
  description: descriptionSchema.required(),
13
27
  });
14
28
 
15
29
  const updateRoleSchema = JOI.object({
16
- name: nameSchema.required(),
30
+ name: roleNameSchema.required(),
17
31
  title: titleSchema,
18
32
  description: descriptionSchema,
19
33
  });
@@ -1,10 +1,13 @@
1
1
  /* eslint-disable newline-per-chained-call */
2
2
  const Joi = require('joi');
3
- const { DOMAIN_FOR_DEFAULT_SITE, ROUTING_RULE_TYPES } = require('@abtnode/constant');
3
+ const { DOMAIN_FOR_DEFAULT_SITE, ROUTING_RULE_TYPES, ROUTER_CACHE_GROUPS } = require('@abtnode/constant');
4
+ const urlFriendly = require('@blocklet/meta/lib/url-friendly').default;
4
5
  const { getMultipleLangParams } = require('./util');
5
6
 
6
7
  const WILDCARD_DOMAIN_REGEX = /^\*.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/;
7
8
 
9
+ const DOMAIN_SCHEMA = Joi.string().domain({ minDomainSegments: 1, tlds: false }).lowercase();
10
+
8
11
  const domainMessages = {
9
12
  en: {
10
13
  'alternatives.match': 'Illegal domain, please enter a domain like arcblock, arcblock.io, *.arcblock.io',
@@ -25,7 +28,8 @@ const ruleSchema = {
25
28
  .messages({
26
29
  zh: { 'string.empty': 'URL 前缀不能为空', 'string.max': 'URL 前缀的最大长度是 150' },
27
30
  en: { 'string.empty': 'URL prefix cannot be empty', 'string.max': 'The maximum length of URL prefix is 150' },
28
- }),
31
+ })
32
+ .custom((value) => urlFriendly(value)),
29
33
  groupPathPrefix: Joi.string().trim().min(1).max(150), // path prefix of interface of root blocklet
30
34
  header: Joi.any(), // TODO: header does not take effect
31
35
  }),
@@ -39,21 +43,33 @@ const ruleSchema = {
39
43
  ROUTING_RULE_TYPES.BLOCKLET,
40
44
  ROUTING_RULE_TYPES.REDIRECT,
41
45
  ROUTING_RULE_TYPES.GENERAL_PROXY,
46
+ ROUTING_RULE_TYPES.DIRECT_RESPONSE,
42
47
  ROUTING_RULE_TYPES.NONE
43
48
  )
44
49
  .required(),
45
50
  did: Joi.string().label('did').when('type', { is: ROUTING_RULE_TYPES.BLOCKLET, then: Joi.required() }), // root blocklet did
46
- realDid: Joi.string().label('real did'), // child blocklet did
47
51
  port: Joi.number().label('port').port().when('type', { is: ROUTING_RULE_TYPES.BLOCKLET, then: Joi.required() }),
48
52
  url: Joi.string().label('url').when('type', { is: ROUTING_RULE_TYPES.REDIRECT, then: Joi.required() }),
49
53
  redirectCode: Joi.alternatives()
50
- .try(301, 302)
54
+ .try(301, 302, 307, 308)
51
55
  .label('redirect code')
52
56
  .when('type', { is: ROUTING_RULE_TYPES.REDIRECT, then: Joi.required() }),
53
57
  interfaceName: Joi.string() // root interface
54
58
  .label('interface name')
55
59
  .when('type', { is: ROUTING_RULE_TYPES.BLOCKLET, then: Joi.required() }),
56
- realInterfaceName: Joi.string().label('real interface name'), // child blocklet interface
60
+ response: Joi.object({ status: Joi.number().required(), contentType: Joi.string(), body: Joi.string().required() })
61
+ .label('response')
62
+ .when('type', {
63
+ is: ROUTING_RULE_TYPES.DIRECT_RESPONSE,
64
+ then: Joi.required(),
65
+ }),
66
+ componentId: Joi.string().label('component id'), // component global id
67
+ // FUTURE: blocklets can register routing rules for provider cache
68
+ cacheGroup: Joi.string()
69
+ .label('cache group')
70
+ .valid(...Object.keys(ROUTER_CACHE_GROUPS))
71
+ .allow('')
72
+ .default(''),
57
73
  },
58
74
 
59
75
  // List of services that manipulate the request before the upstream blocklet
@@ -73,11 +89,7 @@ const ruleSchema = {
73
89
  };
74
90
 
75
91
  const corsSchema = Joi.array()
76
- .items(
77
- Joi.string().domain({ minDomainSegments: 1, tlds: false }),
78
- Joi.string().valid(DOMAIN_FOR_DEFAULT_SITE),
79
- Joi.string().ip()
80
- )
92
+ .items(DOMAIN_SCHEMA, Joi.string().valid(DOMAIN_FOR_DEFAULT_SITE, '__none__'), Joi.string().ip())
81
93
  .min(1)
82
94
  .optional();
83
95
 
@@ -85,26 +97,30 @@ const ruleJoiSchema = Joi.object(ruleSchema);
85
97
 
86
98
  const addDomainAlias = Joi.alternatives()
87
99
  .try(
88
- Joi.string().domain({ minDomainSegments: 1, tlds: false }),
100
+ DOMAIN_SCHEMA,
89
101
  Joi.string().regex(WILDCARD_DOMAIN_REGEX) // 这种其实是一种特殊的 tld
90
102
  )
91
103
  .required();
92
104
 
105
+ const domainAliases = Joi.array().items(
106
+ Joi.object({
107
+ value: addDomainAlias,
108
+ isProtected: Joi.boolean(),
109
+ })
110
+ );
111
+
112
+ const updateAliases = domainAliases.required();
113
+
93
114
  const addSiteSchema = Joi.object({
94
115
  domain: Joi.alternatives()
95
116
  .try(
96
- Joi.string().domain({ minDomainSegments: 1, tlds: false }),
117
+ DOMAIN_SCHEMA,
97
118
  Joi.string().valid('', DOMAIN_FOR_DEFAULT_SITE),
98
119
  Joi.string().regex(WILDCARD_DOMAIN_REGEX) // 这种其实是一种特殊的 tld
99
120
  )
100
121
  .required()
101
122
  .messages(domainMessages),
102
- domainAliases: Joi.array().items(
103
- Joi.object({
104
- value: addDomainAlias,
105
- isProtected: Joi.boolean(),
106
- })
107
- ),
123
+ domainAliases,
108
124
  isProtected: Joi.boolean(),
109
125
  rules: Joi.array().items(ruleJoiSchema),
110
126
  corsAllowedOrigins: corsSchema,
@@ -115,12 +131,12 @@ const updateSite = Joi.object({
115
131
  corsAllowedOrigins: corsSchema,
116
132
  domain: Joi.alternatives()
117
133
  .try(
118
- Joi.string().domain({ minDomainSegments: 1, tlds: false }),
134
+ DOMAIN_SCHEMA,
119
135
  Joi.string().regex(WILDCARD_DOMAIN_REGEX) // 这种其实是一种特殊的 tld
120
136
  )
121
137
  .optional()
122
138
  .messages(domainMessages),
123
- });
139
+ }).unknown();
124
140
 
125
141
  const addRuleSchema = Joi.object({
126
142
  id: Joi.string().required(),
@@ -142,10 +158,14 @@ const validateUpdateSite = (entity, context) => updateSite.validateAsync(entity,
142
158
  const validateAddRule = (entity, context) => addRuleSchema.validateAsync(entity, getMultipleLangParams(context));
143
159
  const validateEditRule = (entity, context) => editRuleSchema.validateAsync(entity, getMultipleLangParams(context));
144
160
 
161
+ const validateUpdateDomainAliases = (entity, context) =>
162
+ updateAliases.validateAsync(entity, getMultipleLangParams(context));
163
+
145
164
  module.exports = {
146
165
  validateAddRule,
147
166
  validateAddSite,
148
167
  validateEditRule,
149
168
  validateAddDomainAlias,
150
169
  validateUpdateSite,
170
+ validateUpdateDomainAliases,
151
171
  };
@@ -8,6 +8,7 @@ const Joi = JOI.extend(didExtension);
8
8
  const trustedPassportsSchema = Joi.array().items(
9
9
  Joi.object({
10
10
  issuerDid: Joi.DID().required(),
11
+ remark: Joi.string().trim(),
11
12
  mappings: Joi.array().items(
12
13
  Joi.object({
13
14
  from: Joi.object({
@@ -1,9 +1,26 @@
1
1
  const get = require('lodash/get');
2
+ const JOI = require('joi');
3
+ const { didExtension } = require('@blocklet/meta/lib/extension');
4
+
5
+ const Joi = JOI.extend(didExtension);
6
+
7
+ const getMultipleLangParams = (context) => ({
8
+ errors: {
9
+ language: get(context, 'query.locale', 'en'),
10
+ },
11
+ });
12
+
13
+ const blockletController = Joi.object({
14
+ nftId: Joi.DID().required(),
15
+ nftOwner: Joi.DID().required(),
16
+ chainHost: Joi.string().uri().required(),
17
+ appMaxCount: Joi.number().required().min(1),
18
+ }).options({ stripUnknown: true });
19
+
20
+ const createValidator = (schema) => (entity) => schema.validateAsync(entity);
2
21
 
3
22
  module.exports = {
4
- getMultipleLangParams: (context) => ({
5
- errors: {
6
- language: get(context, 'query.locale', 'en'),
7
- },
8
- }),
23
+ createValidator,
24
+ getMultipleLangParams,
25
+ blockletController,
9
26
  };
@@ -1,13 +1,16 @@
1
1
  const logger = require('@abtnode/logger')('@abtnode/core:webhook:index');
2
- const upperFirst = require('lodash/upperFirst');
3
2
 
3
+ const { evaluateURLs } = require('@abtnode/util/lib/url-evaluation');
4
+ const checkURLAccessible = require('@abtnode/util/lib/url-evaluation/check-accessible-node');
5
+ const { EVENTS } = require('@abtnode/constant');
4
6
  const WebHookSender = require('./sender');
5
- const createQueue = require('../queue');
7
+ const WalletSender = require('./sender/wallet');
8
+ const createQueue = require('../util/queue');
6
9
  const IP = require('../util/ip');
7
10
  const states = require('../states');
8
11
  const { getBaseUrls } = require('../util');
9
12
 
10
- const getSlackUrlInfo = (actionPath = '/notifications', urls) => {
13
+ const getSlackUrlInfo = async (actionPath = '/notifications', urls) => {
11
14
  const info = [];
12
15
 
13
16
  if (actionPath && actionPath.startsWith('/blocklet/')) {
@@ -28,29 +31,30 @@ const getSlackUrlInfo = (actionPath = '/notifications', urls) => {
28
31
  });
29
32
  }
30
33
 
31
- const httpUrls = urls.filter((x) => x.url.startsWith('http://'));
32
- const httpsUrls = urls.filter((x) => x.url.startsWith('https://'));
33
-
34
- const showUrls = httpsUrls.length ? httpsUrls : httpUrls;
35
- const elements = [];
36
- showUrls.forEach((x) => {
37
- const { protocol } = new URL(x.url);
38
- const normalized = `${x.url}${actionPath}`.replace(`${protocol}//`, '').replace(/\/+/g, '/');
39
- const url = `${protocol}//${normalized}`;
40
- elements.push({
41
- type: 'button',
42
- text: {
43
- type: 'plain_text',
44
- text: `${upperFirst(x.type)} Address`,
45
- },
46
- style: 'primary',
47
- value: `${upperFirst(x.type)} Address`,
48
- url,
49
- });
50
- });
34
+ const prioritys = await evaluateURLs(
35
+ urls.map((item) => item.url),
36
+ { checkAccessble: checkURLAccessible }
37
+ );
38
+ const priorityUrl = prioritys[0].url;
39
+
40
+ const { protocol } = new URL(priorityUrl);
41
+ const normalized = `${priorityUrl}${actionPath}`.replace(`${protocol}//`, '').replace(/\/+/g, '/');
42
+ const url = `${protocol}//${normalized}`;
43
+
51
44
  info.push({
52
45
  type: 'actions',
53
- elements,
46
+ elements: [
47
+ {
48
+ type: 'button',
49
+ text: {
50
+ type: 'plain_text',
51
+ text: 'Click Me',
52
+ },
53
+ style: 'primary',
54
+ value: 'Click Me',
55
+ url,
56
+ },
57
+ ],
54
58
  });
55
59
 
56
60
  return info;
@@ -66,9 +70,14 @@ module.exports = ({ events, dataDirs, instance }) => {
66
70
  const webhookList = await webhookState.list();
67
71
  const nodeInfo = await nodeState.read();
68
72
  const { internal, external } = await IP.get();
69
- const baseUrls = await getBaseUrls(instance, { internal, external });
73
+ const baseUrls = await getBaseUrls(instance, [external, internal]);
70
74
  const senderFns = {};
71
75
 
76
+ // Always send message to wallet
77
+ webhookList.push({
78
+ type: WalletSender.type,
79
+ });
80
+
72
81
  if (webhookList.length) {
73
82
  for (let i = 0; i < webhookList.length; i++) {
74
83
  const item = webhookList[i];
@@ -76,10 +85,10 @@ module.exports = ({ events, dataDirs, instance }) => {
76
85
  const senderInstance = WebHookSender.getMessageSender(item.type);
77
86
  senderFns[item.type] = senderInstance.send.bind(senderInstance);
78
87
  }
79
- const { description } = nodeInfo;
80
- const options = { ...message, nodeInfo: `*${description}*` };
88
+ const options = { ...message, nodeInfo, node: instance };
81
89
  if (item.type === 'slack') {
82
- options.urlInfo = getSlackUrlInfo(message.action, baseUrls);
90
+ // eslint-disable-next-line
91
+ options.urlInfo = await getSlackUrlInfo(message.action, baseUrls);
83
92
  }
84
93
  try {
85
94
  // eslint-disable-next-line
@@ -106,15 +115,15 @@ module.exports = ({ events, dataDirs, instance }) => {
106
115
  },
107
116
  });
108
117
 
109
- events.on('notification.create', (data) => {
118
+ events.on(EVENTS.NOTIFICATION_CREATE, (data) => {
110
119
  const { title, description, severity, action, entityType } = data;
111
120
  queue.push({ title, description, status: severity, action, entityType });
112
121
  });
113
122
 
114
- events.on('node.started', async (message) => {
123
+ events.on(EVENTS.NODE_STARTED, async (message) => {
115
124
  await sendMessage(message);
116
125
  });
117
- events.on('node.stopped', async (message) => {
126
+ events.on(EVENTS.NODE_STOPPED, async (message) => {
118
127
  await sendMessage(message);
119
128
  });
120
129
 
@@ -125,13 +134,14 @@ module.exports = ({ events, dataDirs, instance }) => {
125
134
 
126
135
  const createWebhook = async (webhook) => {
127
136
  try {
128
- const createRes = await webhookState.create(webhook);
129
- await sentTextMessage(createRes, `A ${createRes.type} integration is now *successfully added*`);
137
+ const mockCreateRes = await webhookState.create(webhook, { mock: true });
138
+ await sentTextMessage(mockCreateRes, `A ${mockCreateRes.type} integration is now *successfully added*`);
130
139
 
140
+ const createRes = await webhookState.create(webhook);
131
141
  return createRes;
132
142
  } catch (err) {
133
- logger.error('create webhook error', { err });
134
- throw new Error(err.message);
143
+ logger.error('Failed to create webhook', { err });
144
+ throw new Error(`Failed to create webhook: ${err.message}`);
135
145
  }
136
146
  };
137
147
 
@@ -3,6 +3,7 @@ const logger = require('@abtnode/logger')('@abtnode/core:sender');
3
3
 
4
4
  const Slack = require('./slack');
5
5
  const Api = require('./api');
6
+ const Wallet = require('./wallet');
6
7
 
7
8
  const SenderMap = new Map([
8
9
  [Slack.type, Slack],
@@ -12,6 +13,10 @@ const SenderMap = new Map([
12
13
  const getSenderNames = () => [...SenderMap.keys()];
13
14
 
14
15
  const getSender = (name) => {
16
+ if (name === Wallet.type) {
17
+ return Wallet;
18
+ }
19
+
15
20
  if (!SenderMap.has(name)) {
16
21
  logger.error(`getSender:sender name [${name}] does not exist`);
17
22
  return null;
@@ -29,7 +29,7 @@ class SlackSender extends BaseSender {
29
29
  type: 'section',
30
30
  text: {
31
31
  type: 'mrkdwn',
32
- text: nodeInfo,
32
+ text: `*${nodeInfo?.name}*`,
33
33
  },
34
34
  },
35
35
  {
@@ -0,0 +1,48 @@
1
+ const logger = require('@abtnode/logger')('@abtnode/core:sender:api');
2
+ const { sendToUser } = require('@blocklet/sdk/lib/util/send-notification');
3
+ const { ROLES, PASSPORT_STATUS } = require('@abtnode/constant');
4
+ const BaseSender = require('../base');
5
+
6
+ class WalletSender extends BaseSender {
7
+ async send(params, data = {}) {
8
+ const { title, description, nodeInfo, node } = data;
9
+
10
+ try {
11
+ const sender = {
12
+ appDid: nodeInfo.did,
13
+ appSk: nodeInfo.sk,
14
+ };
15
+
16
+ const message = {
17
+ title,
18
+ body: description,
19
+ };
20
+
21
+ const { users } = await node.getUsers({ teamDid: nodeInfo.did, paging: { pageSize: 100 } });
22
+ const adminUsers = users
23
+ .filter(
24
+ (x) =>
25
+ x.approved &&
26
+ (x.passports || []).some(
27
+ (y) => [ROLES.OWNER, ROLES.ADMIN].includes(y.name) && y.status === PASSPORT_STATUS.VALID
28
+ )
29
+ )
30
+ .map((x) => x.did);
31
+
32
+ if (!adminUsers.length) {
33
+ return;
34
+ }
35
+
36
+ await sendToUser(adminUsers, message, sender, process.env.ABT_NODE_SERVICE_PORT);
37
+ } catch (error) {
38
+ delete error.request;
39
+ delete error.response;
40
+ delete error.config;
41
+ logger.error('failed to push notification to wallet', { error });
42
+ }
43
+ }
44
+ }
45
+
46
+ WalletSender.type = 'wallet';
47
+
48
+ module.exports = WalletSender;