@abtnode/core 1.16.44-beta-20250529-223630-10e16ac8 → 1.16.44-beta-20250603-231026-30a9d27f

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.
@@ -26,6 +26,7 @@ const { sendToUser } = require('@blocklet/sdk/lib/util/send-notification');
26
26
  const { getDidDomainForBlocklet } = require('@abtnode/util/lib/get-domain-for-blocklet');
27
27
  const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager');
28
28
  const promiseSpawn = require('@abtnode/util/lib/promise-spawn');
29
+ const { sanitizeTag } = require('@abtnode/util/lib/sanitize');
29
30
 
30
31
  const {
31
32
  BLOCKLET_INSTALL_TYPE,
@@ -237,7 +238,6 @@ const ensureBlockletRunning = require('./ensure-blocklet-running');
237
238
  const { transformNotification } = require('../../util/notification');
238
239
  const { generateUserUpdateData } = require('../../util/user');
239
240
  const { blockletThemeSchema } = require('../../validators/theme');
240
- const checkDNS = require('../../util/check-dns.js');
241
241
  const { removeDockerNetwork } = require('../../util/docker/docker-network.js');
242
242
  const parseDockerName = require('../../util/docker/parse-docker-name.js');
243
243
 
@@ -341,6 +341,7 @@ class DiskBlockletManager extends BaseBlockletManager {
341
341
  nodeAPI,
342
342
  teamAPI,
343
343
  certManager,
344
+ routerManager,
344
345
  }) {
345
346
  super();
346
347
 
@@ -358,6 +359,7 @@ class DiskBlockletManager extends BaseBlockletManager {
358
359
  this.resendNotificationQueue = resendNotificationQueue;
359
360
  this.teamManager = teamManager;
360
361
  this.certManager = certManager;
362
+ this.routerManager = routerManager;
361
363
  /**
362
364
  * @type {import('../../api/node')}
363
365
  */
@@ -443,7 +445,7 @@ class DiskBlockletManager extends BaseBlockletManager {
443
445
  title,
444
446
  description,
445
447
  action: `/blocklets/${did}/overview`,
446
- blockletDashboardAction: '/.well-known/service/admin/blocklets',
448
+ blockletDashboardAction: `${WELLKNOWN_SERVICE_PATH_PREFIX}/admin/blocklets`,
447
449
  entityType: 'blocklet',
448
450
  entityId: did,
449
451
  severity,
@@ -1482,7 +1484,7 @@ class DiskBlockletManager extends BaseBlockletManager {
1482
1484
  entityId: newBlocklet.meta.did,
1483
1485
  severity: 'success',
1484
1486
  action: `/blocklets/${newBlocklet.meta.did}/components`,
1485
- blockletDashboardAction: '/.well-known/service/admin/components',
1487
+ blockletDashboardAction: `${WELLKNOWN_SERVICE_PATH_PREFIX}/admin/components`,
1486
1488
  });
1487
1489
 
1488
1490
  this.emit(BlockletEvents.componentRemoved, {
@@ -1664,6 +1666,12 @@ class DiskBlockletManager extends BaseBlockletManager {
1664
1666
  throw new Error('configs list is not an array');
1665
1667
  }
1666
1668
 
1669
+ // 对用户输入的配置进行 XSS 清理
1670
+ const sanitizedConfigs = newConfigs.map((c) => ({
1671
+ ...c,
1672
+ value: sanitizeTag(c.value),
1673
+ }));
1674
+
1667
1675
  const tmpDids = Array.isArray(did) ? did : [did];
1668
1676
  const [rootDid, childDid] = tmpDids;
1669
1677
  const rootMetaDid = await states.blocklet.getBlockletMetaDid(rootDid);
@@ -1682,7 +1690,7 @@ class DiskBlockletManager extends BaseBlockletManager {
1682
1690
 
1683
1691
  const configObj = {};
1684
1692
  const ignoredKeys = [];
1685
- for (const x of newConfigs) {
1693
+ for (const x of sanitizedConfigs) {
1686
1694
  if (['CHAIN_TYPE', 'BLOCKLET_APP_CHAIN_TYPE'].includes(x.key)) {
1687
1695
  throw new Error(`${x.key} should not be changed`);
1688
1696
  } else if (x.custom === true) {
@@ -1715,7 +1723,7 @@ class DiskBlockletManager extends BaseBlockletManager {
1715
1723
  configObj[x.key] = x.value;
1716
1724
  }
1717
1725
 
1718
- const finalConfigs = newConfigs.filter((x) => !ignoredKeys.includes(x.key));
1726
+ const finalConfigs = sanitizedConfigs.filter((x) => !ignoredKeys.includes(x.key));
1719
1727
  const willAppSkChange = isRotatingAppSk(finalConfigs, blocklet.configs, blocklet.externalSk);
1720
1728
 
1721
1729
  // NOTICE: cannot use appDid as did param because appDid will become old appDid in alsoKnownAs and cannot get blocklet by the old appDid
@@ -2348,12 +2356,18 @@ class DiskBlockletManager extends BaseBlockletManager {
2348
2356
 
2349
2357
  await Promise.all(
2350
2358
  customAliases.map(async (alias) => {
2351
- const dns = await checkDNS(alias.value, cnameDomain?.value);
2359
+ const dns = await this.routerManager.checkDomainDNS(alias.value, cnameDomain?.value);
2352
2360
  logger.info('dns info', { dns });
2353
2361
  if (!dns.isDnsResolved || !dns.isCnameMatch) {
2354
2362
  return;
2355
2363
  }
2356
2364
 
2365
+ const foundCert = await this.routerManager.getHttpsCert({ domain: alias.value });
2366
+ logger.info('cron check dns global certificate', { foundCert: !!foundCert, domain: alias.value });
2367
+ if (foundCert) {
2368
+ return;
2369
+ }
2370
+
2357
2371
  if (!alias.certificateId) {
2358
2372
  logger.info('cron check dns no certificate');
2359
2373
  await issueCert(alias);
@@ -3513,7 +3527,7 @@ class DiskBlockletManager extends BaseBlockletManager {
3513
3527
  };
3514
3528
 
3515
3529
  const action = `/blocklets/${did}/components?checkUpdate=1`;
3516
- const blockletDashboardAction = '/.well-known/service/admin/components?checkUpdate=1';
3530
+ const blockletDashboardAction = `${WELLKNOWN_SERVICE_PATH_PREFIX}/admin/components?checkUpdate=1`;
3517
3531
  const users = await this.teamManager.getOwnerAndAdminUsers(did, 1);
3518
3532
  const nodeInfo = await states.node.read();
3519
3533
  const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
@@ -4067,7 +4081,7 @@ class DiskBlockletManager extends BaseBlockletManager {
4067
4081
  deployedFrom || fromBlockletSource(source)
4068
4082
  })`,
4069
4083
  action: `/blocklets/${did}/overview`,
4070
- blockletDashboardAction: '/.well-known/service/admin/blocklets',
4084
+ blockletDashboardAction: `${WELLKNOWN_SERVICE_PATH_PREFIX}/admin/blocklets`,
4071
4085
  entityType: 'blocklet',
4072
4086
  entityId: did,
4073
4087
  severity: 'success',
@@ -4308,7 +4322,7 @@ class DiskBlockletManager extends BaseBlockletManager {
4308
4322
  componentDids
4309
4323
  )} is ${actionName} successfully for ${title}`,
4310
4324
  action: `/blocklets/${did}/overview`,
4311
- blockletDashboardAction: '/.well-known/service/admin/blocklets',
4325
+ blockletDashboardAction: `${WELLKNOWN_SERVICE_PATH_PREFIX}/admin/blocklets`,
4312
4326
  entityType: 'blocklet',
4313
4327
  entityId: did,
4314
4328
  severity: 'success',
@@ -5162,7 +5176,7 @@ class DiskBlockletManager extends BaseBlockletManager {
5162
5176
  async getDomainDNS({ teamDid, domain }) {
5163
5177
  const blocklet = await this.getBlocklet(teamDid);
5164
5178
  const cnameDomain = (get(blocklet, 'site.domainAliases') || []).find((item) => isDidDomain(item.value));
5165
- return checkDNS(domain, cnameDomain?.value);
5179
+ return this.routerManager.checkDomainDNS(domain, cnameDomain?.value);
5166
5180
  }
5167
5181
  }
5168
5182
 
@@ -287,6 +287,8 @@ const EVENTS = {
287
287
  RELOAD_GATEWAY: 'gateway.reload',
288
288
  NOTIFICATION_CREATE_QUEUED: 'notification.create.queued',
289
289
  UPDATE_DOMAIN_ALIAS: 'router.domain.alias.updated',
290
+
291
+ WEBHOOK_ATTEMPT: 'webhook.attempt',
290
292
  };
291
293
 
292
294
  const WHO_CAN_ACCESS = Object.freeze({
@@ -752,6 +754,7 @@ module.exports = Object.freeze({
752
754
  // FIXME: 梁柱, 下面两个 get 接口, 可以不开放但是前端需要根据情况取消获取这两个接口的调用
753
755
  getBlockletRuntimeHistory: true,
754
756
  checkDomains: true,
757
+ isDidDomain: true,
755
758
  },
756
759
  // 这些接口可以跳过 ACCESS VERIFY
757
760
  SKIP_ACCESS_VERIFY_METHODS: {
@@ -38885,7 +38888,7 @@ module.exports = require("zlib");
38885
38888
  /***/ ((module) => {
38886
38889
 
38887
38890
  "use strict";
38888
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.43","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib","test":"node tools/jest.js","coverage":"npm run test -- --coverage"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.16.43","@abtnode/auth":"1.16.43","@abtnode/certificate-manager":"1.16.43","@abtnode/client":"1.16.43","@abtnode/constant":"1.16.43","@abtnode/cron":"1.16.43","@abtnode/docker-utils":"1.16.43","@abtnode/logger":"1.16.43","@abtnode/models":"1.16.43","@abtnode/queue":"1.16.43","@abtnode/rbac":"1.16.43","@abtnode/router-provider":"1.16.43","@abtnode/static-server":"1.16.43","@abtnode/timemachine":"1.16.43","@abtnode/util":"1.16.43","@arcblock/did":"1.20.11","@arcblock/did-auth":"1.20.11","@arcblock/did-ext":"1.20.11","@arcblock/did-motif":"^1.1.13","@arcblock/did-util":"1.20.11","@arcblock/event-hub":"1.20.11","@arcblock/jwt":"1.20.11","@arcblock/pm2-events":"^0.0.5","@arcblock/validator":"1.20.11","@arcblock/vc":"1.20.11","@blocklet/constant":"1.16.43","@blocklet/did-space-js":"^1.0.56","@blocklet/env":"1.16.43","@blocklet/error":"^0.2.4","@blocklet/meta":"1.16.43","@blocklet/resolver":"1.16.43","@blocklet/sdk":"1.16.43","@blocklet/store":"1.16.43","@blocklet/theme":"^2.13.55","@fidm/x509":"^1.2.1","@ocap/mcrypto":"1.20.11","@ocap/util":"1.20.11","@ocap/wallet":"1.20.11","@slack/webhook":"^5.0.4","archiver":"^7.0.1","axios":"^1.7.9","axon":"^2.0.3","chalk":"^4.1.2","cross-spawn":"^7.0.3","dayjs":"^1.11.13","deep-diff":"^1.0.2","detect-port":"^1.5.1","envfile":"^7.1.0","escape-string-regexp":"^4.0.0","fast-glob":"^3.3.2","filesize":"^10.1.1","flat":"^5.0.2","fs-extra":"^11.2.0","get-port":"^5.1.1","hasha":"^5.2.2","is-base64":"^1.1.0","is-cidr":"4","is-ip":"3","is-url":"^1.2.4","joi":"17.12.2","joi-extension-semver":"^5.0.0","js-yaml":"^4.1.0","kill-port":"^2.0.1","lodash":"^4.17.21","lru-cache":"^11.0.2","node-stream-zip":"^1.15.0","p-all":"^3.0.0","p-limit":"^3.1.0","p-map":"^4.0.0","p-retry":"^4.6.2","p-wait-for":"^3.2.0","rate-limiter-flexible":"^5.0.5","read-last-lines":"^1.8.0","semver":"^7.6.3","sequelize":"^6.35.0","shelljs":"^0.8.5","slugify":"^1.6.6","ssri":"^8.0.1","stream-throttle":"^0.1.3","stream-to-promise":"^3.0.0","systeminformation":"^5.23.3","tail":"^2.2.4","tar":"^6.1.11","transliteration":"^2.3.5","ua-parser-js":"^1.0.2","ufo":"^1.5.3","uuid":"^9.0.1","valid-url":"^1.0.9","which":"^2.0.2","xbytes":"^1.8.0"},"devDependencies":{"expand-tilde":"^2.0.2","express":"^4.18.2","jest":"^29.7.0","unzipper":"^0.10.11"},"gitHead":"e5764f753181ed6a7c615cd4fc6682aacf0cb7cd"}');
38891
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.43","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib","test":"node tools/jest.js","coverage":"npm run test -- --coverage"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.16.43","@abtnode/auth":"1.16.43","@abtnode/certificate-manager":"1.16.43","@abtnode/client":"1.16.43","@abtnode/constant":"1.16.43","@abtnode/cron":"1.16.43","@abtnode/docker-utils":"1.16.43","@abtnode/logger":"1.16.43","@abtnode/models":"1.16.43","@abtnode/queue":"1.16.43","@abtnode/rbac":"1.16.43","@abtnode/router-provider":"1.16.43","@abtnode/static-server":"1.16.43","@abtnode/timemachine":"1.16.43","@abtnode/util":"1.16.43","@arcblock/did":"1.20.12","@arcblock/did-auth":"1.20.12","@arcblock/did-ext":"1.20.12","@arcblock/did-motif":"^1.1.13","@arcblock/did-util":"1.20.12","@arcblock/event-hub":"1.20.12","@arcblock/jwt":"1.20.12","@arcblock/pm2-events":"^0.0.5","@arcblock/validator":"1.20.12","@arcblock/vc":"1.20.12","@blocklet/constant":"1.16.43","@blocklet/did-space-js":"^1.0.57","@blocklet/env":"1.16.43","@blocklet/error":"^0.2.5","@blocklet/meta":"1.16.43","@blocklet/resolver":"1.16.43","@blocklet/sdk":"1.16.43","@blocklet/store":"1.16.43","@blocklet/theme":"^2.13.61","@fidm/x509":"^1.2.1","@ocap/mcrypto":"1.20.12","@ocap/util":"1.20.12","@ocap/wallet":"1.20.12","@slack/webhook":"^5.0.4","archiver":"^7.0.1","axios":"^1.7.9","axon":"^2.0.3","chalk":"^4.1.2","cross-spawn":"^7.0.3","dayjs":"^1.11.13","deep-diff":"^1.0.2","detect-port":"^1.5.1","envfile":"^7.1.0","escape-string-regexp":"^4.0.0","fast-glob":"^3.3.2","filesize":"^10.1.1","flat":"^5.0.2","fs-extra":"^11.2.0","get-port":"^5.1.1","hasha":"^5.2.2","is-base64":"^1.1.0","is-cidr":"4","is-ip":"3","is-url":"^1.2.4","joi":"17.12.2","joi-extension-semver":"^5.0.0","js-yaml":"^4.1.0","kill-port":"^2.0.1","lodash":"^4.17.21","lru-cache":"^11.0.2","node-stream-zip":"^1.15.0","p-all":"^3.0.0","p-limit":"^3.1.0","p-map":"^4.0.0","p-retry":"^4.6.2","p-wait-for":"^3.2.0","rate-limiter-flexible":"^5.0.5","read-last-lines":"^1.8.0","semver":"^7.6.3","sequelize":"^6.35.0","shelljs":"^0.8.5","slugify":"^1.6.6","ssri":"^8.0.1","stream-throttle":"^0.1.3","stream-to-promise":"^3.0.0","systeminformation":"^5.23.3","tail":"^2.2.4","tar":"^6.1.11","transliteration":"^2.3.5","ua-parser-js":"^1.0.2","ufo":"^1.5.3","uuid":"^9.0.1","valid-url":"^1.0.9","which":"^2.0.2","xbytes":"^1.8.0"},"devDependencies":{"expand-tilde":"^2.0.2","express":"^4.18.2","jest":"^29.7.0","unzipper":"^0.10.11"},"gitHead":"e5764f753181ed6a7c615cd4fc6682aacf0cb7cd"}');
38889
38892
 
38890
38893
  /***/ }),
38891
38894
 
@@ -2,7 +2,9 @@ const pick = require('lodash/pick');
2
2
  const pickBy = require('lodash/pickBy');
3
3
  const get = require('lodash/get');
4
4
  const { EventEmitter } = require('events');
5
- const { API_VERSION } = require('./util');
5
+ const { EVENTS } = require('@abtnode/constant');
6
+ const { API_VERSION, STATUS } = require('./util');
7
+ const endpointQueueInit = require('./queues');
6
8
 
7
9
  const DEFAULT_PAGE = 1;
8
10
  const DEFAULT_PAGE_SIZE = 10;
@@ -25,6 +27,9 @@ class WebhooksAPI extends EventEmitter {
25
27
 
26
28
  this.cache = new Map();
27
29
  this.TTL = 5 * 60 * 1000;
30
+
31
+ const { webhookEndpointQueue } = endpointQueueInit.init({ states, teamManager });
32
+ this.webhookEndpointQueue = webhookEndpointQueue;
28
33
  }
29
34
 
30
35
  async getUserInfo(userDid, teamDid, serverDid) {
@@ -161,6 +166,46 @@ class WebhooksAPI extends EventEmitter {
161
166
  const list = await webhookAttemptState.list(page, pageSize, where);
162
167
  return list;
163
168
  }
169
+
170
+ async retryWebhookAttempt({ eventId, webhookId, attemptId, teamDid }, context) {
171
+ const { webhookAttemptState } = await this.teamManager.getWebhookState(teamDid);
172
+
173
+ const result = await webhookAttemptState.insert({
174
+ eventId,
175
+ webhookId,
176
+ status: STATUS.PENDING,
177
+ retryCount: 1,
178
+ responseStatus: 200,
179
+ responseBody: {},
180
+ triggeredBy: getUserDid(context),
181
+ triggeredFrom: attemptId,
182
+ });
183
+
184
+ const emitResult = async () => {
185
+ return {
186
+ teamDid,
187
+ ...(await webhookAttemptState.findOne({ eventId, webhookId, id: result.id })),
188
+ };
189
+ };
190
+
191
+ this.webhookEndpointQueue.push({
192
+ eventId,
193
+ webhookId,
194
+ appDid: teamDid,
195
+ attemptId: result.id,
196
+ triggeredBy: getUserDid(context),
197
+ triggeredFrom: attemptId,
198
+ });
199
+
200
+ this.webhookEndpointQueue.on('finished', async () => {
201
+ this.emit(EVENTS.WEBHOOK_ATTEMPT, { ...(await emitResult()) });
202
+ });
203
+ this.webhookEndpointQueue.on('failed', async () => {
204
+ this.emit(EVENTS.WEBHOOK_ATTEMPT, { ...(await emitResult()) });
205
+ });
206
+
207
+ return result;
208
+ }
164
209
  }
165
210
 
166
211
  module.exports = WebhooksAPI;
@@ -3,13 +3,19 @@ const axios = require('@abtnode/util/lib/axios');
3
3
  const { Op } = require('sequelize');
4
4
 
5
5
  const createQueue = require('../../util/queue');
6
- const { getWebhookJobId } = require('./util');
6
+ const { getWebhookJobId, STATUS } = require('./util');
7
7
 
8
8
  const MAX_RETRY_COUNT = 2; // 重试一次
9
9
  const AXIOS_TIMEOUT = 1000 * 60 * 3;
10
10
 
11
11
  const createWebhookEndpointQueue = ({ states, teamManager }) => {
12
12
  // https://stripe.com/docs/webhooks
13
+ /**
14
+ *
15
+ * @param {*} job eventId: event.id, webhookId: webhook.id, appDid: job.appDid, attemptId?:string
16
+ *
17
+ * @returns
18
+ */
13
19
  const handleWebhookEndpoint = async (job) => {
14
20
  logger.info('handle webhook endpoint', job);
15
21
 
@@ -48,14 +54,31 @@ const createWebhookEndpointQueue = ({ states, teamManager }) => {
48
54
  // verify similar to component call, but supports external urls
49
55
  const response = await axios.post(webhook.url, event.request, { timeout: AXIOS_TIMEOUT });
50
56
 
51
- await webhookAttemptState.insert({
52
- eventId: event.id,
53
- webhookId: webhook.id,
54
- status: 'succeeded',
55
- responseStatus: response.status,
56
- responseBody: response.data || {},
57
- retryCount,
58
- });
57
+ if (job.attemptId) {
58
+ await webhookAttemptState.update(
59
+ { id: job.attemptId },
60
+ {
61
+ status: STATUS.SUCCEEDED,
62
+ responseStatus: response.status,
63
+ responseBody: response.data || {},
64
+ retryCount,
65
+ triggeredBy: job.triggeredBy,
66
+ triggeredFrom: job.triggeredFrom,
67
+ }
68
+ );
69
+ } else {
70
+ await webhookAttemptState.insert({
71
+ eventId: event.id,
72
+ webhookId: webhook.id,
73
+ status: STATUS.SUCCEEDED,
74
+ responseStatus: response.status,
75
+ responseBody: response.data || {},
76
+ retryCount,
77
+ triggeredBy: job.triggeredBy,
78
+ triggeredFrom: job.triggeredFrom,
79
+ });
80
+ }
81
+
59
82
  logger.info('WebhookAttempt created successfully', { eventId: event.id, webhookId: webhook.id });
60
83
 
61
84
  const newPendingCount = Math.max(0, event.pendingWebhooks - 1);
@@ -66,14 +89,31 @@ const createWebhookEndpointQueue = ({ states, teamManager }) => {
66
89
  } catch (err) {
67
90
  logger.warn('webhook attempt error', { ...job, retryCount, message: err.message });
68
91
 
69
- await webhookAttemptState.insert({
70
- eventId: event.id,
71
- webhookId: webhook.id,
72
- status: 'failed',
73
- responseStatus: err.response?.status || 500,
74
- responseBody: err.response?.data || {},
75
- retryCount,
76
- });
92
+ if (job.attemptId) {
93
+ await webhookAttemptState.update(
94
+ { id: job.attemptId },
95
+ {
96
+ status: STATUS.FAILED,
97
+ responseStatus: err.response?.status || 500,
98
+ responseBody: err.response?.data || {},
99
+ retryCount,
100
+ triggeredBy: job.triggeredBy,
101
+ triggeredFrom: job.triggeredFrom,
102
+ }
103
+ );
104
+ } else {
105
+ await webhookAttemptState.insert({
106
+ eventId: event.id,
107
+ webhookId: webhook.id,
108
+ status: STATUS.FAILED,
109
+ responseStatus: err.response?.status || 500,
110
+ responseBody: err.response?.data || {},
111
+ retryCount,
112
+ triggeredBy: job.triggeredBy,
113
+ triggeredFrom: job.triggeredFrom,
114
+ });
115
+ }
116
+
77
117
  logger.info('Failed WebhookAttempt created', { eventId: event.id, webhookId: webhook.id });
78
118
 
79
119
  // reschedule next attempt
@@ -6,7 +6,14 @@ const getWebhookJobId = (eventId, webhookId) => {
6
6
  return md5([eventId, webhookId].join('-'));
7
7
  };
8
8
 
9
+ const STATUS = {
10
+ SUCCEEDED: 'succeeded',
11
+ FAILED: 'failed',
12
+ PENDING: 'pending',
13
+ };
14
+
9
15
  module.exports = {
10
16
  API_VERSION,
17
+ STATUS,
11
18
  getWebhookJobId,
12
19
  };
@@ -19,6 +19,7 @@ const {
19
19
  DEFAULT_DID_DOMAIN,
20
20
  WELLKNOWN_BLOCKLET_ADMIN_PATH,
21
21
  TEST_STORE_URL,
22
+ WELLKNOWN_SERVICE_PATH_PREFIX,
22
23
  } = require('@abtnode/constant');
23
24
  const { joinURL } = require('ufo');
24
25
  const { encode } = require('@abtnode/util/lib/base32');
@@ -65,6 +66,7 @@ module.exports = ({
65
66
  node,
66
67
  nodeRuntimeMonitor,
67
68
  daemon,
69
+ webhookManager,
68
70
  }) => {
69
71
  const nodeState = states.node;
70
72
  const nodeMonitSender = new NodeMonitSender({ node });
@@ -561,6 +563,10 @@ module.exports = ({
561
563
  eventHub.broadcast(name, data);
562
564
  });
563
565
 
566
+ listen(webhookManager, EVENTS.WEBHOOK_ATTEMPT, (name, data) => {
567
+ onEvent(name, data, true);
568
+ });
569
+
564
570
  listen(nodeState, BlockletEvents.purchaseChange, onEvent);
565
571
  nodeState.on(EVENTS.ROUTING_UPDATED, (nodeInfo) => onEvent(EVENTS.ROUTING_UPDATED, { routing: nodeInfo.routing }));
566
572
  nodeState.once(EVENTS.NODE_ADDED_OWNER, () => downloadAddedBlocklet());
@@ -706,7 +712,7 @@ module.exports = ({
706
712
  actions: [
707
713
  {
708
714
  name: translation.viewYourAccount,
709
- link: joinURL(origin, '/.well-known/service/user/settings'),
715
+ link: joinURL(origin, `${WELLKNOWN_SERVICE_PATH_PREFIX}/user/settings`),
710
716
  },
711
717
  ],
712
718
  };
package/lib/index.js CHANGED
@@ -280,6 +280,7 @@ function ABTNode(options) {
280
280
  nodeAPI,
281
281
  teamAPI,
282
282
  certManager,
283
+ routerManager,
283
284
  });
284
285
  blockletManager.setMaxListeners(0);
285
286
  const securityAPI = new SecurityAPI({ teamManager, blockletManager });
@@ -752,6 +753,7 @@ function ABTNode(options) {
752
753
  getWebhookEndpoints: webhookAPI.getWebhookEndpoints.bind(webhookAPI),
753
754
  getWebhookEndpoint: webhookAPI.getWebhookEndpoint.bind(webhookAPI),
754
755
  getWebhookAttempts: webhookAPI.getWebhookAttempts.bind(webhookAPI),
756
+ retryWebhookAttempt: webhookAPI.retryWebhookAttempt.bind(webhookAPI),
755
757
 
756
758
  // passport
757
759
  createPassportLog: passportAPI.createPassportLog.bind(passportAPI),
@@ -798,6 +800,7 @@ function ABTNode(options) {
798
800
  nodeRuntimeMonitor: nodeAPI.runtimeMonitor,
799
801
  daemon: options.daemon,
800
802
  handleBlockletWafChange,
803
+ webhookManager: webhookAPI,
801
804
  });
802
805
 
803
806
  const webhook = WebHook({ events, dataDirs, instance, teamManager });
@@ -4,6 +4,7 @@
4
4
  * Router manager is the business logic layer of router related, includes:
5
5
  * RoutingRuleState, HttpsCertState
6
6
  */
7
+ const https = require('https');
7
8
  const path = require('path');
8
9
  const os = require('os');
9
10
  const dns = require('dns');
@@ -22,7 +23,7 @@ const { getProvider } = require('@abtnode/router-provider');
22
23
  const checkDomainMatch = require('@abtnode/util/lib/check-domain-match');
23
24
  const { isDidDomain, isCustomDomain } = require('@abtnode/util/lib/url-evaluation');
24
25
  const { isTopLevelDomain } = require('@abtnode/util/lib/domain');
25
- const { EVENTS } = require('@abtnode/constant');
26
+ const { EVENTS, WELLKNOWN_PING_PREFIX } = require('@abtnode/constant');
26
27
  const {
27
28
  DOMAIN_FOR_IP_SITE,
28
29
  DOMAIN_FOR_DEFAULT_SITE,
@@ -342,12 +343,41 @@ class RouterManager extends EventEmitter {
342
343
 
343
344
  if (issueCert) {
344
345
  const didDomain = doc.domainAliases.find((x) => isDidDomain(x.value));
345
- const dnsValue = await checkDNS(domain, didDomain?.value);
346
- const shouldSkipCertIssue = isCustomDomain(domain) && (!dnsValue.isDnsResolved || !dnsValue.isCnameMatch);
346
+ const [dnsValue, cert] = await Promise.all([
347
+ this.checkDomainDNS(domain, didDomain?.value),
348
+ this.getHttpsCert({ domain }),
349
+ ]);
350
+
351
+ // 自定义域名如果 DNS 未解析成功 或 CNAME 不匹配 或 已配置证书,则不自动颁发证书
352
+ const shouldSkipCertIssue =
353
+ isCustomDomain(domain) && (!dnsValue.isDnsResolved || !dnsValue.isCnameMatch || !!cert);
354
+
355
+ if (shouldSkipCertIssue) {
356
+ const reasonFn = () => {
357
+ if (cert) {
358
+ return 'cert already exists';
359
+ }
360
+
361
+ if (!dnsValue.isDnsResolved) {
362
+ return 'DNS not resolved';
363
+ }
347
364
 
348
- if (!shouldSkipCertIssue) {
365
+ if (!dnsValue.isCnameMatch) {
366
+ return 'CNAME not match';
367
+ }
368
+
369
+ return 'unknown reason';
370
+ };
371
+
372
+ logger.info('skip cert issue for domain alias', {
373
+ cert,
374
+ domain,
375
+ dnsValue,
376
+ reason: reasonFn(),
377
+ });
378
+ } else {
349
379
  this.certManager
350
- .issue({ domain, did, siteId: id, inBlockletSetup }, { delay: 5000 })
380
+ .issue({ domain, did, siteId: id, inBlockletSetup }, { delay: 3000 })
351
381
  .then(() => {
352
382
  logger.info('issue cert for domain alias', { domain, did });
353
383
  })
@@ -635,6 +665,63 @@ class RouterManager extends EventEmitter {
635
665
  return null;
636
666
  }
637
667
 
668
+ checkDomainDNS(domain, cnameDomain) {
669
+ try {
670
+ if (isCustomDomain(domain)) {
671
+ return checkDNS(domain, cnameDomain);
672
+ }
673
+
674
+ return Promise.resolve({
675
+ isDnsResolved: true,
676
+ hasCname: true,
677
+ cnameRecords: [cnameDomain],
678
+ isCnameMatch: true,
679
+ });
680
+ } catch (error) {
681
+ logger.error('check domain dns error', error?.message);
682
+ return Promise.resolve({
683
+ isDnsResolved: false,
684
+ hasCname: false,
685
+ cnameRecords: [],
686
+ isCnameMatch: false,
687
+ });
688
+ }
689
+ }
690
+
691
+ async getHttpsCert({ domain }) {
692
+ const matchedCert = await this.getMatchedCert(domain);
693
+
694
+ if (matchedCert) {
695
+ return matchedCert;
696
+ }
697
+
698
+ return new Promise((resolve) => {
699
+ const req = https.request(
700
+ { host: domain, path: WELLKNOWN_PING_PREFIX, method: 'GET', timeout: 1000 * 10 },
701
+ (res) => {
702
+ try {
703
+ const data = res.socket.getPeerCertificate();
704
+ const cert = {
705
+ issuer: {
706
+ countryName: data.issuer.C,
707
+ organizationName: data.issuer.O,
708
+ commonName: data.issuer.CN,
709
+ },
710
+ validFrom: data.valid_from,
711
+ validTo: data.valid_to,
712
+ };
713
+ resolve(cert);
714
+ } catch {
715
+ resolve(null);
716
+ }
717
+ }
718
+ );
719
+
720
+ req.on('error', () => resolve(null));
721
+ req.end();
722
+ });
723
+ }
724
+
638
725
  isCertMatchedDomain(cert, domain = '') {
639
726
  return [cert.domain, ...(cert.sans || [])].some((d) => checkDomainMatch(d, domain));
640
727
  }
@@ -1,3 +1,4 @@
1
+ const { WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
1
2
  const { createLogWatcher } = require('./watcher');
2
3
 
3
4
  // Function to parse the log entry and extract relevant information
@@ -36,7 +37,7 @@ function parseLogEntry(line, check = true) {
36
37
  logEntry.status <= 599 &&
37
38
  logEntry.request.includes('/.well-known/did.json') === false &&
38
39
  logEntry.request.includes('/websocket') === false &&
39
- logEntry.request.includes('/.well-known/service/health') === false
40
+ logEntry.request.includes(`${WELLKNOWN_SERVICE_PATH_PREFIX}/health`) === false
40
41
  ) {
41
42
  console.warn(`5xx request detected: ${logEntry.host}`, line);
42
43
  return logEntry;
@@ -515,6 +515,8 @@ const getLogContent = async (action, args, context, result, info, node) => {
515
515
  return `Delete Blocklet Webhook(${result.id})`;
516
516
  case 'ensureBlockletRunning':
517
517
  return `${result.title}:\n* ${result.description}`;
518
+ case 'retryWebhookAttempt':
519
+ return `Retry Webhook Attempt(${args.attemptId})`;
518
520
 
519
521
  case 'addUploadEndpoint':
520
522
  return `Create Upload Endpoint(${args.url})`;
@@ -657,6 +659,7 @@ const getLogCategory = (action) => {
657
659
  case 'createWebhookEndpoint':
658
660
  case 'updateWebhookEndpoint':
659
661
  case 'deleteWebhookEndpoint':
662
+ case 'retryWebhookAttempt':
660
663
  return 'integrations';
661
664
 
662
665
  // server
@@ -1,11 +1,9 @@
1
- const https = require('https');
2
1
  const { EventEmitter } = require('events');
3
2
  const logger = require('@abtnode/logger')('@abtnode/domain-status');
4
- const { EVENTS, WELLKNOWN_PING_PREFIX } = require('@abtnode/constant');
3
+ const { EVENTS } = require('@abtnode/constant');
5
4
  const { BlockletEvents } = require('@blocklet/constant');
6
- const { isCustomDomain, isDidDomain } = require('@abtnode/util/lib/url-evaluation');
5
+ const { isDidDomain } = require('@abtnode/util/lib/url-evaluation');
7
6
  const { checkDomainDNS } = require('./index');
8
- const checkDNS = require('./check-dns');
9
7
 
10
8
  const dnsStatusStore = Object.create(null);
11
9
 
@@ -48,71 +46,6 @@ class DomainStatus extends EventEmitter {
48
46
  this.states = states;
49
47
  }
50
48
 
51
- async getHttpsCert(domain) {
52
- const matchedCert = await this.routerManager.getMatchedCert(domain);
53
-
54
- if (matchedCert) {
55
- return matchedCert;
56
- }
57
-
58
- return new Promise((resolve) => {
59
- const req = https.request(
60
- {
61
- host: domain,
62
- path: WELLKNOWN_PING_PREFIX,
63
- method: 'GET',
64
- timeout: 1000 * 10,
65
- },
66
- (res) => {
67
- try {
68
- const data = res.socket.getPeerCertificate();
69
- const cert = {
70
- issuer: {
71
- countryName: data.issuer.C,
72
- organizationName: data.issuer.O,
73
- commonName: data.issuer.CN,
74
- },
75
- validFrom: data.valid_from,
76
- validTo: data.valid_to,
77
- };
78
- resolve(cert);
79
- } catch {
80
- resolve(null);
81
- }
82
- }
83
- );
84
-
85
- req.on('error', () => {
86
- resolve(null);
87
- });
88
-
89
- req.end();
90
- });
91
- }
92
-
93
- checkDomainDNS(domain, cnameDomain) {
94
- try {
95
- if (isCustomDomain(domain)) {
96
- return checkDNS(domain, cnameDomain);
97
- }
98
-
99
- return Promise.resolve({
100
- isDnsResolved: true,
101
- hasCname: true,
102
- cnameRecords: [cnameDomain],
103
- isCnameMatch: true,
104
- });
105
- } catch (error) {
106
- logger.error('check domain dns error', error?.message);
107
- return Promise.resolve({
108
- isDnsResolved: false,
109
- hasCname: false,
110
- cnameRecords: [],
111
- isCnameMatch: false,
112
- });
113
- }
114
- }
115
-
116
49
  async checkDomainsStatus({ domains, did } = {}) {
117
50
  if (did) {
118
51
  // eslint-disable-next-line no-param-reassign
@@ -122,8 +55,12 @@ class DomainStatus extends EventEmitter {
122
55
  const cnameDomain = domains.find((x) => isDidDomain(x));
123
56
 
124
57
  (domains || []).forEach((domain) => {
125
- Promise.all([this.getHttpsCert(domain), checkDomainDnsWrapper(domain), this.checkDomainDNS(domain, cnameDomain)])
126
- .then(([matchedCert, dns, dnsResolve]) => {
58
+ Promise.all([
59
+ this.routerManager.getHttpsCert({ domain }),
60
+ this.routerManager.checkDomainDNS(domain, cnameDomain),
61
+ checkDomainDnsWrapper(domain),
62
+ ])
63
+ .then(([matchedCert, dnsResolve, dns]) => {
127
64
  const eventData = {
128
65
  domain,
129
66
  matchedCert,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.44-beta-20250529-223630-10e16ac8",
6
+ "version": "1.16.44-beta-20250603-231026-30a9d27f",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,44 +19,44 @@
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.44-beta-20250529-223630-10e16ac8",
23
- "@abtnode/auth": "1.16.44-beta-20250529-223630-10e16ac8",
24
- "@abtnode/certificate-manager": "1.16.44-beta-20250529-223630-10e16ac8",
25
- "@abtnode/client": "1.16.44-beta-20250529-223630-10e16ac8",
26
- "@abtnode/constant": "1.16.44-beta-20250529-223630-10e16ac8",
27
- "@abtnode/cron": "1.16.44-beta-20250529-223630-10e16ac8",
28
- "@abtnode/docker-utils": "1.16.44-beta-20250529-223630-10e16ac8",
29
- "@abtnode/logger": "1.16.44-beta-20250529-223630-10e16ac8",
30
- "@abtnode/models": "1.16.44-beta-20250529-223630-10e16ac8",
31
- "@abtnode/queue": "1.16.44-beta-20250529-223630-10e16ac8",
32
- "@abtnode/rbac": "1.16.44-beta-20250529-223630-10e16ac8",
33
- "@abtnode/router-provider": "1.16.44-beta-20250529-223630-10e16ac8",
34
- "@abtnode/static-server": "1.16.44-beta-20250529-223630-10e16ac8",
35
- "@abtnode/timemachine": "1.16.44-beta-20250529-223630-10e16ac8",
36
- "@abtnode/util": "1.16.44-beta-20250529-223630-10e16ac8",
37
- "@arcblock/did": "1.20.11",
38
- "@arcblock/did-auth": "1.20.11",
39
- "@arcblock/did-ext": "1.20.11",
22
+ "@abtnode/analytics": "1.16.44-beta-20250603-231026-30a9d27f",
23
+ "@abtnode/auth": "1.16.44-beta-20250603-231026-30a9d27f",
24
+ "@abtnode/certificate-manager": "1.16.44-beta-20250603-231026-30a9d27f",
25
+ "@abtnode/client": "1.16.44-beta-20250603-231026-30a9d27f",
26
+ "@abtnode/constant": "1.16.44-beta-20250603-231026-30a9d27f",
27
+ "@abtnode/cron": "1.16.44-beta-20250603-231026-30a9d27f",
28
+ "@abtnode/docker-utils": "1.16.44-beta-20250603-231026-30a9d27f",
29
+ "@abtnode/logger": "1.16.44-beta-20250603-231026-30a9d27f",
30
+ "@abtnode/models": "1.16.44-beta-20250603-231026-30a9d27f",
31
+ "@abtnode/queue": "1.16.44-beta-20250603-231026-30a9d27f",
32
+ "@abtnode/rbac": "1.16.44-beta-20250603-231026-30a9d27f",
33
+ "@abtnode/router-provider": "1.16.44-beta-20250603-231026-30a9d27f",
34
+ "@abtnode/static-server": "1.16.44-beta-20250603-231026-30a9d27f",
35
+ "@abtnode/timemachine": "1.16.44-beta-20250603-231026-30a9d27f",
36
+ "@abtnode/util": "1.16.44-beta-20250603-231026-30a9d27f",
37
+ "@arcblock/did": "1.20.12",
38
+ "@arcblock/did-auth": "1.20.12",
39
+ "@arcblock/did-ext": "1.20.12",
40
40
  "@arcblock/did-motif": "^1.1.13",
41
- "@arcblock/did-util": "1.20.11",
42
- "@arcblock/event-hub": "1.20.11",
43
- "@arcblock/jwt": "1.20.11",
41
+ "@arcblock/did-util": "1.20.12",
42
+ "@arcblock/event-hub": "1.20.12",
43
+ "@arcblock/jwt": "1.20.12",
44
44
  "@arcblock/pm2-events": "^0.0.5",
45
- "@arcblock/validator": "1.20.11",
46
- "@arcblock/vc": "1.20.11",
47
- "@blocklet/constant": "1.16.44-beta-20250529-223630-10e16ac8",
48
- "@blocklet/did-space-js": "^1.0.56",
49
- "@blocklet/env": "1.16.44-beta-20250529-223630-10e16ac8",
50
- "@blocklet/error": "^0.2.4",
51
- "@blocklet/meta": "1.16.44-beta-20250529-223630-10e16ac8",
52
- "@blocklet/resolver": "1.16.44-beta-20250529-223630-10e16ac8",
53
- "@blocklet/sdk": "1.16.44-beta-20250529-223630-10e16ac8",
54
- "@blocklet/store": "1.16.44-beta-20250529-223630-10e16ac8",
55
- "@blocklet/theme": "^2.13.55",
45
+ "@arcblock/validator": "1.20.12",
46
+ "@arcblock/vc": "1.20.12",
47
+ "@blocklet/constant": "1.16.44-beta-20250603-231026-30a9d27f",
48
+ "@blocklet/did-space-js": "^1.0.57",
49
+ "@blocklet/env": "1.16.44-beta-20250603-231026-30a9d27f",
50
+ "@blocklet/error": "^0.2.5",
51
+ "@blocklet/meta": "1.16.44-beta-20250603-231026-30a9d27f",
52
+ "@blocklet/resolver": "1.16.44-beta-20250603-231026-30a9d27f",
53
+ "@blocklet/sdk": "1.16.44-beta-20250603-231026-30a9d27f",
54
+ "@blocklet/store": "1.16.44-beta-20250603-231026-30a9d27f",
55
+ "@blocklet/theme": "^2.13.61",
56
56
  "@fidm/x509": "^1.2.1",
57
- "@ocap/mcrypto": "1.20.11",
58
- "@ocap/util": "1.20.11",
59
- "@ocap/wallet": "1.20.11",
57
+ "@ocap/mcrypto": "1.20.12",
58
+ "@ocap/util": "1.20.12",
59
+ "@ocap/wallet": "1.20.12",
60
60
  "@slack/webhook": "^5.0.4",
61
61
  "archiver": "^7.0.1",
62
62
  "axios": "^1.7.9",
@@ -116,5 +116,5 @@
116
116
  "jest": "^29.7.0",
117
117
  "unzipper": "^0.10.11"
118
118
  },
119
- "gitHead": "381ba5459e32dd7bc94f7ea62df65b72644d6d16"
119
+ "gitHead": "c76d07121652516f178de4188d8694fc130767b8"
120
120
  }