@abtnode/core 1.17.3-beta-20251117-230305-4637416e → 1.17.3-beta-20251119-034511-f26047c0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -669,6 +669,7 @@ function ABTNode(options) {
669
669
  getNotificationComponents: teamAPI.getNotificationComponents.bind(teamAPI),
670
670
  resendNotification: blockletManager.resendNotification.bind(blockletManager),
671
671
  getReceivers: teamAPI.getReceivers.bind(teamAPI),
672
+ getNotificationStats: teamAPI.getNotificationStats.bind(teamAPI),
672
673
 
673
674
  // AuditLog
674
675
  createAuditLog: async (params) => {
@@ -4,6 +4,7 @@ const { Sequelize, Op } = require('sequelize');
4
4
  const { isValid } = require('@arcblock/did');
5
5
  const { Joi } = require('@arcblock/validator');
6
6
  const { ROLES, SERVER_ROLES, NOTIFICATION_SEND_CHANNEL, NOTIFICATION_SEND_STATUS } = require('@abtnode/constant');
7
+ const dayjs = require('@abtnode/util/lib/dayjs');
7
8
  const BaseState = require('./base');
8
9
  const { getReceiversStatistics } = require('../util/notification');
9
10
 
@@ -954,6 +955,52 @@ class NotificationState extends BaseState {
954
955
  });
955
956
  return Number(countResult.total);
956
957
  }
958
+
959
+ async getNotificationsBySince({ since = '1h' }) {
960
+ // 解析 since 参数,格式为 "数字h",例如 "1h", "24h"
961
+ const sinceMatch = since.match(/^(\d+)h$/);
962
+ if (!sinceMatch) {
963
+ throw new Error('Invalid since format. Expected format: "1h", "2h", "24h", etc.');
964
+ }
965
+
966
+ const hours = parseInt(sinceMatch[1], 10);
967
+
968
+ // 验证范围:最小 1h,最大 24h
969
+ if (hours < 1 || hours > 24) {
970
+ throw new Error('The since parameter must be between 1h and 24h.');
971
+ }
972
+
973
+ // 计算时间范围
974
+ const startTime = dayjs().subtract(hours, 'hours').toDate();
975
+
976
+ // 第一步:在 notifications 表中查询符合时间范围的通知 ID
977
+ const notifications = await this.model.findAll({
978
+ where: {
979
+ createdAt: {
980
+ [Op.gte]: startTime,
981
+ },
982
+ },
983
+ attributes: ['id'],
984
+ });
985
+
986
+ // 如果没有找到符合条件的通知,直接返回空数组
987
+ if (notifications.length === 0) {
988
+ return [];
989
+ }
990
+
991
+ // 提取通知 ID 列表
992
+ const notificationIds = notifications.map((n) => n.id);
993
+
994
+ // 第二步:根据通知 ID 在 notification_receivers 表中查询数据
995
+ return this.notificationReceivers.model.findAll({
996
+ where: {
997
+ notificationId: {
998
+ [Op.in]: notificationIds,
999
+ },
1000
+ },
1001
+ order: [['createdAt', 'DESC']],
1002
+ });
1003
+ }
957
1004
  }
958
1005
 
959
1006
  module.exports = NotificationState;
@@ -2,9 +2,9 @@
2
2
  /* eslint-disable no-await-in-loop */
3
3
 
4
4
  const fs = require('fs-extra');
5
- const path = require('path');
5
+ const path = require('node:path');
6
6
  const shelljs = require('shelljs');
7
- const os = require('os');
7
+ const os = require('node:os');
8
8
  const tar = require('tar');
9
9
  const get = require('lodash/get');
10
10
  const isNil = require('lodash/isNil');
@@ -338,7 +338,7 @@ const fillBlockletConfigs = (blocklet, configs) => {
338
338
  }, {});
339
339
  };
340
340
 
