@abtnode/core 1.16.47-beta-20250805-140707-3a4df7fd → 1.16.47-beta-20250808-102837-d10f3b40
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/blocklet/manager/disk.js +25 -28
- package/lib/blocklet/manager/ensure-blocklet-running.js +88 -95
- package/lib/blocklet/migration-dist/migration.cjs +1 -1
- package/lib/monitor/node-runtime-monitor.js +2 -1
- package/lib/states/base.js +33 -0
- package/lib/states/user.js +4 -0
- package/lib/states/webhook-endpoint.js +7 -2
- package/lib/states/webhook.js +4 -0
- package/lib/team/manager.js +2 -2
- package/lib/util/aigne-verify.js +1 -1
- package/lib/util/blocklet.js +10 -11
- package/lib/util/docker/docker-exec-chown.js +7 -3
- package/lib/util/docker/docker-exec.js +20 -7
- package/lib/util/docker/parse-docker-options-from-pm2.js +1 -7
- package/lib/util/ensure-bun.js +1 -1
- package/lib/util/env.js +2 -0
- package/lib/util/reset-node.js +2 -1
- package/lib/webhook/index.js +10 -1
- package/lib/webhook/sender/api/index.js +4 -0
- package/package.json +39 -38
|
@@ -435,33 +435,31 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
435
435
|
await this.ensureAutoBackupJobs();
|
|
436
436
|
await this.ensureAutoCheckUpdateJobs();
|
|
437
437
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
restart
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
stop
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
});
|
|
464
|
-
}
|
|
438
|
+
ensureBlockletRunning.initialize({
|
|
439
|
+
restart: async (params) => {
|
|
440
|
+
await this.restart(params);
|
|
441
|
+
},
|
|
442
|
+
stop: async (params) => {
|
|
443
|
+
await this.stop(params);
|
|
444
|
+
},
|
|
445
|
+
createAuditLog: (params) => this.createAuditLog(params),
|
|
446
|
+
notification: (did, title, description, severity) => {
|
|
447
|
+
try {
|
|
448
|
+
this._createNotification(did, {
|
|
449
|
+
title,
|
|
450
|
+
description,
|
|
451
|
+
action: `/blocklets/${did}/overview`,
|
|
452
|
+
blockletDashboardAction: `${WELLKNOWN_SERVICE_PATH_PREFIX}/admin/blocklets`,
|
|
453
|
+
entityType: 'blocklet',
|
|
454
|
+
entityId: did,
|
|
455
|
+
severity,
|
|
456
|
+
});
|
|
457
|
+
} catch (err) {
|
|
458
|
+
logger.error('create notification failed', { err });
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
checkSystemHighLoad: (...args) => this.nodeAPI.runtimeMonitor.checkSystemHighLoad(...args),
|
|
462
|
+
});
|
|
465
463
|
}
|
|
466
464
|
|
|
467
465
|
async ensureJobs({ queue, getJobId, find, entity, action, interval, restoreCancelled }) {
|
|
@@ -922,7 +920,6 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
922
920
|
nodeInfo,
|
|
923
921
|
env,
|
|
924
922
|
...hookArgs,
|
|
925
|
-
timeout: 20000,
|
|
926
923
|
});
|
|
927
924
|
}
|
|
928
925
|
return hooks[hookName](b, {
|
|
@@ -12,16 +12,10 @@ const inProgressStatuses = [BlockletStatus.stopping, BlockletStatus.restarting,
|
|
|
12
12
|
class EnsureBlockletRunning {
|
|
13
13
|
initialized = false;
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
whenCycleCheck = false;
|
|
16
16
|
|
|
17
17
|
// 每次任务的最小间隔时间
|
|
18
|
-
checkInterval = +process.env.ABT_NODE_ENSURE_RUNNING_CHECK_INTERVAL ||
|
|
19
|
-
|
|
20
|
-
// 首次任务的延迟时间
|
|
21
|
-
deferredTime = +process.env.ABT_NODE_ENSURE_RUNNING_DEFERRED_TIME || 30 * 1000;
|
|
22
|
-
|
|
23
|
-
// 每个重启任务(did + componentDids)的重启间隔为 2 min,如果 2 min 内重启过,就不会再重启
|
|
24
|
-
restartInterval = 1000 * 60 * 2;
|
|
18
|
+
checkInterval = +process.env.ABT_NODE_ENSURE_RUNNING_CHECK_INTERVAL || 180 * 1000;
|
|
25
19
|
|
|
26
20
|
everyBlockletCheckInterval = 2000;
|
|
27
21
|
|
|
@@ -41,7 +35,7 @@ class EnsureBlockletRunning {
|
|
|
41
35
|
|
|
42
36
|
fakeRunningBlocklets = {};
|
|
43
37
|
|
|
44
|
-
|
|
38
|
+
needRestartBlocklets = {};
|
|
45
39
|
|
|
46
40
|
restartingBlocklets = {};
|
|
47
41
|
|
|
@@ -52,23 +46,36 @@ class EnsureBlockletRunning {
|
|
|
52
46
|
// Ease to mock
|
|
53
47
|
isBlockletPortHealthy = isBlockletPortHealthy;
|
|
54
48
|
|
|
55
|
-
isBlockletPortHealthyWithRetries = async (blocklet,
|
|
49
|
+
isBlockletPortHealthyWithRetries = async (blocklet, fastCheck = false) => {
|
|
56
50
|
let error;
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
if (!this.whenCycleCheck) {
|
|
52
|
+
try {
|
|
53
|
+
// eslint-disable-next-line no-await-in-loop
|
|
54
|
+
await this.isBlockletPortHealthy(blocklet, {
|
|
55
|
+
minConsecutiveTime: 200,
|
|
56
|
+
timeout: 1000,
|
|
57
|
+
});
|
|
58
|
+
return true;
|
|
59
|
+
} catch (e) {
|
|
60
|
+
logger.error('blocklet port is not healthy', e);
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
59
64
|
|
|
60
|
-
for (let attempt = 0; attempt <
|
|
65
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
61
66
|
try {
|
|
62
67
|
// eslint-disable-next-line no-await-in-loop
|
|
63
68
|
await this.isBlockletPortHealthy(blocklet, {
|
|
64
69
|
minConsecutiveTime: 3000,
|
|
65
|
-
timeout,
|
|
70
|
+
timeout: 6000,
|
|
66
71
|
});
|
|
67
72
|
return true;
|
|
68
73
|
} catch (e) {
|
|
69
74
|
error = e;
|
|
70
75
|
// eslint-disable-next-line no-await-in-loop
|
|
71
|
-
await sleep(
|
|
76
|
+
await sleep(
|
|
77
|
+
fastCheck && this.whenCycleCheck ? this.everyBlockletDoingInterval : this.everyBlockletCheckInterval
|
|
78
|
+
);
|
|
72
79
|
}
|
|
73
80
|
}
|
|
74
81
|
logger.error('blocklet port is not healthy', error);
|
|
@@ -91,7 +98,9 @@ class EnsureBlockletRunning {
|
|
|
91
98
|
this.checkSystemHighLoad = checkSystemHighLoad;
|
|
92
99
|
logger.info('check and fix blocklet status interval', this.checkInterval);
|
|
93
100
|
const task = async () => {
|
|
94
|
-
|
|
101
|
+
if (this.whenCycleCheck) {
|
|
102
|
+
await sleep(this.checkInterval);
|
|
103
|
+
}
|
|
95
104
|
try {
|
|
96
105
|
await this.checkAndFix();
|
|
97
106
|
} catch (e) {
|
|
@@ -116,91 +125,72 @@ class EnsureBlockletRunning {
|
|
|
116
125
|
|
|
117
126
|
checkAndFix = async () => {
|
|
118
127
|
logger.info('check and fix blocklet status');
|
|
119
|
-
if (process.env.ABT_NODE_RESTART_RUNNING_COMPONENT === '1') {
|
|
120
|
-
logger.info('skip check and fix blocklet status because ABT_NODE_RESTART_RUNNING_COMPONENT is 1');
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
128
|
const systemHighLoad = this.checkSystemHighLoad({
|
|
125
129
|
maxCpus: this.highLoadCpu,
|
|
126
130
|
maxMem: this.highLoadMemory,
|
|
127
131
|
maxDisk: this.highLoadDisk,
|
|
128
132
|
});
|
|
129
133
|
|
|
130
|
-
if (systemHighLoad.isHighLoad) {
|
|
134
|
+
if (this.whenCycleCheck && systemHighLoad.isHighLoad) {
|
|
131
135
|
logger.warn('Skip once ensure blocklet running because system high load', systemHighLoad);
|
|
132
136
|
return;
|
|
133
137
|
}
|
|
138
|
+
|
|
134
139
|
this.runningBlocklets = {};
|
|
135
140
|
this.fakeRunningBlocklets = {};
|
|
136
|
-
this.
|
|
141
|
+
this.needRestartBlocklets = {};
|
|
137
142
|
|
|
143
|
+
const startTime = Date.now();
|
|
138
144
|
try {
|
|
139
145
|
await this.getRunningBlocklets();
|
|
140
146
|
await this.getFakeRunningBlocklets();
|
|
141
|
-
if (
|
|
147
|
+
if (!this.whenCycleCheck) {
|
|
142
148
|
await this.setFakeRunningToWaiting();
|
|
143
149
|
}
|
|
144
150
|
await this.restartFakeRunningBlocklets();
|
|
145
151
|
} catch (e) {
|
|
146
152
|
logger.error('ensure blocklet status failed', e);
|
|
147
153
|
}
|
|
154
|
+
logger.info(
|
|
155
|
+
`ensure blocklet status finished in ${Date.now() - startTime}ms. It's server first start: ${!this.whenCycleCheck}`
|
|
156
|
+
);
|
|
148
157
|
this.runningRootBlocklets = {};
|
|
149
|
-
this.
|
|
158
|
+
this.whenCycleCheck = true;
|
|
150
159
|
};
|
|
151
160
|
|
|
152
161
|
getRunningBlocklets = async () => {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
for (const realBlocklet of blocklets) {
|
|
157
|
-
const { did } = realBlocklet.meta;
|
|
158
|
-
if (realBlocklet.children) {
|
|
159
|
-
for (const childBlocklet of realBlocklet.children) {
|
|
160
|
-
if (childBlocklet.status === BlockletStatus.running || childBlocklet.status === BlockletStatus.waiting) {
|
|
161
|
-
if (!this.runningBlocklets[did]) {
|
|
162
|
-
this.runningBlocklets[did] = [];
|
|
163
|
-
}
|
|
164
|
-
if (this.runningBlocklets[did].find((b) => b.meta.did === childBlocklet.meta.did)) {
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
this.runningBlocklets[did].push(childBlocklet);
|
|
168
|
-
this.runningRootBlocklets[did] = realBlocklet;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const blockletDoings = await this.states.blocklet.getBlocklets({
|
|
175
|
-
status: {
|
|
176
|
-
$in: inProgressStatuses,
|
|
177
|
-
},
|
|
178
|
-
});
|
|
162
|
+
const runningStatuses = this.whenCycleCheck
|
|
163
|
+
? [BlockletStatus.running, BlockletStatus.waiting, BlockletStatus.starting]
|
|
164
|
+
: [BlockletStatus.running, BlockletStatus.waiting, BlockletStatus.starting, BlockletStatus.error];
|
|
179
165
|
|
|
180
|
-
|
|
166
|
+
const blocklets = await this.states.blocklet.getBlocklets();
|
|
167
|
+
for (const rootBlocklet of blocklets) {
|
|
181
168
|
const { did } = rootBlocklet.meta;
|
|
182
169
|
if (rootBlocklet.children) {
|
|
183
170
|
for (const childBlocklet of rootBlocklet.children) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
171
|
+
const isRunning = runningStatuses.includes(childBlocklet.status);
|
|
172
|
+
const isInProgress = inProgressStatuses.includes(childBlocklet.status);
|
|
173
|
+
if (isRunning || isInProgress) {
|
|
188
174
|
if (!this.runningBlocklets[did]) {
|
|
189
175
|
this.runningBlocklets[did] = [];
|
|
190
176
|
}
|
|
177
|
+
if (this.runningBlocklets[did].find((b) => b.meta.did === childBlocklet.meta.did)) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
191
180
|
this.runningBlocklets[did].push(childBlocklet);
|
|
181
|
+
this.runningRootBlocklets[did] = rootBlocklet;
|
|
192
182
|
}
|
|
193
183
|
}
|
|
194
184
|
}
|
|
195
185
|
}
|
|
186
|
+
|
|
196
187
|
logger.info('get running blocklets', Object.keys(this.runningBlocklets).length);
|
|
197
188
|
};
|
|
198
189
|
|
|
199
190
|
getFakeRunningBlocklets = async () => {
|
|
200
|
-
|
|
201
|
-
const blockletDids = Object.keys(this.runningBlocklets);
|
|
191
|
+
const rootDids = Object.keys(this.runningBlocklets);
|
|
202
192
|
await pAll(
|
|
203
|
-
|
|
193
|
+
rootDids.map((did) => {
|
|
204
194
|
return async () => {
|
|
205
195
|
const blocklets = this.runningBlocklets[did];
|
|
206
196
|
// eslint-disable-next-line
|
|
@@ -241,7 +231,7 @@ class EnsureBlockletRunning {
|
|
|
241
231
|
);
|
|
242
232
|
};
|
|
243
233
|
}),
|
|
244
|
-
{ concurrency:
|
|
234
|
+
{ concurrency: 8 }
|
|
245
235
|
);
|
|
246
236
|
|
|
247
237
|
logger.info('get fake running blocklets', Object.keys(this.fakeRunningBlocklets).length);
|
|
@@ -257,7 +247,9 @@ class EnsureBlockletRunning {
|
|
|
257
247
|
blockletDids.map((did) => {
|
|
258
248
|
return async () => {
|
|
259
249
|
const blocklets = this.fakeRunningBlocklets[did];
|
|
260
|
-
const componentDids = blocklets
|
|
250
|
+
const componentDids = blocklets
|
|
251
|
+
.filter((b) => b.status === BlockletStatus.running || b.status === BlockletStatus.waiting)
|
|
252
|
+
.map((b) => b.meta.did);
|
|
261
253
|
try {
|
|
262
254
|
// eslint-disable-next-line
|
|
263
255
|
await this.states.blocklet.setBlockletStatus(did, BlockletStatus.waiting, { componentDids });
|
|
@@ -266,17 +258,11 @@ class EnsureBlockletRunning {
|
|
|
266
258
|
}
|
|
267
259
|
};
|
|
268
260
|
}),
|
|
269
|
-
{ concurrency:
|
|
261
|
+
{ concurrency: 8 }
|
|
270
262
|
);
|
|
271
263
|
};
|
|
272
264
|
|
|
273
265
|
restartFakeRunningBlocklets = async () => {
|
|
274
|
-
for (const key of Object.keys(this.restartingBlocklets)) {
|
|
275
|
-
if (Date.now() - this.restartingBlocklets[key] > this.restartInterval) {
|
|
276
|
-
delete this.restartingBlocklets[key];
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
266
|
// blocklet 一组组重启
|
|
281
267
|
const blockletDids = Object.keys(this.fakeRunningBlocklets);
|
|
282
268
|
await pAll(
|
|
@@ -286,7 +272,8 @@ class EnsureBlockletRunning {
|
|
|
286
272
|
const componentDids = blocklets.map((b) => b.meta.did);
|
|
287
273
|
if (componentDids.length > 0) {
|
|
288
274
|
const key = `${did}-${componentDids.join('-')}`;
|
|
289
|
-
|
|
275
|
+
this.needRestartBlocklets[key] = true;
|
|
276
|
+
if (this.restartingBlocklets[key] && this.restartingBlocklets[key] + this.checkInterval < Date.now()) {
|
|
290
277
|
return;
|
|
291
278
|
}
|
|
292
279
|
this.restartingBlocklets[key] = Date.now();
|
|
@@ -294,31 +281,36 @@ class EnsureBlockletRunning {
|
|
|
294
281
|
const blockletDisplayName = this.getDisplayNameByRootDid(did);
|
|
295
282
|
const restartTitle = 'Blocklet health check failed';
|
|
296
283
|
const restartDescription = `Blocklet ${blockletDisplayName} with components ${componentDids.map((v) => this.getDisplayName(blocklets.find((b) => b.meta.did === v))).join(', ')} health check failed, restarting...`;
|
|
297
|
-
this.
|
|
284
|
+
if (this.whenCycleCheck) {
|
|
285
|
+
this.notification(did, restartTitle, restartDescription, 'warning');
|
|
286
|
+
}
|
|
298
287
|
|
|
299
288
|
try {
|
|
300
289
|
logger.info('restart blocklet:', did, componentDids);
|
|
301
290
|
await this.restart({ did, componentDids, checkHealthImmediately: true });
|
|
302
|
-
this.
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
context: {
|
|
309
|
-
user: {
|
|
310
|
-
did,
|
|
311
|
-
role: 'daemon',
|
|
312
|
-
blockletDid: did,
|
|
313
|
-
fullName: blockletDisplayName,
|
|
314
|
-
elevated: false,
|
|
291
|
+
if (this.whenCycleCheck) {
|
|
292
|
+
this.createAuditLog({
|
|
293
|
+
action: 'ensureBlockletRunning',
|
|
294
|
+
args: {
|
|
295
|
+
teamDid: did,
|
|
296
|
+
componentDids,
|
|
315
297
|
},
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
298
|
+
context: {
|
|
299
|
+
user: {
|
|
300
|
+
did,
|
|
301
|
+
role: 'daemon',
|
|
302
|
+
blockletDid: did,
|
|
303
|
+
fullName: blockletDisplayName,
|
|
304
|
+
elevated: false,
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
result: {
|
|
308
|
+
title: restartTitle,
|
|
309
|
+
description: restartDescription,
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
322
314
|
delete this.restartingBlocklets[key];
|
|
323
315
|
} catch (e) {
|
|
324
316
|
logger.error('restart blocklet failed', did, componentDids, e);
|
|
@@ -327,15 +319,16 @@ class EnsureBlockletRunning {
|
|
|
327
319
|
}
|
|
328
320
|
this.errorStartBlocklets[key] += 1;
|
|
329
321
|
|
|
330
|
-
// 如果重启失败次数超过 3
|
|
331
|
-
if (this.errorStartBlocklets[key] >= 3) {
|
|
322
|
+
// 如果重启失败次数超过 3 次,则发送通知, 如果 server 是第一次启动遇到失败,则立刻发送通知
|
|
323
|
+
if (this.errorStartBlocklets[key] >= 3 || !this.whenCycleCheck) {
|
|
332
324
|
const title = 'Restart blocklet failed when health check failed';
|
|
333
325
|
const description = `Restart blocklet ${blockletDisplayName} with components ${componentDids.map((v) => this.getDisplayName(blocklets.find((b) => b.meta.did === v))).join(', ')} failed`;
|
|
334
326
|
this.notification(did, title, description, 'error');
|
|
335
327
|
delete this.errorStartBlocklets[key];
|
|
336
|
-
logger.error('restart many times blocklet failed
|
|
328
|
+
logger.error('restart many times blocklet failed', did, componentDids, e);
|
|
337
329
|
try {
|
|
338
|
-
|
|
330
|
+
// 失败了应该保持 error 状态
|
|
331
|
+
await this.states.blocklet.setBlockletStatus(did, BlockletStatus.error, { componentDids });
|
|
339
332
|
this.createAuditLog({
|
|
340
333
|
action: 'ensureBlockletRunning',
|
|
341
334
|
args: {
|
|
@@ -358,14 +351,14 @@ class EnsureBlockletRunning {
|
|
|
358
351
|
},
|
|
359
352
|
});
|
|
360
353
|
} catch (err) {
|
|
361
|
-
logger.error('
|
|
354
|
+
logger.error('ensure blocklet running, create audit log failed', did, componentDids, err);
|
|
362
355
|
}
|
|
363
356
|
}
|
|
364
357
|
}
|
|
365
358
|
}
|
|
366
359
|
};
|
|
367
360
|
}),
|
|
368
|
-
{ concurrency:
|
|
361
|
+
{ concurrency: 8 }
|
|
369
362
|
);
|
|
370
363
|
};
|
|
371
364
|
}
|
|
@@ -38918,7 +38918,7 @@ module.exports = require("zlib");
|
|
|
38918
38918
|
/***/ ((module) => {
|
|
38919
38919
|
|
|
38920
38920
|
"use strict";
|
|
38921
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.46","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.46","@abtnode/auth":"1.16.46","@abtnode/certificate-manager":"1.16.46","@abtnode/client":"1.16.46","@abtnode/constant":"1.16.46","@abtnode/cron":"1.16.46","@abtnode/db-cache":"1.16.46","@abtnode/docker-utils":"1.16.46","@abtnode/logger":"1.16.46","@abtnode/models":"1.16.46","@abtnode/queue":"1.16.46","@abtnode/rbac":"1.16.46","@abtnode/router-provider":"1.16.46","@abtnode/static-server":"1.16.46","@abtnode/timemachine":"1.16.46","@abtnode/util":"1.16.46","@aigne/aigne-hub":"^0.
|
|
38921
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.46","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.46","@abtnode/auth":"1.16.46","@abtnode/certificate-manager":"1.16.46","@abtnode/client":"1.16.46","@abtnode/constant":"1.16.46","@abtnode/cron":"1.16.46","@abtnode/db-cache":"1.16.46","@abtnode/docker-utils":"1.16.46","@abtnode/logger":"1.16.46","@abtnode/models":"1.16.46","@abtnode/queue":"1.16.46","@abtnode/rbac":"1.16.46","@abtnode/router-provider":"1.16.46","@abtnode/static-server":"1.16.46","@abtnode/timemachine":"1.16.46","@abtnode/util":"1.16.46","@aigne/aigne-hub":"^0.4.6","@arcblock/did":"1.21.2","@arcblock/did-connect-js":"1.21.2","@arcblock/did-ext":"1.21.2","@arcblock/did-motif":"^1.1.14","@arcblock/did-util":"1.21.2","@arcblock/event-hub":"1.21.2","@arcblock/jwt":"1.21.2","@arcblock/pm2-events":"^0.0.5","@arcblock/validator":"1.21.2","@arcblock/vc":"1.21.2","@blocklet/constant":"1.16.46","@blocklet/did-space-js":"^1.1.14","@blocklet/env":"1.16.46","@blocklet/error":"^0.2.5","@blocklet/meta":"1.16.46","@blocklet/resolver":"1.16.46","@blocklet/sdk":"1.16.46","@blocklet/store":"1.16.46","@blocklet/theme":"^3.1.3","@fidm/x509":"^1.2.1","@ocap/mcrypto":"1.21.2","@ocap/util":"1.21.2","@ocap/wallet":"1.21.2","@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","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","private-ip":"^2.3.4","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":"^11.1.0","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"}');
|
|
38922
38922
|
|
|
38923
38923
|
/***/ }),
|
|
38924
38924
|
|
|
@@ -234,7 +234,8 @@ class NodeRuntimeMonitor extends EventEmitter {
|
|
|
234
234
|
if (cpus.some((v) => v > maxCpus) && memory > maxMem) {
|
|
235
235
|
highType = 'cpu and memory';
|
|
236
236
|
}
|
|
237
|
-
|
|
237
|
+
// 1 表示虚拟盘,不参与计算
|
|
238
|
+
if (disks.some((v) => v !== 1 && v > maxDisk)) {
|
|
238
239
|
highType = 'disk';
|
|
239
240
|
}
|
|
240
241
|
if (highType) {
|
package/lib/states/base.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const { BaseState } = require('@abtnode/models');
|
|
2
|
+
const { isAllowedURL } = require('@abtnode/util/lib/ssrf-protector');
|
|
3
|
+
const { CustomError } = require('@blocklet/error');
|
|
2
4
|
const logger = require('@abtnode/logger')('@abtnode/core:states:base');
|
|
3
5
|
|
|
4
6
|
const { isCLI } = require('../util');
|
|
@@ -20,6 +22,37 @@ class ExtendedBase extends BaseState {
|
|
|
20
22
|
update(condition, updates, options) {
|
|
21
23
|
return super.update(condition, updates, { returnUpdatedDocs: true, ...options });
|
|
22
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 验证输入的URL是否合法,如果非法禁止保存
|
|
28
|
+
*/
|
|
29
|
+
async validateURL(urls = [], label = '') {
|
|
30
|
+
if (urls.length === 0) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
const validateResults = await Promise.all(
|
|
34
|
+
urls.map(async (url) => {
|
|
35
|
+
try {
|
|
36
|
+
const allowed = await isAllowedURL(url);
|
|
37
|
+
return { url, allowed };
|
|
38
|
+
} catch (error) {
|
|
39
|
+
logger.warn('URL validation failed', { url, error });
|
|
40
|
+
return { url, allowed: false };
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const allowed = validateResults.every((item) => item.allowed);
|
|
46
|
+
if (!allowed) {
|
|
47
|
+
const invalidUrls = validateResults
|
|
48
|
+
.filter((result) => !result.allowed)
|
|
49
|
+
.map((result) => result.url)
|
|
50
|
+
.filter(Boolean);
|
|
51
|
+
|
|
52
|
+
throw new CustomError(400, `Invalid ${label || 'URLs'}: ${invalidUrls.join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
23
56
|
}
|
|
24
57
|
|
|
25
58
|
module.exports = ExtendedBase;
|
package/lib/states/user.js
CHANGED
|
@@ -121,6 +121,10 @@ class User extends ExtendBase {
|
|
|
121
121
|
Object.assign(pending, await this._extractInviteInfo({ did, ...pending }));
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
// 验证 webhooks 中 URL 是否合法
|
|
125
|
+
const webhooks = get(pending, 'extra.webhooks', []);
|
|
126
|
+
await this.validateURL(webhooks.map((x) => x.url));
|
|
127
|
+
|
|
124
128
|
await super.update({ did }, { $set: pending });
|
|
125
129
|
await Promise.all(
|
|
126
130
|
(get(cloneData, 'passports') || [])
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const Joi = require('joi');
|
|
2
|
+
const { CustomError } = require('@blocklet/error');
|
|
3
|
+
|
|
2
4
|
const BaseState = require('./base');
|
|
3
5
|
|
|
4
6
|
const webhookEndpointSchema = Joi.object({
|
|
@@ -41,6 +43,9 @@ const updateWebhookEndpointSchema = Joi.object({
|
|
|
41
43
|
*/
|
|
42
44
|
class WebhookEndpointState extends BaseState {
|
|
43
45
|
async create(input) {
|
|
46
|
+
// 校验非法的URL
|
|
47
|
+
await this.validateURL([input.url], 'webhook endpoint URL');
|
|
48
|
+
|
|
44
49
|
await webhookEndpointSchema.validateAsync(input, { stripUnknown: true });
|
|
45
50
|
|
|
46
51
|
const webhook = await this.insert(input);
|
|
@@ -55,7 +60,7 @@ class WebhookEndpointState extends BaseState {
|
|
|
55
60
|
async updateWebhook(id, updates) {
|
|
56
61
|
const doc = await this.findOne({ where: { id } });
|
|
57
62
|
if (!doc) {
|
|
58
|
-
throw new
|
|
63
|
+
throw new CustomError(400, 'webhook endpoint not found');
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
await updateWebhookEndpointSchema.validateAsync(updates, { stripUnknown: true });
|
|
@@ -67,7 +72,7 @@ class WebhookEndpointState extends BaseState {
|
|
|
67
72
|
async deleteWebhook(id) {
|
|
68
73
|
const doc = await this.findOne({ where: { id } });
|
|
69
74
|
if (!doc) {
|
|
70
|
-
throw new
|
|
75
|
+
throw new CustomError(400, 'webhook endpoint not found');
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
await this.remove({ id });
|
package/lib/states/webhook.js
CHANGED
|
@@ -9,6 +9,10 @@ const { validateWebhook } = require('../validators/webhook');
|
|
|
9
9
|
class WebhookState extends BaseState {
|
|
10
10
|
async create(info, { mock } = {}) {
|
|
11
11
|
const { type, params } = info;
|
|
12
|
+
|
|
13
|
+
const urls = params.map((item) => item.value);
|
|
14
|
+
await this.validateURL(urls, 'webhook URLs');
|
|
15
|
+
|
|
12
16
|
const filterParams = params.map((item) => {
|
|
13
17
|
const data = {
|
|
14
18
|
name: item.name,
|
package/lib/team/manager.js
CHANGED
|
@@ -495,7 +495,7 @@ class TeamManager extends EventEmitter {
|
|
|
495
495
|
// 获取 notification state
|
|
496
496
|
const notificationState = await this.getNotificationState(teamDid);
|
|
497
497
|
if (!notificationState) {
|
|
498
|
-
|
|
498
|
+
return undefined;
|
|
499
499
|
}
|
|
500
500
|
|
|
501
501
|
// 检查通知是否已存在
|
|
@@ -505,7 +505,7 @@ class TeamManager extends EventEmitter {
|
|
|
505
505
|
|
|
506
506
|
if (!receivers?.length && process.env.NODE_ENV !== 'test') {
|
|
507
507
|
logger.warn('No valid receivers', { teamDid, receiver });
|
|
508
|
-
|
|
508
|
+
return undefined;
|
|
509
509
|
}
|
|
510
510
|
|
|
511
511
|
const notificationActor = notification?.activity?.actor || payload.activity?.actor;
|
package/lib/util/aigne-verify.js
CHANGED
|
@@ -49,7 +49,7 @@ const verifyAigneHub = async (config, blocklet) => {
|
|
|
49
49
|
const { did, name } = blocklet.meta || {};
|
|
50
50
|
|
|
51
51
|
const modelConfig = {
|
|
52
|
-
|
|
52
|
+
apiKey: decryptValue(config.key, did),
|
|
53
53
|
model: !config.model || config.model === 'auto' ? undefined : config.model,
|
|
54
54
|
url: baseUrl,
|
|
55
55
|
};
|
package/lib/util/blocklet.js
CHANGED
|
@@ -21,7 +21,7 @@ const diff = require('deep-diff');
|
|
|
21
21
|
const createArchive = require('archiver');
|
|
22
22
|
const isUrl = require('is-url');
|
|
23
23
|
const semver = require('semver');
|
|
24
|
-
const { chainInfo: chainInfoSchema } = require('@arcblock/did-
|
|
24
|
+
const { chainInfo: chainInfoSchema } = require('@arcblock/did-connect-js/lib/schema');
|
|
25
25
|
|
|
26
26
|
const urlPathFriendly = require('@blocklet/meta/lib/url-path-friendly').default;
|
|
27
27
|
const { fromSecretKey, fromPublicKey } = require('@ocap/wallet');
|
|
@@ -145,7 +145,7 @@ const getBlockletEngineNameByPlatform = (meta) => getBlockletEngine(meta).interp
|
|
|
145
145
|
const startLock = new DBCache(() => ({
|
|
146
146
|
...getAbtNodeRedisAndSQLiteUrl(),
|
|
147
147
|
prefix: 'blocklet-start-locks2',
|
|
148
|
-
ttl: 1000 * 60 *
|
|
148
|
+
ttl: 1000 * 60 * 3,
|
|
149
149
|
}));
|
|
150
150
|
|
|
151
151
|
const blockletCache = new DBCache(() => ({
|
|
@@ -737,6 +737,7 @@ const startBlockletProcess = async (
|
|
|
737
737
|
|
|
738
738
|
if (b.mode === BLOCKLET_MODES.DEVELOPMENT) {
|
|
739
739
|
options.env.NODE_ENV = e2eMode ? 'e2e' : 'development';
|
|
740
|
+
options.env.IS_E2E = e2eMode ? '1' : undefined;
|
|
740
741
|
options.env.BROWSER = 'none';
|
|
741
742
|
options.env.PORT = options.env[BLOCKLET_DEFAULT_PORT_NAME];
|
|
742
743
|
|
|
@@ -818,23 +819,21 @@ const startBlockletProcess = async (
|
|
|
818
819
|
* @returns
|
|
819
820
|
*/
|
|
820
821
|
async (b, { ancestors }) => {
|
|
821
|
-
const lockName =
|
|
822
|
-
blocklet.meta.did,
|
|
823
|
-
blocklet.meta.version,
|
|
824
|
-
blocklet.meta.group,
|
|
825
|
-
b.meta.did,
|
|
826
|
-
b.meta.version,
|
|
827
|
-
b.meta.group,
|
|
828
|
-
].join('__');
|
|
822
|
+
const lockName = `${blocklet.meta.did}-${b.meta.did}`;
|
|
829
823
|
|
|
824
|
+
// 如果锁存在,则跳过执行
|
|
825
|
+
if (!(await startLock.hasExpired(lockName))) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
830
828
|
await startLock.acquire(lockName);
|
|
829
|
+
|
|
831
830
|
try {
|
|
832
831
|
await startBlockletTask(b, { ancestors });
|
|
833
832
|
} finally {
|
|
834
833
|
startLock.releaseLock(lockName);
|
|
835
834
|
}
|
|
836
835
|
},
|
|
837
|
-
{ parallel: true, concurrencyLimit:
|
|
836
|
+
{ parallel: true, concurrencyLimit: 4 }
|
|
838
837
|
);
|
|
839
838
|
};
|
|
840
839
|
|
|
@@ -12,7 +12,7 @@ const { createDockerImage } = require('./create-docker-image');
|
|
|
12
12
|
|
|
13
13
|
const lockFile = new DBCache(() => ({
|
|
14
14
|
prefix: 'docker-exec-chown-locks',
|
|
15
|
-
ttl: 1000 * 60 *
|
|
15
|
+
ttl: 1000 * 60 * 3,
|
|
16
16
|
...getAbtNodeRedisAndSQLiteUrl(),
|
|
17
17
|
}));
|
|
18
18
|
|
|
@@ -53,10 +53,14 @@ async function dockerExecChown({ name, dirs, code = 777, force = false }) {
|
|
|
53
53
|
return `chmod ${code === 750 ? '' : '-R'} ${code} ${path.join(baseDir, dir.replace(process.env.ABT_NODE_DATA_DIR, ''))}`;
|
|
54
54
|
})
|
|
55
55
|
.join(' && ');
|
|
56
|
-
const realName = parseDockerName(name, 'docker-exec-chown');
|
|
57
|
-
const startTime = Date.now();
|
|
58
56
|
|
|
57
|
+
const startTime = Date.now();
|
|
58
|
+
const realName = parseDockerName(name, 'docker-exec-chown');
|
|
59
|
+
if (!(await lockFile.hasExpired(realName))) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
59
62
|
await lockFile.acquire(realName);
|
|
63
|
+
|
|
60
64
|
try {
|
|
61
65
|
await promiseSpawn(
|
|
62
66
|
`docker rm -fv ${realName} > /dev/null 2>&1 || true && docker run --rm --name ${realName} ${volumes} ${image} sh -c '${command}'`,
|
|
@@ -2,10 +2,20 @@ const fs = require('fs').promises;
|
|
|
2
2
|
const fse = require('fs-extra');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const promiseSpawn = require('@abtnode/util/lib/promise-spawn');
|
|
5
|
+
const { DBCache, getAbtNodeRedisAndSQLiteUrl } = require('@abtnode/db-cache');
|
|
5
6
|
const logger = require('@abtnode/logger')('@abtnode/docker-exec');
|
|
7
|
+
|
|
6
8
|
const parseDockerOptionsFromPm2 = require('./parse-docker-options-from-pm2');
|
|
7
9
|
const { checkDockerInstalled } = require('./check-docker-installed');
|
|
8
10
|
|
|
11
|
+
const lock = new DBCache(() => {
|
|
12
|
+
return {
|
|
13
|
+
...getAbtNodeRedisAndSQLiteUrl(),
|
|
14
|
+
prefix: 'docker-exec',
|
|
15
|
+
ttl: 1000 * 60 * 3,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
9
19
|
async function dockerExec({
|
|
10
20
|
blocklet,
|
|
11
21
|
meta,
|
|
@@ -15,7 +25,7 @@ async function dockerExec({
|
|
|
15
25
|
runScriptDir,
|
|
16
26
|
runScriptParams,
|
|
17
27
|
nodeInfo,
|
|
18
|
-
timeout =
|
|
28
|
+
timeout = 120_000,
|
|
19
29
|
retry = 3,
|
|
20
30
|
output,
|
|
21
31
|
error,
|
|
@@ -40,8 +50,13 @@ async function dockerExec({
|
|
|
40
50
|
await fs.writeFile(paramsFilePath, JSON.stringify(runScriptParams));
|
|
41
51
|
}
|
|
42
52
|
|
|
43
|
-
const
|
|
53
|
+
const lockKey = `${blockletDid}-${meta.name}-${hookName}`;
|
|
54
|
+
if (!(await lock.hasExpired(lockKey))) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
await lock.acquire(lockKey);
|
|
44
58
|
|
|
59
|
+
const command = `sh -c 'cd $BLOCKLET_APP_DIR && ${script}'`;
|
|
45
60
|
const options = await parseDockerOptionsFromPm2({
|
|
46
61
|
options: {
|
|
47
62
|
name: `${blockletDid}-${meta.name}-${hookName}`,
|
|
@@ -54,6 +69,7 @@ async function dockerExec({
|
|
|
54
69
|
dockerNamePrefix: 'docker-exec',
|
|
55
70
|
eventName: hookName,
|
|
56
71
|
});
|
|
72
|
+
|
|
57
73
|
const startTime = Date.now();
|
|
58
74
|
try {
|
|
59
75
|
await promiseSpawn(
|
|
@@ -65,12 +81,9 @@ async function dockerExec({
|
|
|
65
81
|
{ timeout, retry }
|
|
66
82
|
);
|
|
67
83
|
} finally {
|
|
84
|
+
await lock.releaseLock(lockKey);
|
|
68
85
|
if (nodeInfo.isDockerInstalled) {
|
|
69
|
-
await promiseSpawn(
|
|
70
|
-
`docker rm -fv ${options.env.dockerName} > /dev/null 2>&1 || true`,
|
|
71
|
-
{},
|
|
72
|
-
{ timeout: 1000 * 10, retry: 3 }
|
|
73
|
-
);
|
|
86
|
+
await promiseSpawn(`docker rm -f ${options.env.dockerName} > /dev/null 2>&1 || true`, {}, { timeout: 1000 * 10 });
|
|
74
87
|
}
|
|
75
88
|
}
|
|
76
89
|
logger.info(`dockerExec ${options.env.dockerName} cost time: ${Date.now() - startTime}ms`);
|
|
@@ -334,13 +334,8 @@ async function parseDockerOptionsFromPm2({
|
|
|
334
334
|
user = `-u ${uid}:${gid}`;
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
try {
|
|
338
|
-
await promiseSpawn(`docker rm -f ${name}`, { mute: true }, { timeout: 5 * 1000, retry: 0 });
|
|
339
|
-
} catch (_) {
|
|
340
|
-
// ignore error
|
|
341
|
-
}
|
|
342
|
-
|
|
343
337
|
nextOptions.script = `
|
|
338
|
+
docker rm -f ${name} > /dev/null 2>&1 || true && \
|
|
344
339
|
docker run --rm --name ${name} \
|
|
345
340
|
${user} \
|
|
346
341
|
${volumes} \
|
|
@@ -350,7 +345,6 @@ async function parseDockerOptionsFromPm2({
|
|
|
350
345
|
--cpus="${dockerEnv.BLOCKLET_DOCKER_CPUS}" \
|
|
351
346
|
--memory="${dockerEnv.BLOCKLET_DOCKER_MEMORY}" \
|
|
352
347
|
--memory-swap="${dockerEnv.BLOCKLET_DOCKER_MEMORY}" \
|
|
353
|
-
--memory-swappiness=0 \
|
|
354
348
|
--oom-kill-disable=false \
|
|
355
349
|
--env-file ${dockerEnvFile} \
|
|
356
350
|
${dockerInfo.network} \
|
package/lib/util/ensure-bun.js
CHANGED
|
@@ -36,7 +36,7 @@ async function _ensureBun() {
|
|
|
36
36
|
// 如果有 bun 且版本大于等于 BUN_VERSION, 则直接使用现有的 bun
|
|
37
37
|
if (whichBun) {
|
|
38
38
|
// 检查 bun 版本
|
|
39
|
-
const bunVersion = shelljs.exec(`${whichBun} --version
|
|
39
|
+
const bunVersion = shelljs.exec(`${whichBun} --version`, { silent: true }).stdout.trim();
|
|
40
40
|
// 判断 bun 版本是否大于等于 BUN_VERSION, 应该用版本对比库
|
|
41
41
|
if (semver.gte(bunVersion, BUN_VERSION)) {
|
|
42
42
|
return whichBun.toString();
|
package/lib/util/env.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const serverJobBackoffSeconds = process.env.ABT_NODE_JOB_BACKOFF_SECONDS
|
|
2
2
|
? +process.env.ABT_NODE_JOB_BACKOFF_SECONDS
|
|
3
3
|
: 600;
|
|
4
|
+
const isE2E = process.env.NODE_ENV === 'e2e' || ['1', 'true'].includes(process.env.IS_E2E);
|
|
4
5
|
|
|
5
6
|
const shouldJobBackoff = () => {
|
|
6
7
|
if (process.env.ABT_NODE_JOB_BACKOFF_SECONDS === '0') {
|
|
@@ -13,4 +14,5 @@ const shouldJobBackoff = () => {
|
|
|
13
14
|
module.exports = {
|
|
14
15
|
serverJobBackoffSeconds,
|
|
15
16
|
shouldJobBackoff,
|
|
17
|
+
isE2E,
|
|
16
18
|
};
|
package/lib/util/reset-node.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const logger = require('@abtnode/logger')('@abtnode/core:util:reset');
|
|
2
2
|
|
|
3
3
|
const states = require('../states');
|
|
4
|
+
const { isE2E } = require('./env');
|
|
4
5
|
|
|
5
6
|
const noop = () => true;
|
|
6
7
|
|
|
@@ -121,7 +122,7 @@ module.exports = async ({
|
|
|
121
122
|
teamManager,
|
|
122
123
|
certManager,
|
|
123
124
|
}) => {
|
|
124
|
-
if (
|
|
125
|
+
if (!isE2E) {
|
|
125
126
|
throw new Error('Reset node only exists for test purpose');
|
|
126
127
|
}
|
|
127
128
|
|
package/lib/webhook/index.js
CHANGED
|
@@ -3,6 +3,7 @@ const { evaluateURLs } = require('@abtnode/util/lib/url-evaluation');
|
|
|
3
3
|
const checkURLAccessible = require('@abtnode/util/lib/url-evaluation/check-accessible-node');
|
|
4
4
|
const { EVENTS } = require('@abtnode/constant');
|
|
5
5
|
const isEmpty = require('lodash/isEmpty');
|
|
6
|
+
const { isAllowedReferer } = require('@abtnode/util/lib/ssrf-protector');
|
|
6
7
|
|
|
7
8
|
const { joinURL } = require('ufo');
|
|
8
9
|
const isUrl = require('is-url');
|
|
@@ -235,13 +236,21 @@ module.exports = ({ events, dataDirs, instance, teamManager }) => {
|
|
|
235
236
|
}
|
|
236
237
|
};
|
|
237
238
|
|
|
238
|
-
const sendTestMessage = async (msg) => {
|
|
239
|
+
const sendTestMessage = async (msg, context) => {
|
|
239
240
|
const { webhookId, message } = msg;
|
|
240
241
|
try {
|
|
242
|
+
if (context?.referer) {
|
|
243
|
+
const { host = '' } = context || {};
|
|
244
|
+
if (!isAllowedReferer(context.referer, host)) {
|
|
245
|
+
throw new Error('Invalid request');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
241
248
|
const webhook = webhookId ? await webhookState.findOne(webhookId) : msg.webhook;
|
|
249
|
+
|
|
242
250
|
await sentTextMessage(webhook, message);
|
|
243
251
|
} catch (error) {
|
|
244
252
|
logger.error('webhook sender error', { error, msg });
|
|
253
|
+
throw new Error(error.message);
|
|
245
254
|
}
|
|
246
255
|
};
|
|
247
256
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const Joi = require('joi');
|
|
2
2
|
const axios = require('@abtnode/util/lib/axios');
|
|
3
3
|
const logger = require('@abtnode/logger')('@abtnode/core:sender:api');
|
|
4
|
+
const { isAllowedURL } = require('@abtnode/util/lib/ssrf-protector');
|
|
4
5
|
const BaseSender = require('../base');
|
|
5
6
|
|
|
6
7
|
class APISender extends BaseSender {
|
|
@@ -9,6 +10,9 @@ class APISender extends BaseSender {
|
|
|
9
10
|
const { status, name, title, description } = message;
|
|
10
11
|
|
|
11
12
|
try {
|
|
13
|
+
if (!(await isAllowedURL(url))) {
|
|
14
|
+
throw new Error('Invalid URL');
|
|
15
|
+
}
|
|
12
16
|
await axios.post(
|
|
13
17
|
url,
|
|
14
18
|
{
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.16.47-beta-
|
|
6
|
+
"version": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -19,46 +19,46 @@
|
|
|
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.47-beta-
|
|
23
|
-
"@abtnode/auth": "1.16.47-beta-
|
|
24
|
-
"@abtnode/certificate-manager": "1.16.47-beta-
|
|
25
|
-
"@abtnode/client": "1.16.47-beta-
|
|
26
|
-
"@abtnode/constant": "1.16.47-beta-
|
|
27
|
-
"@abtnode/cron": "1.16.47-beta-
|
|
28
|
-
"@abtnode/db-cache": "1.16.47-beta-
|
|
29
|
-
"@abtnode/docker-utils": "1.16.47-beta-
|
|
30
|
-
"@abtnode/logger": "1.16.47-beta-
|
|
31
|
-
"@abtnode/models": "1.16.47-beta-
|
|
32
|
-
"@abtnode/queue": "1.16.47-beta-
|
|
33
|
-
"@abtnode/rbac": "1.16.47-beta-
|
|
34
|
-
"@abtnode/router-provider": "1.16.47-beta-
|
|
35
|
-
"@abtnode/static-server": "1.16.47-beta-
|
|
36
|
-
"@abtnode/timemachine": "1.16.47-beta-
|
|
37
|
-
"@abtnode/util": "1.16.47-beta-
|
|
38
|
-
"@aigne/aigne-hub": "^0.
|
|
39
|
-
"@arcblock/did": "1.21.
|
|
40
|
-
"@arcblock/did-
|
|
41
|
-
"@arcblock/did-ext": "1.21.
|
|
22
|
+
"@abtnode/analytics": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
23
|
+
"@abtnode/auth": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
24
|
+
"@abtnode/certificate-manager": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
25
|
+
"@abtnode/client": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
26
|
+
"@abtnode/constant": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
27
|
+
"@abtnode/cron": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
28
|
+
"@abtnode/db-cache": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
29
|
+
"@abtnode/docker-utils": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
30
|
+
"@abtnode/logger": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
31
|
+
"@abtnode/models": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
32
|
+
"@abtnode/queue": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
33
|
+
"@abtnode/rbac": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
34
|
+
"@abtnode/router-provider": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
35
|
+
"@abtnode/static-server": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
36
|
+
"@abtnode/timemachine": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
37
|
+
"@abtnode/util": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
38
|
+
"@aigne/aigne-hub": "^0.4.6",
|
|
39
|
+
"@arcblock/did": "1.21.2",
|
|
40
|
+
"@arcblock/did-connect-js": "1.21.2",
|
|
41
|
+
"@arcblock/did-ext": "1.21.2",
|
|
42
42
|
"@arcblock/did-motif": "^1.1.14",
|
|
43
|
-
"@arcblock/did-util": "1.21.
|
|
44
|
-
"@arcblock/event-hub": "1.21.
|
|
45
|
-
"@arcblock/jwt": "1.21.
|
|
43
|
+
"@arcblock/did-util": "1.21.2",
|
|
44
|
+
"@arcblock/event-hub": "1.21.2",
|
|
45
|
+
"@arcblock/jwt": "1.21.2",
|
|
46
46
|
"@arcblock/pm2-events": "^0.0.5",
|
|
47
|
-
"@arcblock/validator": "1.21.
|
|
48
|
-
"@arcblock/vc": "1.21.
|
|
49
|
-
"@blocklet/constant": "1.16.47-beta-
|
|
50
|
-
"@blocklet/did-space-js": "^1.1.
|
|
51
|
-
"@blocklet/env": "1.16.47-beta-
|
|
47
|
+
"@arcblock/validator": "1.21.2",
|
|
48
|
+
"@arcblock/vc": "1.21.2",
|
|
49
|
+
"@blocklet/constant": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
50
|
+
"@blocklet/did-space-js": "^1.1.14",
|
|
51
|
+
"@blocklet/env": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
52
52
|
"@blocklet/error": "^0.2.5",
|
|
53
|
-
"@blocklet/meta": "1.16.47-beta-
|
|
54
|
-
"@blocklet/resolver": "1.16.47-beta-
|
|
55
|
-
"@blocklet/sdk": "1.16.47-beta-
|
|
56
|
-
"@blocklet/store": "1.16.47-beta-
|
|
57
|
-
"@blocklet/theme": "^3.
|
|
53
|
+
"@blocklet/meta": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
54
|
+
"@blocklet/resolver": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
55
|
+
"@blocklet/sdk": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
56
|
+
"@blocklet/store": "1.16.47-beta-20250808-102837-d10f3b40",
|
|
57
|
+
"@blocklet/theme": "^3.1.3",
|
|
58
58
|
"@fidm/x509": "^1.2.1",
|
|
59
|
-
"@ocap/mcrypto": "1.21.
|
|
60
|
-
"@ocap/util": "1.21.
|
|
61
|
-
"@ocap/wallet": "1.21.
|
|
59
|
+
"@ocap/mcrypto": "1.21.2",
|
|
60
|
+
"@ocap/util": "1.21.2",
|
|
61
|
+
"@ocap/wallet": "1.21.2",
|
|
62
62
|
"@slack/webhook": "^5.0.4",
|
|
63
63
|
"archiver": "^7.0.1",
|
|
64
64
|
"axios": "^1.7.9",
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"p-map": "^4.0.0",
|
|
92
92
|
"p-retry": "^4.6.2",
|
|
93
93
|
"p-wait-for": "^3.2.0",
|
|
94
|
+
"private-ip": "^2.3.4",
|
|
94
95
|
"rate-limiter-flexible": "^5.0.5",
|
|
95
96
|
"read-last-lines": "^1.8.0",
|
|
96
97
|
"semver": "^7.6.3",
|
|
@@ -117,5 +118,5 @@
|
|
|
117
118
|
"jest": "^29.7.0",
|
|
118
119
|
"unzipper": "^0.10.11"
|
|
119
120
|
},
|
|
120
|
-
"gitHead": "
|
|
121
|
+
"gitHead": "545f4b619e4e872f3cb6645aa95a0c22c06b58d0"
|
|
121
122
|
}
|