@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
package/lib/util/index.js CHANGED
@@ -6,40 +6,33 @@ const unzipper = require('unzipper');
6
6
  const crypto = require('crypto');
7
7
  const shell = require('shelljs');
8
8
  const get = require('lodash/get');
9
- const uniqBy = require('lodash/uniqBy');
10
9
  const pickBy = require('lodash/pickBy');
11
10
  const { isFromPublicKey } = require('@arcblock/did');
12
11
  const joinUrl = require('url-join');
13
12
  const { Certificate } = require('@fidm/x509');
14
13
  const getPortLib = require('get-port');
15
- const isIp = require('is-ip');
16
14
  const v8 = require('v8');
17
15
  const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
16
+ const axios = require('@abtnode/util/lib/axios');
17
+ const { encode: encodeBase32 } = require('@abtnode/util/lib/base32');
18
18
  const parseBlockletMeta = require('@blocklet/meta/lib/parse');
19
- const { validateMeta, fixAndValidateService } = require('@blocklet/meta/lib/validate');
20
- const { BlockletStatus, BLOCKLET_INTERFACE_WELLKNOWN } = require('@blocklet/meta/lib/constants');
19
+ const { BlockletStatus } = require('@blocklet/constant');
20
+ const { replaceSlotToIp } = require('@blocklet/meta/lib/util');
21
21
  const {
22
22
  StatusCode,
23
- ROUTER_PROVIDER_NONE,
24
23
  DOMAIN_FOR_DEFAULT_SITE,
25
24
  DOMAIN_FOR_IP_SITE,
26
25
  DOMAIN_FOR_IP_SITE_REGEXP,
27
26
  DEFAULT_HTTP_PORT,
28
27
  DEFAULT_HTTPS_PORT,
29
- ROUTING_RULE_TYPES,
30
28
  SLOT_FOR_IP_DNS_SITE,
31
- DEFAULT_IP_DNS_DOMAIN_SUFFIX,
32
- BLOCKLET_SITE_GROUP_SUFFIX,
29
+ NODE_MODES,
33
30
  } = require('@abtnode/constant');
34
- const { BLOCKLET_INTERFACE_TYPE_WEB } = require('@blocklet/meta/lib/constants');
35
31
 
36
32
  const DEFAULT_WELLKNOWN_PORT = 8088;
37
33
 
38
34
  const logger = require('@abtnode/logger')('@abtnode/core:util');
39
35
 