341
- const ensureBlockletExpanded = async (meta, appDir) => {
341
+ const ensureBlockletExpanded = async (_meta, appDir) => {
342
342
  const bundlePath = path.join(appDir, BLOCKLET_BUNDLE_FILE);
343
343
  if (fs.existsSync(bundlePath)) {
344
344
  try {
@@ -773,7 +773,6 @@ const startBlockletProcess = async (
773
773
 
774
774
  // start process
775
775
  const maxMemoryRestart = get(nodeInfo, 'runtimeConfig.blockletMaxMemoryLimit', BLOCKLET_MAX_MEM_LIMIT_IN_MB);
776
-
777
776
  const processIdName = isGreen ? `${processId}-green` : processId;
778
777
  /**
779
778
  * @type {pm2.StartOptions}
@@ -800,8 +799,8 @@ const startBlockletProcess = async (
800
799
  BLOCKLET_START_AT: now,
801
800
  NODE_OPTIONS: await getSecurityNodeOptions(b, nodeInfo.enableFileSystemIsolation),
802
801
  },
803
- // should not inject appSk and appPsk to the blocklet environment
804
- process.env.WITH_SK ? [] : ['BLOCKLET_APP_SK', 'BLOCKLET_APP_PSK']
802
+ // should only inject appSk and appPsk to the blocklet environment when unsafe mode enabled
803
+ ['1', 1].includes(env.UNSAFE_MODE) ? [] : ['BLOCKLET_APP_SK', 'BLOCKLET_APP_PSK']
805
804
  ),
806
805
  script,
807
806
  args,
@@ -1158,11 +1157,11 @@ const checkBlockletProcessHealthy = async (
1158
1157
  blocklet,
1159
1158
  { minConsecutiveTime, timeout, componentDids, setBlockletRunning, isGreen = false, appDid } = {}
1160
1159
  ) => {
1161
- if (process.env.NODE_ENV === 'test' && process.env.ABT_NODE_TEST_MIN_CONSECUTIVE_TIME) {
1162
- // need bigger than minConsecutiveTime in test env
1163
- // eslint-disable-next-line no-param-reassign
1164
- timeout = Math.max(+process.env.ABT_NODE_TEST_MIN_CONSECUTIVE_TIME * 10, minConsecutiveTime + 1000);
1165
- }
1160
+ // if (process.env.NODE_ENV === 'test' && process.env.ABT_NODE_TEST_MIN_CONSECUTIVE_TIME) {
1161
+ // // need bigger than minConsecutiveTime in test env
1162
+ // // eslint-disable-next-line no-param-reassign
1163
+ // timeout = Math.max(+process.env.ABT_NODE_TEST_MIN_CONSECUTIVE_TIME * 10, minConsecutiveTime + 3000);
1164
+ // }
1166
1165
  await forEachBlocklet(
1167
1166
  blocklet,
1168
1167
  async (b) => {
@@ -1457,7 +1456,7 @@ const getRuntimeInfo = async (processId) => {
1457
1456
  return {
1458
1457
  ...dockerInfo,
1459
1458
  pid: proc.pid,
1460
- uptime: proc.pm2_env ? +new Date() - Number(proc.pm2_env.pm_uptime) : 0,
1459
+ uptime: proc.pm2_env ? Date.now() - Number(proc.pm2_env.pm_uptime) : 0,
1461
1460
  port: proc.pm2_env ? proc.pm2_env.BLOCKLET_PORT : null,
1462
1461
  status: proc.pm2_env ? proc.pm2_env.status : null,
1463
1462
  runningDocker: !!dockerName,
@@ -1465,7 +1464,7 @@ const getRuntimeInfo = async (processId) => {
1465
1464
  }
1466
1465
  return {
1467
1466
  pid: proc.pid,
1468
- uptime: proc.pm2_env ? +new Date() - Number(proc.pm2_env.pm_uptime) : 0,
1467
+ uptime: proc.pm2_env ? Date.now() - Number(proc.pm2_env.pm_uptime) : 0,
1469
1468
  memoryUsage: proc.monit.memory,
1470
1469
  cpuUsage: proc.monit.cpu,
1471
1470
  status: proc.pm2_env ? proc.pm2_env.status : null,
@@ -2385,7 +2384,7 @@ const shouldSkipComponent = (componentDid, whiteList) => {
2385
2384
  return !arr.includes(componentDid);
2386
2385
  };
2387
2386
 
2388
- const ensurePortsShape = (states, portsA, portsB) => {
2387
+ const ensurePortsShape = (_states, portsA, portsB) => {
2389
2388
  if (!portsA || Object.keys(portsA).length === 0) {
2390
2389
  return;
2391
2390
  }
@@ -2,7 +2,11 @@ const { joinURL } = require('ufo');
2
2
  const isUrl = require('is-url');
3
3
  const omit = require('lodash/omit');
4
4
  const groupBy = require('lodash/groupBy');
5
- const { NOTIFICATION_SEND_STATUS } = require('@abtnode/constant');
5
+ const {
6
+ NOTIFICATION_SEND_STATUS,
7
+ NOTIFICATION_SEND_CHANNEL,
8
+ NOTIFICATION_SEND_FAILED_REASON,
9
+ } = require('@abtnode/constant');
6
10
 
7
11
  const REMOVE_FIELDS = [
8
12
  'description',
@@ -131,9 +135,189 @@ function getReceiversStatistics(receivers) {
131
135
  };
132
136
  }
133
137
 
138
+ /**
139
+ * 检测消息推送渠道是否开启
140
+ */
141
+ function checkPushChannelAvailable(blocklet = {}, isServer = false) {
142
+ const config = blocklet.settings?.notification || {};
143
+
144
+ const pushKitEnabled = config.pushKit?.enabled && config.pushKit?.endpoint;
145
+ const emailEnabled = config.email?.enabled;
146
+
147
+ return {
148
+ [NOTIFICATION_SEND_CHANNEL.WALLET]: {
149
+ enabled: true,
150
+ },
151
+ ...(isServer
152
+ ? {}
153
+ : {
154
+ [NOTIFICATION_SEND_CHANNEL.PUSH]: {
155
+ enabled: !!pushKitEnabled,
156
+ },
157
+ [NOTIFICATION_SEND_CHANNEL.EMAIL]: {
158
+ enabled: emailEnabled || false,
159
+ },
160
+ }),
161
+ [NOTIFICATION_SEND_CHANNEL.WEBHOOK]: {
162
+ enabled: true,
163
+ },
164
+ };
165
+ }
166
+
167
+ function getColumnField(channel, suffix) {
168
+ switch (channel) {
169
+ case NOTIFICATION_SEND_CHANNEL.WALLET:
170
+ return `wallet${suffix}`;
171
+ case NOTIFICATION_SEND_CHANNEL.PUSH:
172
+ return `pushKit${suffix}`;
173
+ case NOTIFICATION_SEND_CHANNEL.EMAIL:
174
+ return `email${suffix}`;
175
+ default:
176
+ return '';
177
+ }
178
+ }
179
+
180
+ const SEND_STATUS_MAP = {
181
+ [NOTIFICATION_SEND_STATUS.PENDING]: 'pending',
182
+ [NOTIFICATION_SEND_STATUS.SENT]: 'success',
183
+ [NOTIFICATION_SEND_STATUS.FAILED]: 'failed',
184
+ };
185
+
186
+ function getFailedReasonMessage(reason, channel) {
187
+ if (!reason || !channel) {
188
+ return reason;
189
+ }
190
+ switch (reason) {
191
+ case NOTIFICATION_SEND_FAILED_REASON.USER_DISABLED:
192
+ return `The user has disabled the notification for the "${channel}" channel.`;
193
+ case NOTIFICATION_SEND_FAILED_REASON.CHANNEL_UNAVAILABLE:
194
+ return `The "${channel}" channel is not available.`;
195
+ case NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED:
196
+ return `The "${channel}" channel was not selected for this notification.`;
197
+ case NOTIFICATION_SEND_FAILED_REASON.NOT_ONLINE:
198
+ return 'The user is not online.';
199
+ default:
200
+ return reason;
201
+ }
202
+ }
203
+
204
+ function getStatisticsState(results, channel = NOTIFICATION_SEND_CHANNEL.WALLET) {
205
+ const last = results[0];
206
+
207
+ const getIgnoredAndFailedCount = (data) => {
208
+ const notSuccessCount = data.filter(
209
+ (item) => item[getColumnField(channel, 'SendStatus')] !== NOTIFICATION_SEND_STATUS.SENT
210
+ ).length;
211
+ const ignoredCount = data.filter(
212
+ (item) =>
213
+ item[getColumnField(channel, 'SendStatus')] !== NOTIFICATION_SEND_STATUS.SENT &&
214
+ (item[getColumnField(channel, 'SendStatus')] === NOTIFICATION_SEND_STATUS.PENDING ||
215
+ [
216
+ ...Object.values(NOTIFICATION_SEND_FAILED_REASON),
217
+ 'Email Service is not available.',
218
+ 'Push Kit Service is not Enabled.',
219
+ ].includes(item[getColumnField(channel, 'SendFailedReason')]))
220
+ ).length;
221
+
222
+ return {
223
+ ignored: ignoredCount,
224
+ failed: notSuccessCount - ignoredCount,
225
+ };
226
+ };
227
+
228
+ return {
229
+ last: {
230
+ sendAt: last[getColumnField(channel, 'SendAt')],
231
+ sendStatus: SEND_STATUS_MAP[last[getColumnField(channel, 'SendStatus')]],
232
+ reason: getFailedReasonMessage(last[getColumnField(channel, 'SendFailedReason')], channel),
233
+ },
234
+ state: {
235
+ total: results.length,
236
+ success: results.filter((item) => item[getColumnField(channel, 'SendStatus')] === NOTIFICATION_SEND_STATUS.SENT)
237
+ .length,
238
+ ...getIgnoredAndFailedCount(results),
239
+ },
240
+ };
241
+ }
242
+
243
+ function getWebhookStatisticsState(results) {
244
+ // 收集所有 URL 的最新记录
245
+ const latestRecords = [];
246
+
247
+ const state = results.reduce(
248
+ (acc, item) => {
249
+ const webhook = item.webhook ?? {};
250
+ Object.values(webhook).forEach((records) => {
251
+ // 对每个 URL 的记录数组按 sendAt 倒序排列,取最新的
252
+ const latestRecord = records.sort((a, b) => new Date(b.sendAt) - new Date(a.sendAt))[0];
253
+ if (latestRecord) {
254
+ latestRecords.push(latestRecord);
255
+ acc.total++;
256
+ if (latestRecord.status === NOTIFICATION_SEND_STATUS.SENT) {
257
+ acc.success++;
258
+ } else if (latestRecord.status === NOTIFICATION_SEND_STATUS.FAILED) {
259
+ acc.failed++;
260
+ } else if (latestRecord.status === NOTIFICATION_SEND_STATUS.PENDING) {
261
+ acc.ignored++;
262
+ }
263
+ }
264
+ });
265
+ return acc;
266
+ },
267
+ {
268
+ total: 0,
269
+ success: 0,
270
+ failed: 0,
271
+ ignored: 0,
272
+ }
273
+ );
274
+
275
+ // 从所有 URL 的最新记录中,找到最新的那个
276
+ const lastWebhook = latestRecords.sort((a, b) => new Date(b.sendAt) - new Date(a.sendAt))[0];
277
+
278
+ return {
279
+ last: lastWebhook
280
+ ? {
281
+ sendAt: lastWebhook.sendAt,
282
+ type: lastWebhook.type,
283
+ sendStatus: SEND_STATUS_MAP[lastWebhook.status],
284
+ reason: lastWebhook.failedReason,
285
+ }
286
+ : null,
287
+ state,
288
+ };
289
+ }
290
+
291
+ function getNotificationPushState(results, channelsAvailable, isServer = false) {
292
+ return {
293
+ wallet: {
294
+ ...channelsAvailable[NOTIFICATION_SEND_CHANNEL.WALLET],
295
+ ...getStatisticsState(results, NOTIFICATION_SEND_CHANNEL.WALLET),
296
+ },
297
+ ...(isServer
298
+ ? {}
299
+ : {
300
+ pushKit: {
301
+ ...channelsAvailable[NOTIFICATION_SEND_CHANNEL.PUSH],
302
+ ...getStatisticsState(results, NOTIFICATION_SEND_CHANNEL.PUSH),
303
+ },
304
+ email: {
305
+ ...channelsAvailable[NOTIFICATION_SEND_CHANNEL.EMAIL],
306
+ ...getStatisticsState(results, NOTIFICATION_SEND_CHANNEL.EMAIL),
307
+ },
308
+ }),
309
+ webhook: {
310
+ ...channelsAvailable[NOTIFICATION_SEND_CHANNEL.WEBHOOK],
311
+ ...getWebhookStatisticsState(results),
312
+ },
313
+ };
314
+ }
315
+
134
316
  module.exports = {
135
317
  transformNotification,
136
318
  getStatusCounts,
137
319
  getWebhookStatusCounts,
138
320
  getReceiversStatistics,
321
+ checkPushChannelAvailable,
322
+ getNotificationPushState,
139
323
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.17.3-beta-20251117-230305-4637416e",
6
+ "version": "1.17.3-beta-20251119-034511-f26047c0",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -17,21 +17,21 @@
17
17
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
18
18
  "license": "Apache-2.0",
19
19
  "dependencies": {
20
- "@abtnode/analytics": "1.17.3-beta-20251117-230305-4637416e",
21
- "@abtnode/auth": "1.17.3-beta-20251117-230305-4637416e",
22
- "@abtnode/certificate-manager": "1.17.3-beta-20251117-230305-4637416e",
23
- "@abtnode/constant": "1.17.3-beta-20251117-230305-4637416e",
24
- "@abtnode/cron": "1.17.3-beta-20251117-230305-4637416e",
25
- "@abtnode/db-cache": "1.17.3-beta-20251117-230305-4637416e",
26
- "@abtnode/docker-utils": "1.17.3-beta-20251117-230305-4637416e",
27
- "@abtnode/logger": "1.17.3-beta-20251117-230305-4637416e",
28
- "@abtnode/models": "1.17.3-beta-20251117-230305-4637416e",
29
- "@abtnode/queue": "1.17.3-beta-20251117-230305-4637416e",
30
- "@abtnode/rbac": "1.17.3-beta-20251117-230305-4637416e",
31
- "@abtnode/router-provider": "1.17.3-beta-20251117-230305-4637416e",
32
- "@abtnode/static-server": "1.17.3-beta-20251117-230305-4637416e",
33
- "@abtnode/timemachine": "1.17.3-beta-20251117-230305-4637416e",
34
- "@abtnode/util": "1.17.3-beta-20251117-230305-4637416e",
20
+ "@abtnode/analytics": "1.17.3-beta-20251119-034511-f26047c0",
21
+ "@abtnode/auth": "1.17.3-beta-20251119-034511-f26047c0",
22
+ "@abtnode/certificate-manager": "1.17.3-beta-20251119-034511-f26047c0",
23
+ "@abtnode/constant": "1.17.3-beta-20251119-034511-f26047c0",
24
+ "@abtnode/cron": "1.17.3-beta-20251119-034511-f26047c0",
25
+ "@abtnode/db-cache": "1.17.3-beta-20251119-034511-f26047c0",
26
+ "@abtnode/docker-utils": "1.17.3-beta-20251119-034511-f26047c0",
27
+ "@abtnode/logger": "1.17.3-beta-20251119-034511-f26047c0",
28
+ "@abtnode/models": "1.17.3-beta-20251119-034511-f26047c0",
29
+ "@abtnode/queue": "1.17.3-beta-20251119-034511-f26047c0",
30
+ "@abtnode/rbac": "1.17.3-beta-20251119-034511-f26047c0",
31
+ "@abtnode/router-provider": "1.17.3-beta-20251119-034511-f26047c0",
32
+ "@abtnode/static-server": "1.17.3-beta-20251119-034511-f26047c0",
33
+ "@abtnode/timemachine": "1.17.3-beta-20251119-034511-f26047c0",
34
+ "@abtnode/util": "1.17.3-beta-20251119-034511-f26047c0",
35
35
  "@aigne/aigne-hub": "^0.10.9",
36
36
  "@arcblock/did": "^1.27.7",
37
37
  "@arcblock/did-connect-js": "^1.27.7",
@@ -43,15 +43,15 @@
43
43
  "@arcblock/pm2-events": "^0.0.5",
44
44
  "@arcblock/validator": "^1.27.7",
45
45
  "@arcblock/vc": "^1.27.7",
46
- "@blocklet/constant": "1.17.3-beta-20251117-230305-4637416e",
46
+ "@blocklet/constant": "1.17.3-beta-20251119-034511-f26047c0",
47
47
  "@blocklet/did-space-js": "^1.2.4",
48
- "@blocklet/env": "1.17.3-beta-20251117-230305-4637416e",
48
+ "@blocklet/env": "1.17.3-beta-20251119-034511-f26047c0",
49
49
  "@blocklet/error": "^0.3.3",
50
- "@blocklet/meta": "1.17.3-beta-20251117-230305-4637416e",
51
- "@blocklet/resolver": "1.17.3-beta-20251117-230305-4637416e",
52
- "@blocklet/sdk": "1.17.3-beta-20251117-230305-4637416e",
53
- "@blocklet/server-js": "1.17.3-beta-20251117-230305-4637416e",
54
- "@blocklet/store": "1.17.3-beta-20251117-230305-4637416e",
50
+ "@blocklet/meta": "1.17.3-beta-20251119-034511-f26047c0",
51
+ "@blocklet/resolver": "1.17.3-beta-20251119-034511-f26047c0",
52
+ "@blocklet/sdk": "1.17.3-beta-20251119-034511-f26047c0",
53
+ "@blocklet/server-js": "1.17.3-beta-20251119-034511-f26047c0",
54
+ "@blocklet/store": "1.17.3-beta-20251119-034511-f26047c0",
55
55
  "@blocklet/theme": "^3.2.6",
56
56
  "@fidm/x509": "^1.2.1",
57
57
  "@ocap/mcrypto": "^1.27.7",
@@ -116,5 +116,5 @@
116
116
  "express": "^4.18.2",
117
117
  "unzipper": "^0.10.11"
118
118
  },
119
- "gitHead": "91bbcd8cfe4db52306f6ce83b90295c296350a7f"
119
+ "gitHead": "7ab331f3b29e171a1e02aca80e73f35b6a161b86"
120
120
  }