40
- const { getServices, getServicesFromBlockletInterface } = require('./service');
41
- const request = require('./request');
42
-
43
36
  const validateOwner = (owner) => {
44
37
  try {
45
38
  return owner && owner.did && owner.pk && isFromPublicKey(owner.did, owner.pk);
@@ -54,27 +47,21 @@ const fromStatus = (v) => {
54
47
  return match ? match[0] : 'unknown';
55
48
  };
56
49
 
57
- const getInterfaceUrl = ({ baseUrl, url, version }) => {
58
- if (!url) {
59
- return '';
60
- }
61
-
62
- if (url.startsWith('https://') || url.startsWith('http://')) {
63
- return url;
64
- }
65
-
66
- const parsed = new URL(joinUrl(baseUrl, url));
67
- const params = parsed.searchParams;
68
-
69
- if (version) {
70
- params.set('__bv__', version);
50
+ const replaceDomainSlot = ({ domain, context = {}, nodeIp }) => {
51
+ let processed = domain;
52
+ if (processed.includes(SLOT_FOR_IP_DNS_SITE)) {
53
+ const ipRegex = /\d+[-.]\d+[-.]\d+[-.]\d+/;
54
+ const match = ipRegex.exec(context.hostname);
55
+ if (match) {
56
+ const ip = match[0];
57
+ processed = replaceSlotToIp(processed, ip);
58
+ } else if (nodeIp) {
59
+ processed = replaceSlotToIp(processed, nodeIp);
60
+ }
71
61
  }
72
-
73
- return parsed.href;
62
+ return processed;
74
63
  };
75
64
 
76
- const trimSlash = (str = '') => str.replace(/^\/+/, '').replace(/\/+$/, '');
77
-
78
65
  const getBlockletHost = ({ domain, context, nodeIp }) => {
79
66
  const { protocol, port } = context || {};
80
67
 
@@ -87,16 +74,7 @@ const getBlockletHost = ({ domain, context, nodeIp }) => {
87
74
  tmpDomain = context.hostname || '';
88
75
  }
89
76
 
90
- if (tmpDomain.includes(SLOT_FOR_IP_DNS_SITE)) {
91
- const ipRegex = /\d+[-.]\d+[-.]\d+[-.]\d+/;
92
- const match = ipRegex.exec(context.hostname);
93
- if (match) {
94
- const ip = match[0].replace(/\./g, '-');
95
- tmpDomain = tmpDomain.replace(SLOT_FOR_IP_DNS_SITE, ip);
96
- } else if (nodeIp) {
97
- tmpDomain = tmpDomain.replace(SLOT_FOR_IP_DNS_SITE, nodeIp.replace(/\./g, '-'));
98
- }
99
- }
77
+ tmpDomain = replaceDomainSlot({ domain: tmpDomain, context, nodeIp });
100
78
 
101
79
  if (!port) {
102
80
  return tmpDomain;
@@ -113,136 +91,7 @@ const getBlockletHost = ({ domain, context, nodeIp }) => {
113
91
  return `${tmpDomain}:${port}`;
114
92
  };
115
93
 
116
- /**
117
- * Get config of auth service of blocklet component specified the routing rule
118
- * @param {*} rule routing rule
119
- * @param {*} blocklet
120
- * @returns
121
- */
122
- const getAuthConfig = (rule, blocklet) => {
123
- if (!blocklet || !rule || rule.to.type !== ROUTING_RULE_TYPES.BLOCKLET || rule.to.did !== blocklet.meta.did) {
124
- return null;
125
- }
126
-
127
- // find blocklet component
128
- const isRootRule = rule.to.did === rule.to.realDid;
129
- let _blocklet;
130
- if (isRootRule) {
131
- _blocklet = blocklet;
132
- } else {
133
- _blocklet = (blocklet.children || []).find((x) => x.meta.did === rule.to.realDid);
134
- }
135
- if (!_blocklet) {
136
- return null;
137
- }
138
-
139
- // find interface in meta
140
- const interfaceName = rule.to.realInterfaceName;
141
- const { interfaces } = _blocklet.meta;
142
- const _interface = interfaces.find((x) => x.type === BLOCKLET_INTERFACE_TYPE_WEB && x.name === interfaceName);
143
-
144
- if (!_interface) {
145
- return null;
146
- }
147
-
148
- // find auth service
149
- const auth = getServicesFromBlockletInterface(_interface, { stringifyConfig: false }).find(
150
- (x) => x.name === '@abtnode/auth-service'
151
- );
152
-
153
- if (!auth) {
154
- return null;
155
- }
156
-
157
- return auth.config;
158
- };
159
-
160
- const getBlockletBaseUrls = ({ routingEnabled = false, port, rules = [], context = {}, blocklet, nodeIp }) => {
161
- let baseUrls = [];
162
-
163
- if (routingEnabled && Array.isArray(rules) && rules.length > 0) {
164
- baseUrls = rules
165
- .filter((rule) => !(rule.from.domain || '').endsWith(BLOCKLET_SITE_GROUP_SUFFIX))
166
- .map((rule) => {
167
- const host = getBlockletHost({ domain: rule.from.domain, context, nodeIp });
168
- if (host) {
169
- let protocol = 'http'; // TODO: 这里固定为 http, 因为判断 url 是不是 https 和证书相关,这里实现的话比较复杂
170
- if (host.includes(DEFAULT_IP_DNS_DOMAIN_SUFFIX)) {
171
- protocol = 'https';
172
- }
173
-
174
- return {
175
- ruleId: rule.id,
176
- baseUrl: `${protocol}://${host}/${trimSlash(rule.from.pathPrefix)}`,
177
- interfaceName: get(rule, 'to.interfaceName', ''),
178
- authConfig: getAuthConfig(rule, blocklet),
179
- };
180
- }
181
-
182
- return null;
183
- })
184
- .filter(Boolean);
185
- }
186
-
187
- if (!baseUrls.length && isIp(context.hostname)) {
188
- baseUrls = [{ baseUrl: `http://${context.hostname}:${port}` }];
189
- }
190
-
191
- return baseUrls;
192
- };
193
-
194
- const getBlockletInterfaces = ({ blocklet, context, nodeInfo, routingRules, nodeIp }) => {
195
- const interfaces = [];
196
- (blocklet.meta.interfaces || []).forEach((x) => {
197
- if (x.port && x.port.external) {
198
- // If an interface was exposed through external port, path is not appended
199
- const baseUrls = getBlockletBaseUrls({
200
- routingEnabled: false,
201
- port: x.port.external,
202
- rules: [],
203
- context,
204
- });
205
- baseUrls.forEach(({ baseUrl }) => interfaces.push({ type: x.type, name: x.name, url: baseUrl }));
206
- } else {
207
- // Otherwise, the url is composed with routing rules
208
- const port = x.port && x.port.internal ? blocklet.ports[x.port.internal] : blocklet.ports[x.port];
209
- const baseUrls = getBlockletBaseUrls({
210
- routingEnabled: isRoutingEnabled(nodeInfo.routing),
211
- port,
212
- rules: (routingRules || []).filter((r) => {
213
- return (
214
- // don't show wellknown interface
215
- r.to.type !== ROUTING_RULE_TYPES.GENERAL_PROXY &&
216
- // LEGACY don't show wellknown interface
217
- r.to.interfaceName !== BLOCKLET_INTERFACE_WELLKNOWN &&
218
- // don't show child blocklet interface
219
- (!r.from.groupPathPrefix || r.from.pathPrefix === r.from.groupPathPrefix)
220
- );
221
- }),
222
- context,
223
- blocklet,
224
- nodeIp,
225
- });
226
-
227
- baseUrls.forEach(({ baseUrl, ruleId, interfaceName, authConfig }) => {
228
- if (interfaceName && interfaceName !== x.name) {
229
- return;
230
- }
231
- interfaces.push({
232
- ruleId,
233
- type: x.type,
234
- name: x.name,
235
- url: getInterfaceUrl({ baseUrl, url: '/', version: blocklet.meta.version }),
236
- authConfig,
237
- });
238
- });
239
- }
240
- });
241
-
242
- return uniqBy(interfaces, 'url');
243
- };
244
-
245
- const isRoutingEnabled = (routing) => !!routing && !!routing.provider && routing.provider !== ROUTER_PROVIDER_NONE;
94
+ const isRoutingEnabled = (routing) => !!routing && !!routing.provider;
246
95
 
247
96
  const isInProgress = (status) =>
248
97
  [
@@ -283,7 +132,7 @@ const asyncExec = (command, options) =>
283
132
  });
284
133
  });
285
134
 
286
- const getProviderFromNodeInfo = (nodeInfo) => get(nodeInfo, 'routing.provider', null) || ROUTER_PROVIDER_NONE;
135
+ const getProviderFromNodeInfo = (info) => get(info, 'routing.provider', 'default');
287
136
 
288
137
  const isCLI = () => !process.env.ABT_NODE_SK;
289
138
 
@@ -315,7 +164,6 @@ const getHttpsCertInfo = (certificate) => {
315
164
  validFrom,
316
165
  validTo,
317
166
  issuer,
318
- serialNumber: info.serialNumber,
319
167
  sans: info.dnsNames,
320
168
  validityPeriod: info.validTo - info.validFrom,
321
169
  fingerprintAlg: 'SHA256',
@@ -332,6 +180,8 @@ const getDataDirs = (dataDir) => ({
332
180
  tmp: path.join(dataDir, 'tmp'),
333
181
  blocklets: path.join(dataDir, 'blocklets'),
334
182
  services: path.join(dataDir, 'services'),
183
+ modules: path.join(dataDir, 'modules'),
184
+ certManagerModule: path.join(dataDir, 'modules', 'certificate-manager'),
335
185
  });
336
186
 
337
187
  // Ensure data dir for Blocklet Server exists
@@ -356,39 +206,52 @@ const formatEnvironments = (environments) => Object.keys(environments).map((x) =
356
206
  const transformIPToDomain = (ip) => ip.split('.').join('-');
357
207
 
358
208
  const getBaseUrls = async (node, ips) => {
359
- const ipTypes = {
360
- internal: 'private',
361
- external: 'public',
362
- };
363
209
  const info = await node.getNodeInfo();
364
210
  const { https, httpPort, httpsPort } = info.routing;
365
- const ipKeys = Object.keys(ips).filter((x) => ips[x]);
366
211
  const getPort = (port, defaultPort) => (port && port !== defaultPort ? `:${port}` : '');
212
+ const availableIps = ips.filter(Boolean);
367
213
 
368
- if (info.routing.provider !== ROUTER_PROVIDER_NONE && info.routing.snapshotHash && info.routing.adminPath) {
369
- const sites = await node.getSitesFromSnapshot();
214
+ const getHttpInfo = async (domain) => {
215
+ const certificate = https ? await node.certManager.getNormalByDomain(domain) : null;
216
+ const protocol = certificate ? 'https' : 'http';
217
+ const port = certificate ? getPort(httpsPort, DEFAULT_HTTPS_PORT) : getPort(httpPort, DEFAULT_HTTP_PORT);
370
218
 
371
- const { dashboardDomain } = info.routing;
219
+ return { protocol, port };
220
+ };
221
+
222
+ if (info.routing.provider && info.routing.snapshotHash && info.routing.adminPath) {
223
+ const sites = await node.getSitesFromSnapshot();
224
+ const { ipWildcardDomain } = info.routing;
372
225
  const adminPath = normalizePathPrefix(info.routing.adminPath);
373
- const httpUrls = ipKeys.map((x) => {
374
- const port = getPort(httpPort, DEFAULT_HTTP_PORT);
226
+ const tmpHttpPort = getPort(httpPort, DEFAULT_HTTP_PORT);
227
+
228
+ const httpUrls = availableIps.map((ip) => {
375
229
  return {
376
- url: `http://${ips[x]}${port}${adminPath}`,
377
- type: ipTypes[x],
230
+ url: `http://${ip}${tmpHttpPort}${adminPath}`,
378
231
  };
379
232
  });
380
233
 
381
- if (dashboardDomain) {
382
- const site = sites.find((c) => (c.domainAliases || []).find((item) => item.value === dashboardDomain));
234
+ if (ipWildcardDomain) {
235
+ const site = sites.find((c) => (c.domainAliases || []).find((item) => item.value === ipWildcardDomain));
383
236
  if (site) {
384
- const certificate = https ? await node.findCertificateByDomain(dashboardDomain) : null;
385
- const protocol = certificate ? 'https' : 'http';
386
- const port = certificate ? getPort(httpsPort, DEFAULT_HTTPS_PORT) : getPort(httpPort, DEFAULT_HTTP_PORT);
387
- const httpsUrls = ipKeys.map((x) => ({
388
- url: `${protocol}://${transformIPToDomain(ips[x])}.${dashboardDomain.substring(2)}${port}${adminPath}`,
389
- type: ipTypes[x],
237
+ const httpInfo = await getHttpInfo(ipWildcardDomain);
238
+ const httpsUrls = availableIps.map((ip) => ({
239
+ url: `${httpInfo.protocol}://${transformIPToDomain(ip)}.${ipWildcardDomain.substring(2)}${
240
+ httpInfo.port
241
+ }${adminPath}`,
390
242
  }));
391
243
 
244
+ // add did domain access url
245
+ const didDomainAlias = site.domainAliases.find((item) => item.value.endsWith(info.didDomain));
246
+ if (didDomainAlias) {
247
+ const didDomain = didDomainAlias.value;
248
+ const didDomainHttpInfo = await getHttpInfo(didDomain);
249
+
250
+ httpsUrls.push({
251
+ url: `${didDomainHttpInfo.protocol}://${didDomain}${didDomainHttpInfo.port}${adminPath}`,
252
+ });
253
+ }
254
+
392
255
  return httpUrls.concat(httpsUrls);
393
256
  }
394
257
  }
@@ -397,73 +260,11 @@ const getBaseUrls = async (node, ips) => {
397
260
  }
398
261
 
399
262
  // port urls
400
- return ipKeys.map((x) => ({
401
- url: `http://${ips[x]}:${info.port}`,
402
- type: ipTypes[x],
263
+ return availableIps.map((ip) => ({
264
+ url: `http://${ip}:${info.port}`,
403
265
  }));
404
266
  };
405
267
 
406
- const getBlockletMetaByUrl = async (url) => {
407
- const { protocol, pathname } = new URL(url);
408
-
409
- if (protocol.startsWith('file')) {
410
- const decoded = decodeURIComponent(pathname);
411
- if (!fs.existsSync(decoded)) {
412
- throw new Error(`File does not exist: ${decoded}`);
413
- }
414
- const d = await fs.promises.readFile(decoded);
415
- const meta = JSON.parse(d);
416
- return meta;
417
- }
418
-
419
- if (protocol.startsWith('http')) {
420
- try {
421
- const { data: meta } = await request({ url, method: 'GET', timeout: 1000 * 20 });
422
- if (Object.prototype.toString.call(meta) !== '[object Object]') {
423
- throw new Error('Url is not valid');
424
- }
425
- return meta;
426
- } catch (err) {
427
- throw new Error(`Failed to get blocklet meta from ${url}: ${err.message}`);
428
- }
429
- }
430
-
431
- throw new Error(`Invalid url protocol: ${protocol.replace(/:$/, '')}`);
432
- };
433
-
434
- const validateUrl = async (url, expectedHttpResTypes = ['application/json', 'text/plain']) => {
435
- const parsed = new URL(url);
436
- const { protocol, pathname } = parsed;
437
-
438
- // file
439
- if (protocol.startsWith('file')) {
440
- const decoded = decodeURIComponent(pathname);
441
- if (!fs.existsSync(decoded)) {
442
- throw new Error(`File does not exist: ${decoded}`);
443
- }
444
- return true;
445
- }
446
-
447
- // http(s)
448
- if (protocol.startsWith('http')) {
449
- let res;
450
-
451
- try {
452
- res = await request({ url, method: 'HEAD', timeout: 1000 * 10 });
453
- } catch (err) {
454
- throw new Error(`Cannot get content-type from ${url}: ${err.message}`);
455
- }
456
-
457
- if (expectedHttpResTypes.some((x) => res.headers['content-type'].includes(x)) === false) {
458
- throw new Error(`Unexpected content-type from ${url}: ${res.headers['content-type']}`);
459
- }
460
-
461
- return true;
462
- }
463
-
464
- throw new Error(`Invalid url protocol: ${protocol.replace(/:$/, '')}`);
465
- };
466
-
467
268
  const expandBundle = (bundlePath, destDir) =>
468
269
  unzipper.Open.file(bundlePath).then((d) => d.extract({ path: destDir, concurrency: 20 }));
469
270
 
@@ -527,12 +328,151 @@ const getQueueConcurrencyByMem = () => {
527
328
 
528
329
  const getSafeEnv = (inputEnv, processEnv = process.env) => {
529
330
  const whiteList = ['ABT_NODE', 'ABT_NODE_DID', 'ABT_NODE_PK', 'ABT_NODE_PORT', 'ABT_NODE_SERVICE_PORT'];
331
+ const blackList = { ABT_NODE_SK: '' };
530
332
  // 此处需要保留 process.env 中的环境变量,只移除和 ABT_NODE 相关的环境变量(否则丢失了 process.env.SHELL 变量可能会造成无法使用 nodejs 的情况)
531
333
  const filterProcessEnv = pickBy(processEnv, (value, key) => !key.startsWith('ABT_NODE') || whiteList.includes(key));
532
- const mergedEnv = { ...filterProcessEnv, ...inputEnv };
334
+ const mergedEnv = { ...filterProcessEnv, ...inputEnv, ...blackList };
533
335
  return mergedEnv;
534
336
  };
535
337
 
338
+ // For performance sake, we only support 1 param here, and the cache is flushed every minute
339
+ const memoizeAsync = (fn, flushInterval = 60000) => {
340
+ const cache = new Map();
341
+ setInterval(() => cache.clear(), flushInterval);
342
+
343
+ return (arg) => {
344
+ if (cache.has(arg)) {
345
+ return cache.get(arg);
346
+ }
347
+
348
+ // eslint-disable-next-line prefer-spread
349
+ const result = fn.apply(null, [arg]);
350
+ cache.set(arg, result);
351
+
352
+ return result;
353
+ };
354
+ };
355
+
356
+ const getStateCrons = (states) => [
357
+ {
358
+ name: 'cleanup-audit-logs',
359
+ time: '0 0 6 * * *', // cleanup old logs every day at 6am
360
+ // time: '0 */5 * * * *', // cleanup every 5 minutes
361
+ options: { runOnInit: false },
362
+ fn: async () => {
363
+ const removed = await states.auditLog.remove({
364
+ createdAt: { $lt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 90) },
365
+ });
366
+ logger.info(`Removed ${removed} audit logs`);
367
+ },
368
+ },
369
+ ];
370
+
371
+ const getDelegateState = async (chainHost, address) => {
372
+ const result = await axios.post(
373
+ joinUrl(chainHost, '/gql/'),
374
+ JSON.stringify({
375
+ query: `{
376
+ getDelegateState(address: "${address}") {
377
+ state {
378
+ address
379
+ ops {
380
+ key
381
+ }
382
+ }
383
+ }
384
+ }`,
385
+ }),
386
+ {
387
+ headers: {
388
+ 'Content-Type': 'application/json',
389
+ Accept: 'application/json',
390
+ },
391
+ timeout: 60 * 1000,
392
+ }
393
+ );
394
+
395
+ return get(result.data, 'data.getDelegateState.state');
396
+ };
397
+
398
+ const getNFTState = async (chainHost, nftId) => {
399
+ const url = joinUrl(new URL(chainHost).origin, '/api/gql/');
400
+
401
+ const result = await axios.post(
402
+ url,
403
+ JSON.stringify({
404
+ query: `{
405
+ getAssetState(address: "${nftId}") {
406
+ state {
407
+ address
408
+ data {
409
+ typeUrl
410
+ value
411
+ }
412
+ display {
413
+ type
414
+ content
415
+ }
416
+ issuer
417
+ owner
418
+ parent
419
+ tags
420
+ }
421
+ }
422
+ }`,
423
+ }),
424
+ {
425
+ headers: {
426
+ 'Content-Type': 'application/json',
427
+ Accept: 'application/json',
428
+ },
429
+ timeout: 60 * 1000,
430
+ }
431
+ );
432
+
433
+ const state = get(result, 'data.data.getAssetState.state');
434
+ if (state && state.data.typeUrl === 'json') {
435
+ state.data.value = JSON.parse(state.data.value);
436
+ }
437
+
438
+ return state;
439
+ };
440
+
441
+ const getServerDidDomain = (nodeInfo) => `${encodeBase32(nodeInfo.did)}.${nodeInfo.didDomain}`;
442
+
443
+ const prettyURL = (url, isHttps = true) => {
444
+ if (typeof url !== 'string') {
445
+ return url;
446
+ }
447
+
448
+ if (url.toLowerCase().startsWith('http')) {
449
+ return url;
450
+ }
451
+
452
+ return isHttps ? `https://${url}` : `http://${url}`;
453
+ };
454
+
455
+ const templateReplace = (str, vars = {}) => {
456
+ if (typeof str === 'string') {
457
+ return str.replace(/{([.\w]+)}/g, (m, key) => get(vars, key));
458
+ }
459
+
460
+ return str;
461
+ };
462
+
463
+ const isGatewayCacheEnabled = (info) => {
464
+ if (info.mode === NODE_MODES.DEBUG) {
465
+ return false;
466
+ }
467
+
468
+ const cacheEnabled = get(info, 'routing.cacheEnabled');
469
+ if (typeof cacheEnabled === 'boolean') {
470
+ return cacheEnabled;
471
+ }
472
+
473
+ return true;
474
+ };
475
+
536
476
  const lib = {
537
477
  validateOwner,
538
478
  getProviderFromNodeInfo,
@@ -541,30 +481,17 @@ const lib = {
541
481
  formatEnvironments,
542
482
  isInProgress,
543
483
  isBeforeInstalled,
544
- getInterfaceUrl,
545
- getBlockletBaseUrls,
484
+ replaceDomainSlot,
546
485
  getBlockletHost,
547
- getBlockletInterfaces,
548
486
  isRoutingEnabled,
549
487
  checkDomainDNS,
550
488
  asyncExec,
551
- trimSlash,
552
489
  isCLI,
553
490
  getHttpsCertInfo,
554
491
  ensureDataDirs,
555
492
  getDataDirs,
556
493
  getBaseUrls,
557
- getBlockletMeta: (dir, opts = {}) =>
558
- parseBlockletMeta(dir, {
559
- serviceMetas: getServices(),
560
- ...opts,
561
- }),
562
- validateBlockletMeta: (meta, opts = {}) => {
563
- fixAndValidateService(meta, getServices(), opts.fix);
564
- return validateMeta(meta, opts);
565
- },
566
- getBlockletMetaByUrl,
567
- validateUrl,
494
+ getBlockletMeta: parseBlockletMeta,
568
495
  expandBundle,
569
496
  findInterfaceByName,
570
497
  findInterfacePortByName,
@@ -573,6 +500,14 @@ const lib = {
573
500
  transformIPToDomain,
574
501
  getQueueConcurrencyByMem,
575
502
  getSafeEnv,
503
+ memoizeAsync,
504
+ getStateCrons,
505
+ getDelegateState,
506
+ getNFTState,
507
+ getServerDidDomain,
508
+ prettyURL,
509
+ templateReplace,
510
+ isGatewayCacheEnabled,
576
511
  };
577
512
 
578
513
  module.exports = lib;
package/lib/util/ip.js CHANGED
@@ -1,6 +1,9 @@
1
1
  const getIp = require('@abtnode/util/lib/get-ip');
2
2
  const logger = require('@abtnode/logger')('@abtnode/core:ip');
3
3
 
4
+ const doRpc = require('./rpc');
5
+ const { memoizeAsync } = require('./index');
6
+
4
7
  let cache = null;
5
8
  const fetch = async () => {
6
9
  try {
@@ -22,6 +25,9 @@ const cron = {
22
25
  fn: fetch,
23
26
  };
24
27
 
28
+ const lookup = memoizeAsync((ip) => doRpc({ command: 'lookup', ip }));
29
+
25
30
  module.exports.fetch = fetch;
26
31
  module.exports.get = get;
27
32
  module.exports.cron = cron;
33
+ module.exports.lookup = lookup;