crewx 0.8.8-rc.2 → 0.8.8-rc.21
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/bin/cli-commands.js +1 -1
- package/dist/assets/{MarketPage-Bh1UfiC2.js → MarketPage-B5JbqK4T.js} +9 -14
- package/dist/assets/{PromptTab-DYmYVBKo.js → PromptTab-_97mSbLk.js} +7 -7
- package/dist/assets/{arc-C14FbhzP.js → arc-3FDm7TxG.js} +1 -1
- package/dist/assets/architectureDiagram-UYN6MBPD-D8X56UlC.js +36 -0
- package/dist/assets/blockDiagram-ZHA2E4KO-B6jUnEVc.js +121 -0
- package/dist/assets/{c4Diagram-6F5ED5ID-DLSfQS-Z.js → c4Diagram-6F5ED5ID-Cy8x6Ims.js} +1 -1
- package/dist/assets/channel-Bs0J8jjN.js +1 -0
- package/dist/assets/{chunk-5HRBRIJM-j7YcJ7Bx.js → chunk-5HRBRIJM-Bh819Q5A.js} +1 -1
- package/dist/assets/{chunk-7U56Z5CX-Baqt1cj3.js → chunk-7U56Z5CX-CfNtPGa8.js} +1 -1
- package/dist/assets/{chunk-ASOPGD6M-F1YJ2mzH.js → chunk-ASOPGD6M-CMR1ElQ6.js} +1 -1
- package/dist/assets/{chunk-KFBOBJHC-Dvhzl6G9.js → chunk-KFBOBJHC-Dm2I1dHM.js} +1 -1
- package/dist/assets/{chunk-T2TOU4HS-BDczCXP6.js → chunk-T2TOU4HS-0tf-aNtk.js} +1 -1
- package/dist/assets/{chunk-TMUBEWPD-4BhwBH_F.js → chunk-TMUBEWPD-b5ZrpKJw.js} +1 -1
- package/dist/assets/classDiagram-LNE6IOMH-B1-6qC07.js +1 -0
- package/dist/assets/classDiagram-v2-MQ7JQ4JX-B1-6qC07.js +1 -0
- package/dist/assets/dagre-4EVJKHTY-zYmFBlwR.js +4 -0
- package/dist/assets/diagram-QW4FP2JN-DkZngH-Z.js +24 -0
- package/dist/assets/{erDiagram-6RL3IURR-DRhNKkH0.js → erDiagram-6RL3IURR-8TfuIQ6D.js} +2 -2
- package/dist/assets/{flowDiagram-7ASYPVHJ-BDJZsUJS.js → flowDiagram-7ASYPVHJ-BnhSLZTo.js} +1 -1
- package/dist/assets/{ganttDiagram-NTVNEXSI-BYBeyULV.js → ganttDiagram-NTVNEXSI--jDGPnYB.js} +3 -3
- package/dist/assets/gitGraph-YCYPL57B-DkCOt6bF.js +133 -0
- package/dist/assets/gitGraphDiagram-NRZ2UAAF-D4p17Lzw.js +65 -0
- package/dist/assets/graph-BlwPajkw.js +1 -0
- package/dist/assets/infoDiagram-A4XQUW5V-COnYi0Q8.js +2 -0
- package/dist/assets/{journeyDiagram-G5WM74LC-BhSSQ4Od.js → journeyDiagram-G5WM74LC-CTZcqDJf.js} +1 -1
- package/dist/assets/{kanban-definition-QRCXZQQD-CZOkitn-.js → kanban-definition-QRCXZQQD-CezLYsK3.js} +1 -1
- package/dist/assets/layout-Bvwu4dCi.js +1 -0
- package/dist/assets/{linear-BRR4YPhd.js → linear-BcYBjD0V.js} +1 -1
- package/dist/assets/main-CgT_Atre.js +1210 -0
- package/dist/assets/main-kJ_DZpZU.css +10 -0
- package/dist/assets/{mindmap-definition-GWI6TPTV-CiMg1rSL.js → mindmap-definition-GWI6TPTV-NM7qIPkp.js} +1 -1
- package/dist/assets/pieDiagram-YF2LJOPJ-Ck2UBMrZ.js +30 -0
- package/dist/assets/{quadrantDiagram-OS5C2QUG-cYMjsA0L.js → quadrantDiagram-OS5C2QUG-0OKnDbeW.js} +1 -1
- package/dist/assets/{requirementDiagram-MIRIMTAZ-D8piGWQV.js → requirementDiagram-MIRIMTAZ-Nq_mStSk.js} +2 -2
- package/dist/assets/{sankeyDiagram-Y46BX6SQ-DfNocUti.js → sankeyDiagram-Y46BX6SQ-ZPgHNk3C.js} +1 -1
- package/dist/assets/{sequenceDiagram-G6AWOVSC-Xu2X2iUX.js → sequenceDiagram-G6AWOVSC-BRzYMlx2.js} +1 -1
- package/dist/assets/stateDiagram-MAYHULR4-CCNAeCLE.js +1 -0
- package/dist/assets/stateDiagram-v2-4JROLMXI-Bz1kxCVf.js +1 -0
- package/dist/assets/{timeline-definition-U7ZMHBDA-CNra78s7.js → timeline-definition-U7ZMHBDA-BSw5vmnf.js} +1 -1
- package/dist/assets/{xychartDiagram-6QU3TZC5-BSwY5sKC.js → xychartDiagram-6QU3TZC5-BuqrahPf.js} +2 -2
- package/dist/index.html +2 -2
- package/dist-server/app.module.js +4 -0
- package/dist-server/bootstrap/crewx-server.js +6 -3
- package/dist-server/common/limits/defaults.js +2 -0
- package/dist-server/domain/auth/auth.module.js +2 -1
- package/dist-server/domain/auth/guards/base-auth.guard.js +36 -14
- package/dist-server/domain/doc/doc.controller.js +21 -0
- package/dist-server/domain/doc/doc.service.js +36 -6
- package/dist-server/domain/doc/image-constants.js +11 -0
- package/dist-server/domain/events/events.module.js +31 -0
- package/dist-server/domain/events/server-conversation.plugin.js +45 -0
- package/dist-server/domain/events/workspace-event.bus.js +32 -0
- package/dist-server/domain/events/workspace-event.types.js +12 -0
- package/dist-server/domain/events/workspace-events-test.controller.js +83 -0
- package/dist-server/domain/events/workspace-events.controller.js +47 -0
- package/dist-server/domain/mcp/browser-session.store.js +50 -0
- package/dist-server/domain/mcp/chromex-blacklist.js +26 -0
- package/dist-server/domain/mcp/chromex-negotiate.controller.js +77 -0
- package/dist-server/domain/mcp/chromex-negotiate.dto.js +95 -0
- package/dist-server/domain/mcp/chromex-negotiate.service.js +249 -0
- package/dist-server/domain/mcp/mcp-auth.guard.js +40 -26
- package/dist-server/domain/mcp/mcp.constants.js +4 -1
- package/dist-server/domain/mcp/mcp.controller.js +5 -4
- package/dist-server/domain/mcp/mcp.module.js +6 -2
- package/dist-server/domain/mcp/mcp.service.js +62 -5
- package/dist-server/domain/message/dto/send-message.dto.js +48 -1
- package/dist-server/domain/message/message.controller.js +40 -1
- package/dist-server/domain/message/message.service.js +50 -5
- package/dist-server/domain/task/task.service.js +34 -13
- package/dist-server/domain/thread/thread.controller.js +16 -0
- package/dist-server/domain/thread/thread.module.js +2 -0
- package/dist-server/domain/thread/thread.service.js +80 -11
- package/dist-server/domain/token/token.controller.js +73 -0
- package/dist-server/domain/token/token.dto.js +56 -0
- package/dist-server/domain/token/token.module.js +22 -0
- package/dist-server/domain/token/token.service.js +228 -0
- package/dist-server/main.js +23 -2
- package/dist-server/modules/crewx-pool.service.js +129 -5
- package/dist-server/modules/crewx.module.js +6 -0
- package/package.json +17 -14
- package/packages/cli/dist/builtin.js +2 -0
- package/packages/cli/dist/commands/init.js +5 -5
- package/packages/cli/dist/commands/registry.js +1 -1
- package/packages/cli/package.json +2 -1
- package/dist/assets/_baseUniq-B5kIqYhv.js +0 -1
- package/dist/assets/architectureDiagram-UYN6MBPD-Bvk4uHjQ.js +0 -36
- package/dist/assets/blockDiagram-ZHA2E4KO-C9-iioqe.js +0 -121
- package/dist/assets/channel-B1S1YSXQ.js +0 -1
- package/dist/assets/classDiagram-LNE6IOMH-BMiPdBl0.js +0 -1
- package/dist/assets/classDiagram-v2-MQ7JQ4JX-BMiPdBl0.js +0 -1
- package/dist/assets/clone-DkRtovKq.js +0 -1
- package/dist/assets/dagre-4EVJKHTY-LsgK-pqb.js +0 -4
- package/dist/assets/diagram-QW4FP2JN-uvhH5Eln.js +0 -24
- package/dist/assets/gitGraph-YCYPL57B-D9Hkzkze.js +0 -133
- package/dist/assets/gitGraphDiagram-NRZ2UAAF-oWiYlrjZ.js +0 -65
- package/dist/assets/graph-D66Q_If6.js +0 -1
- package/dist/assets/infoDiagram-A4XQUW5V-aJ6i9Snw.js +0 -2
- package/dist/assets/layout-CaxkerSC.js +0 -1
- package/dist/assets/main-3IYf82MG.css +0 -10
- package/dist/assets/main-DCHJFonK.js +0 -1167
- package/dist/assets/min-uKYaLZUT.js +0 -1
- package/dist/assets/pieDiagram-YF2LJOPJ-BFJ_DAm-.js +0 -30
- package/dist/assets/stateDiagram-MAYHULR4-WmJVTQsF.js +0 -1
- package/dist/assets/stateDiagram-v2-4JROLMXI-DNAOV6w0.js +0 -1
|
@@ -15,12 +15,13 @@ const mcp_constants_js_1 = require("../mcp/mcp.constants.js");
|
|
|
15
15
|
const auth_constants_js_1 = require("./auth.constants.js");
|
|
16
16
|
const auth_controller_js_1 = require("./auth.controller.js");
|
|
17
17
|
const base_auth_guard_js_1 = require("./guards/base-auth.guard.js");
|
|
18
|
+
const token_module_js_1 = require("../token/token.module.js");
|
|
18
19
|
let AuthModule = class AuthModule {
|
|
19
20
|
};
|
|
20
21
|
exports.AuthModule = AuthModule;
|
|
21
22
|
exports.AuthModule = AuthModule = __decorate([
|
|
22
23
|
(0, common_1.Module)({
|
|
23
|
-
imports: [mcp_module_js_1.McpModule],
|
|
24
|
+
imports: [mcp_module_js_1.McpModule, token_module_js_1.TokenModule],
|
|
24
25
|
controllers: [auth_controller_js_1.AuthController],
|
|
25
26
|
providers: [
|
|
26
27
|
{
|
|
@@ -15,18 +15,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.BaseAuthGuard = void 0;
|
|
16
16
|
const common_1 = require("@nestjs/common");
|
|
17
17
|
const core_1 = require("@nestjs/core");
|
|
18
|
-
const mcp_constants_js_1 = require("../../mcp/mcp.constants.js");
|
|
19
18
|
const public_decorator_js_1 = require("../decorators/public.decorator.js");
|
|
20
19
|
const ip_utils_js_1 = require("../utils/ip.utils.js");
|
|
21
20
|
const session_store_js_1 = require("../session-store.js");
|
|
21
|
+
const token_service_js_1 = require("../../token/token.service.js");
|
|
22
22
|
let BaseAuthGuard = class BaseAuthGuard {
|
|
23
23
|
reflector;
|
|
24
|
-
|
|
25
|
-
constructor(reflector,
|
|
24
|
+
tokenService;
|
|
25
|
+
constructor(reflector, tokenService) {
|
|
26
26
|
this.reflector = reflector;
|
|
27
|
-
this.
|
|
27
|
+
this.tokenService = tokenService;
|
|
28
28
|
}
|
|
29
|
-
canActivate(context) {
|
|
29
|
+
async canActivate(context) {
|
|
30
30
|
const isPublic = this.reflector.getAllAndOverride(public_decorator_js_1.IS_PUBLIC_KEY, [
|
|
31
31
|
context.getHandler(),
|
|
32
32
|
context.getClass(),
|
|
@@ -34,33 +34,55 @@ let BaseAuthGuard = class BaseAuthGuard {
|
|
|
34
34
|
if (isPublic)
|
|
35
35
|
return true;
|
|
36
36
|
const req = context.switchToHttp().getRequest();
|
|
37
|
+
// Bearer header present → validate with TokenService (no localhost exception)
|
|
38
|
+
const rawToken = (0, ip_utils_js_1.extractBearerToken)(req);
|
|
39
|
+
if (rawToken) {
|
|
40
|
+
const claims = await this.tokenService.validateToken(rawToken);
|
|
41
|
+
if (!claims)
|
|
42
|
+
throw new common_1.UnauthorizedException('Invalid token');
|
|
43
|
+
req.tokenClaims = claims;
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
// localhost bypass (DNS rebinding defense: Host header must also be local)
|
|
47
|
+
// CSRF not required for loopback — cross-origin CSRF to localhost is blocked by the browser.
|
|
37
48
|
if (this.isLocalRequest(req))
|
|
38
49
|
return true;
|
|
39
|
-
|
|
50
|
+
// External access → session first (throws 401 if missing/invalid)
|
|
51
|
+
this.validateWebSession(req);
|
|
52
|
+
// CSRF Double Submit only on unsafe methods (state-changing)
|
|
53
|
+
if (this.requiresCsrf(req)) {
|
|
54
|
+
const csrfHeader = req.headers['x-csrf-token'];
|
|
55
|
+
const csrfCookie = req.cookies?.['crewx_csrf'];
|
|
56
|
+
if (!csrfHeader || csrfHeader !== csrfCookie) {
|
|
57
|
+
throw new common_1.ForbiddenException('CSRF token mismatch');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
40
61
|
}
|
|
41
62
|
isLocalRequest(req) {
|
|
42
63
|
const ip = req.socket?.remoteAddress;
|
|
43
64
|
if (!(0, ip_utils_js_1.isLoopback)(ip))
|
|
44
65
|
return false;
|
|
45
|
-
// DNS rebinding defense: Host header must also be local
|
|
46
66
|
const host = (req.headers['host'] ?? '').split(':')[0];
|
|
47
67
|
return host === 'localhost' || host === '127.0.0.1' || host === '[::1]';
|
|
48
68
|
}
|
|
49
|
-
|
|
69
|
+
requiresCsrf(req) {
|
|
70
|
+
const method = (req.method ?? 'GET').toUpperCase();
|
|
71
|
+
return !['GET', 'HEAD', 'OPTIONS'].includes(method);
|
|
72
|
+
}
|
|
73
|
+
validateWebSession(req) {
|
|
50
74
|
const cookieSessionId = req.cookies?.['crewx_token'];
|
|
51
|
-
const bearerToken = (0, ip_utils_js_1.extractBearerToken)(req);
|
|
52
75
|
if (cookieSessionId && (0, session_store_js_1.validateSession)(cookieSessionId)) {
|
|
53
76
|
return true;
|
|
54
77
|
}
|
|
55
|
-
if (bearerToken && (0, ip_utils_js_1.safeCompare)(bearerToken, this.mcpToken.token)) {
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
78
|
throw new common_1.UnauthorizedException('Authentication required');
|
|
59
79
|
}
|
|
60
80
|
};
|
|
61
81
|
exports.BaseAuthGuard = BaseAuthGuard;
|
|
62
82
|
exports.BaseAuthGuard = BaseAuthGuard = __decorate([
|
|
63
83
|
(0, common_1.Injectable)(),
|
|
64
|
-
__param(
|
|
65
|
-
|
|
84
|
+
__param(0, (0, common_1.Inject)(core_1.Reflector)),
|
|
85
|
+
__param(1, (0, common_1.Inject)(token_service_js_1.TokenService)),
|
|
86
|
+
__metadata("design:paramtypes", [core_1.Reflector,
|
|
87
|
+
token_service_js_1.TokenService])
|
|
66
88
|
], BaseAuthGuard);
|
|
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.DocController = void 0;
|
|
16
16
|
const common_1 = require("@nestjs/common");
|
|
17
17
|
const swagger_1 = require("@nestjs/swagger");
|
|
18
|
+
const fs_1 = require("fs");
|
|
18
19
|
const doc_service_js_1 = require("./doc.service.js");
|
|
19
20
|
const doc_dto_js_1 = require("./dto/doc.dto.js");
|
|
20
21
|
let DocController = class DocController {
|
|
@@ -42,6 +43,17 @@ let DocController = class DocController {
|
|
|
42
43
|
const data = this.docService.getImplStatus();
|
|
43
44
|
return { success: true, data };
|
|
44
45
|
}
|
|
46
|
+
// DOC.IMAGE: Serve document image file
|
|
47
|
+
serveImage(filePath, res) {
|
|
48
|
+
const { absPath, mime } = this.docService.getDocImagePath(filePath);
|
|
49
|
+
const stat = (0, fs_1.statSync)(absPath);
|
|
50
|
+
res.set({
|
|
51
|
+
'Content-Type': mime,
|
|
52
|
+
'Content-Length': stat.size,
|
|
53
|
+
'Cache-Control': 'public, max-age=3600',
|
|
54
|
+
});
|
|
55
|
+
(0, fs_1.createReadStream)(absPath).pipe(res);
|
|
56
|
+
}
|
|
45
57
|
// DOC.SAVE: Save document content
|
|
46
58
|
async saveContent(body) {
|
|
47
59
|
const data = await this.docService.saveContent(body.path, body.content, body.mtime);
|
|
@@ -80,6 +92,15 @@ __decorate([
|
|
|
80
92
|
__metadata("design:paramtypes", []),
|
|
81
93
|
__metadata("design:returntype", void 0)
|
|
82
94
|
], DocController.prototype, "getImplStatus", null);
|
|
95
|
+
__decorate([
|
|
96
|
+
(0, swagger_1.ApiOperation)({ operationId: 'DOC.IMAGE', summary: 'Serve a document image (png/jpg/jpeg/gif/webp)' }),
|
|
97
|
+
(0, common_1.Get)('image'),
|
|
98
|
+
__param(0, (0, common_1.Query)('path')),
|
|
99
|
+
__param(1, (0, common_1.Res)()),
|
|
100
|
+
__metadata("design:type", Function),
|
|
101
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
102
|
+
__metadata("design:returntype", void 0)
|
|
103
|
+
], DocController.prototype, "serveImage", null);
|
|
83
104
|
__decorate([
|
|
84
105
|
(0, swagger_1.ApiOperation)({ operationId: 'DOC.SAVE', summary: 'Save document content' }),
|
|
85
106
|
(0, common_1.Put)('content'),
|
|
@@ -14,6 +14,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
14
14
|
const doc_1 = require("@crewx/doc");
|
|
15
15
|
const knowledge_core_1 = require("@crewx/knowledge-core");
|
|
16
16
|
const workspace_context_store_js_1 = require("../../common/workspace-context.store.js");
|
|
17
|
+
const image_constants_js_1 = require("./image-constants.js");
|
|
17
18
|
// Screen ID → page directory mapping for code existence check
|
|
18
19
|
const SCREEN_CODE_PATHS = {
|
|
19
20
|
NAVUI: 'src/ui/components/layout',
|
|
@@ -94,8 +95,11 @@ let DocService = class DocService {
|
|
|
94
95
|
children.push(child);
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
|
-
else if (entry.isFile()
|
|
98
|
-
|
|
98
|
+
else if (entry.isFile()) {
|
|
99
|
+
const ext = entry.name.split('.').pop()?.toLowerCase() ?? '';
|
|
100
|
+
if (entry.name.endsWith('.md') || image_constants_js_1.IMAGE_EXTENSIONS.has(ext)) {
|
|
101
|
+
children.push(this.fileNode(entryAbs));
|
|
102
|
+
}
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
105
|
return { name, path: rel, type: 'dir', children };
|
|
@@ -301,10 +305,11 @@ let DocService = class DocService {
|
|
|
301
305
|
}
|
|
302
306
|
// ── Path validation ───────────────────────────────────────
|
|
303
307
|
/**
|
|
304
|
-
*
|
|
305
|
-
*
|
|
308
|
+
* Validates traversal and boundary only — does NOT check existence.
|
|
309
|
+
* Fix: use `rootCmp + '/'` prefix (or exact match) to reject sibling dirs
|
|
310
|
+
* like /home/u/proj-secret that share the root as a string prefix.
|
|
306
311
|
*/
|
|
307
|
-
|
|
312
|
+
validatePathBoundary(filePath) {
|
|
308
313
|
if (filePath.includes('..')) {
|
|
309
314
|
throw new common_1.BadRequestException('Path traversal is not allowed');
|
|
310
315
|
}
|
|
@@ -314,13 +319,38 @@ let DocService = class DocService {
|
|
|
314
319
|
// projectRoot may already be normalized (c:/…). Naive startsWith fails.
|
|
315
320
|
const absCmp = abs.replace(/\\/g, '/').toLowerCase();
|
|
316
321
|
const rootCmp = this.projectRoot.replace(/\\/g, '/').toLowerCase();
|
|
317
|
-
if (!absCmp.startsWith(rootCmp)) {
|
|
322
|
+
if (absCmp !== rootCmp && !absCmp.startsWith(rootCmp + '/')) {
|
|
318
323
|
throw new common_1.BadRequestException('Path is outside project root');
|
|
319
324
|
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Prevent path traversal attacks by rejecting paths containing '..'.
|
|
328
|
+
* Also ensures the resolved path stays within the project root.
|
|
329
|
+
*/
|
|
330
|
+
validatePath(filePath) {
|
|
331
|
+
this.validatePathBoundary(filePath);
|
|
332
|
+
const abs = (0, path_1.resolve)(this.projectRoot, filePath);
|
|
320
333
|
if (!(0, fs_1.existsSync)(abs)) {
|
|
321
334
|
throw new common_1.BadRequestException(`File not found: ${filePath}`);
|
|
322
335
|
}
|
|
323
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* DOC.IMAGE: Validate an image path and return its absolute filesystem path.
|
|
339
|
+
* Enforces extension whitelist before existence check to prevent info leakage.
|
|
340
|
+
* Missing images throw 404 (not 400) to match spec and prevent probing.
|
|
341
|
+
*/
|
|
342
|
+
getDocImagePath(filePath) {
|
|
343
|
+
const ext = filePath.split('.').pop()?.toLowerCase() ?? '';
|
|
344
|
+
if (!image_constants_js_1.IMAGE_EXTENSIONS.has(ext)) {
|
|
345
|
+
throw new common_1.NotFoundException('Not found');
|
|
346
|
+
}
|
|
347
|
+
this.validatePathBoundary(filePath);
|
|
348
|
+
const abs = (0, path_1.resolve)(this.projectRoot, filePath);
|
|
349
|
+
if (!(0, fs_1.existsSync)(abs)) {
|
|
350
|
+
throw new common_1.NotFoundException('Not found');
|
|
351
|
+
}
|
|
352
|
+
return { absPath: abs, mime: image_constants_js_1.EXT_MIME[ext] };
|
|
353
|
+
}
|
|
324
354
|
validateEditable(filePath) {
|
|
325
355
|
const lower = filePath.toLowerCase();
|
|
326
356
|
if (!lower.endsWith('.md')) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EXT_MIME = exports.IMAGE_EXTENSIONS = void 0;
|
|
4
|
+
exports.IMAGE_EXTENSIONS = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp']);
|
|
5
|
+
exports.EXT_MIME = {
|
|
6
|
+
png: 'image/png',
|
|
7
|
+
jpg: 'image/jpeg',
|
|
8
|
+
jpeg: 'image/jpeg',
|
|
9
|
+
gif: 'image/gif',
|
|
10
|
+
webp: 'image/webp',
|
|
11
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.EventsModule = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const workspace_events_controller_js_1 = require("./workspace-events.controller.js");
|
|
12
|
+
const workspace_events_test_controller_js_1 = require("./workspace-events-test.controller.js");
|
|
13
|
+
const workspace_event_bus_js_1 = require("./workspace-event.bus.js");
|
|
14
|
+
/**
|
|
15
|
+
* EventsModule — workspace SSE event bus + streaming endpoint.
|
|
16
|
+
*
|
|
17
|
+
* WorkspaceEventBus is exported so CrewxModule can import it and wire
|
|
18
|
+
* SDK event subscriptions without a circular dependency:
|
|
19
|
+
* EventsModule → (no CrewxModule dependency)
|
|
20
|
+
* CrewxModule → imports EventsModule for WorkspaceEventBus
|
|
21
|
+
*/
|
|
22
|
+
let EventsModule = class EventsModule {
|
|
23
|
+
};
|
|
24
|
+
exports.EventsModule = EventsModule;
|
|
25
|
+
exports.EventsModule = EventsModule = __decorate([
|
|
26
|
+
(0, common_1.Module)({
|
|
27
|
+
controllers: [workspace_events_controller_js_1.WorkspaceEventsController, workspace_events_test_controller_js_1.WorkspaceEventsTestController],
|
|
28
|
+
providers: [workspace_event_bus_js_1.WorkspaceEventBus],
|
|
29
|
+
exports: [workspace_event_bus_js_1.WorkspaceEventBus],
|
|
30
|
+
})
|
|
31
|
+
], EventsModule);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ServerConversationPlugin = void 0;
|
|
4
|
+
const plugins_1 = require("@crewx/sdk/plugins");
|
|
5
|
+
/**
|
|
6
|
+
* ServerConversationPlugin — extends ConversationPlugin to emit workspace SSE events
|
|
7
|
+
* immediately after conversation persistence (afterUserMessage / afterAssistantMessage hooks).
|
|
8
|
+
*
|
|
9
|
+
* wsId strategy: captured from crewx.workspaceId in attach() (option a).
|
|
10
|
+
* TaskEndEvent carries no workspaceId, so we must capture it at attach time.
|
|
11
|
+
* Each plugin instance is bound to exactly one Crewx instance, so wsId is stable.
|
|
12
|
+
*
|
|
13
|
+
* Phase 1 note: messageId from hook = threadId (thread PK, not per-message row).
|
|
14
|
+
* SSE payload uses taskId = e.traceId for per-task identity. Per-message IDs are Phase 2.
|
|
15
|
+
*/
|
|
16
|
+
class ServerConversationPlugin extends plugins_1.ConversationPlugin {
|
|
17
|
+
bus;
|
|
18
|
+
wsId = null;
|
|
19
|
+
constructor(bus) {
|
|
20
|
+
super();
|
|
21
|
+
this.bus = bus;
|
|
22
|
+
}
|
|
23
|
+
attach(crewx) {
|
|
24
|
+
this.wsId = crewx.workspaceId;
|
|
25
|
+
super.attach(crewx);
|
|
26
|
+
}
|
|
27
|
+
async afterUserMessage(threadId, _messageId, created, e) {
|
|
28
|
+
const wsId = e.workspaceId;
|
|
29
|
+
if (!wsId)
|
|
30
|
+
return;
|
|
31
|
+
if (created) {
|
|
32
|
+
this.bus.emit(wsId, { type: 'thread.created', data: { threadId } });
|
|
33
|
+
}
|
|
34
|
+
this.bus.emit(wsId, { type: 'message.created', data: { threadId, taskId: e.traceId } });
|
|
35
|
+
this.bus.emit(wsId, { type: 'thread.updated', data: { threadId, updatedAt: e.timestamp.toISOString() } });
|
|
36
|
+
}
|
|
37
|
+
async afterAssistantMessage(threadId, _messageId, e) {
|
|
38
|
+
const wsId = this.wsId;
|
|
39
|
+
if (!wsId)
|
|
40
|
+
return;
|
|
41
|
+
this.bus.emit(wsId, { type: 'message.created', data: { threadId, taskId: e.traceId } });
|
|
42
|
+
this.bus.emit(wsId, { type: 'thread.updated', data: { threadId, updatedAt: e.timestamp.toISOString() } });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.ServerConversationPlugin = ServerConversationPlugin;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.WorkspaceEventBus = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const rxjs_1 = require("rxjs");
|
|
12
|
+
const operators_1 = require("rxjs/operators");
|
|
13
|
+
/**
|
|
14
|
+
* WorkspaceEventBus — singleton RxJS Subject that fan-outs workspace events.
|
|
15
|
+
*
|
|
16
|
+
* All workspace SSE events flow through this single Subject.
|
|
17
|
+
* Subscribers use for(wsId) to receive only their workspace's events.
|
|
18
|
+
*/
|
|
19
|
+
let WorkspaceEventBus = class WorkspaceEventBus {
|
|
20
|
+
subject = new rxjs_1.Subject();
|
|
21
|
+
emit(wsId, event) {
|
|
22
|
+
this.subject.next({ wsId, event });
|
|
23
|
+
}
|
|
24
|
+
/** Returns an Observable filtered to a single workspace's events. */
|
|
25
|
+
for(wsId) {
|
|
26
|
+
return this.subject.pipe((0, operators_1.filter)((envelope) => envelope.wsId === wsId), (0, operators_1.map)((envelope) => envelope.event));
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
exports.WorkspaceEventBus = WorkspaceEventBus;
|
|
30
|
+
exports.WorkspaceEventBus = WorkspaceEventBus = __decorate([
|
|
31
|
+
(0, common_1.Injectable)()
|
|
32
|
+
], WorkspaceEventBus);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WORKSPACE_EVENT_TYPES = void 0;
|
|
4
|
+
/** SSOT: all workspace SSE event types. Value = SSE event name = addEventListener key. */
|
|
5
|
+
exports.WORKSPACE_EVENT_TYPES = [
|
|
6
|
+
'task.created',
|
|
7
|
+
'task.updated',
|
|
8
|
+
'task.logged',
|
|
9
|
+
'thread.created',
|
|
10
|
+
'thread.updated',
|
|
11
|
+
'message.created',
|
|
12
|
+
];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.WorkspaceEventsTestController = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const class_validator_1 = require("class-validator");
|
|
18
|
+
const workspace_decorator_js_1 = require("../../common/decorators/workspace.decorator.js");
|
|
19
|
+
const workspace_event_bus_js_1 = require("./workspace-event.bus.js");
|
|
20
|
+
const workspace_event_types_js_1 = require("./workspace-event.types.js");
|
|
21
|
+
function makeDefaultData(type) {
|
|
22
|
+
const now = new Date().toISOString();
|
|
23
|
+
const taskId = `tsk_test_${Date.now()}`;
|
|
24
|
+
const threadId = `thr_test_${Date.now()}`;
|
|
25
|
+
switch (type) {
|
|
26
|
+
case 'task.created':
|
|
27
|
+
return { taskId, threadId, agent: 'test_agent', status: 'running', updatedAt: now };
|
|
28
|
+
case 'task.updated':
|
|
29
|
+
return { taskId, threadId, agent: 'test_agent', status: 'success', costUsd: 0.001, updatedAt: now };
|
|
30
|
+
case 'task.logged':
|
|
31
|
+
return { taskId, threadId };
|
|
32
|
+
case 'thread.created':
|
|
33
|
+
return { threadId };
|
|
34
|
+
case 'thread.updated':
|
|
35
|
+
return { threadId, updatedAt: now };
|
|
36
|
+
case 'message.created':
|
|
37
|
+
return { threadId, taskId };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
class TestEmitBody {
|
|
41
|
+
type;
|
|
42
|
+
data;
|
|
43
|
+
}
|
|
44
|
+
__decorate([
|
|
45
|
+
(0, class_validator_1.IsString)(),
|
|
46
|
+
(0, class_validator_1.IsIn)(workspace_event_types_js_1.WORKSPACE_EVENT_TYPES),
|
|
47
|
+
__metadata("design:type", String)
|
|
48
|
+
], TestEmitBody.prototype, "type", void 0);
|
|
49
|
+
__decorate([
|
|
50
|
+
(0, class_validator_1.IsOptional)(),
|
|
51
|
+
(0, class_validator_1.IsObject)(),
|
|
52
|
+
__metadata("design:type", Object)
|
|
53
|
+
], TestEmitBody.prototype, "data", void 0);
|
|
54
|
+
/** POST /api/v1/ws/:slug/test/emit — manually push a test event into the workspace SSE bus. Dev only. */
|
|
55
|
+
let WorkspaceEventsTestController = class WorkspaceEventsTestController {
|
|
56
|
+
bus;
|
|
57
|
+
constructor(bus) {
|
|
58
|
+
this.bus = bus;
|
|
59
|
+
}
|
|
60
|
+
emit(body, ws) {
|
|
61
|
+
if (!workspace_event_types_js_1.WORKSPACE_EVENT_TYPES.includes(body.type)) {
|
|
62
|
+
return { success: false, error: `Unknown event type: ${body.type}` };
|
|
63
|
+
}
|
|
64
|
+
const data = body.data ?? makeDefaultData(body.type);
|
|
65
|
+
const event = { type: body.type, data };
|
|
66
|
+
this.bus.emit(ws.id, event);
|
|
67
|
+
return { success: true, wsId: ws.id, event };
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
exports.WorkspaceEventsTestController = WorkspaceEventsTestController;
|
|
71
|
+
__decorate([
|
|
72
|
+
(0, common_1.Post)('emit'),
|
|
73
|
+
(0, common_1.HttpCode)(200),
|
|
74
|
+
__param(0, (0, common_1.Body)()),
|
|
75
|
+
__param(1, (0, workspace_decorator_js_1.Workspace)()),
|
|
76
|
+
__metadata("design:type", Function),
|
|
77
|
+
__metadata("design:paramtypes", [TestEmitBody, Object]),
|
|
78
|
+
__metadata("design:returntype", void 0)
|
|
79
|
+
], WorkspaceEventsTestController.prototype, "emit", null);
|
|
80
|
+
exports.WorkspaceEventsTestController = WorkspaceEventsTestController = __decorate([
|
|
81
|
+
(0, common_1.Controller)('ws/:slug/test'),
|
|
82
|
+
__metadata("design:paramtypes", [workspace_event_bus_js_1.WorkspaceEventBus])
|
|
83
|
+
], WorkspaceEventsTestController);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.WorkspaceEventsController = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const throttler_1 = require("@nestjs/throttler");
|
|
18
|
+
const rxjs_1 = require("rxjs");
|
|
19
|
+
const operators_1 = require("rxjs/operators");
|
|
20
|
+
const workspace_event_bus_js_1 = require("./workspace-event.bus.js");
|
|
21
|
+
const workspace_decorator_js_1 = require("../../common/decorators/workspace.decorator.js");
|
|
22
|
+
const KEEPALIVE_INTERVAL_MS = 15_000;
|
|
23
|
+
let WorkspaceEventsController = class WorkspaceEventsController {
|
|
24
|
+
bus;
|
|
25
|
+
constructor(bus) {
|
|
26
|
+
this.bus = bus;
|
|
27
|
+
}
|
|
28
|
+
/** GET /api/v1/ws/:slug/events — workspace-scoped SSE stream. */
|
|
29
|
+
stream(ws) {
|
|
30
|
+
const events$ = this.bus.for(ws.id).pipe((0, operators_1.map)((event) => ({ type: event.type, data: event.data })));
|
|
31
|
+
const ping$ = (0, rxjs_1.interval)(KEEPALIVE_INTERVAL_MS).pipe((0, operators_1.map)(() => ({ type: 'ping', data: {} })));
|
|
32
|
+
return (0, rxjs_1.merge)(events$, ping$);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
exports.WorkspaceEventsController = WorkspaceEventsController;
|
|
36
|
+
__decorate([
|
|
37
|
+
(0, throttler_1.SkipThrottle)(),
|
|
38
|
+
(0, common_1.Sse)(),
|
|
39
|
+
__param(0, (0, workspace_decorator_js_1.Workspace)()),
|
|
40
|
+
__metadata("design:type", Function),
|
|
41
|
+
__metadata("design:paramtypes", [Object]),
|
|
42
|
+
__metadata("design:returntype", rxjs_1.Observable)
|
|
43
|
+
], WorkspaceEventsController.prototype, "stream", null);
|
|
44
|
+
exports.WorkspaceEventsController = WorkspaceEventsController = __decorate([
|
|
45
|
+
(0, common_1.Controller)('ws/:slug/events'),
|
|
46
|
+
__metadata("design:paramtypes", [workspace_event_bus_js_1.WorkspaceEventBus])
|
|
47
|
+
], WorkspaceEventsController);
|
|
@@ -23,6 +23,7 @@ let BrowserSessionStore = BrowserSessionStore_1 = class BrowserSessionStore {
|
|
|
23
23
|
logger = new common_1.Logger(BrowserSessionStore_1.name);
|
|
24
24
|
sessions = new Map();
|
|
25
25
|
sweepTimer;
|
|
26
|
+
sseDisconnectListeners = new Map();
|
|
26
27
|
constructor() {
|
|
27
28
|
this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
|
|
28
29
|
this.sweepTimer.unref();
|
|
@@ -33,6 +34,7 @@ let BrowserSessionStore = BrowserSessionStore_1 = class BrowserSessionStore {
|
|
|
33
34
|
this.destroySession(session);
|
|
34
35
|
}
|
|
35
36
|
this.sessions.clear();
|
|
37
|
+
this.sseDisconnectListeners.clear();
|
|
36
38
|
}
|
|
37
39
|
create(clientType) {
|
|
38
40
|
const id = SESSION_PREFIX + (0, crypto_1.randomBytes)(SESSION_ID_BYTES).toString('hex');
|
|
@@ -100,6 +102,7 @@ let BrowserSessionStore = BrowserSessionStore_1 = class BrowserSessionStore {
|
|
|
100
102
|
this.logger.debug(`SSE disconnected for session: ${sessionId}`);
|
|
101
103
|
this.cleanupSse(session);
|
|
102
104
|
this.rejectAllPending(session, new Error('SSE connection closed'));
|
|
105
|
+
this.fireSseDisconnectListeners(sessionId);
|
|
103
106
|
});
|
|
104
107
|
return true;
|
|
105
108
|
}
|
|
@@ -117,6 +120,41 @@ let BrowserSessionStore = BrowserSessionStore_1 = class BrowserSessionStore {
|
|
|
117
120
|
this.logger.debug(`Tools registered (${mode}) for session ${sessionId}: ${tools.map((t) => t.name).join(', ')}`);
|
|
118
121
|
return true;
|
|
119
122
|
}
|
|
123
|
+
/** Returns the most recently created browser session with an active SSE stream. */
|
|
124
|
+
getLatestBrowserSession() {
|
|
125
|
+
let latest;
|
|
126
|
+
for (const session of this.sessions.values()) {
|
|
127
|
+
if (session.clientType === 'browser' &&
|
|
128
|
+
session.sseResponse &&
|
|
129
|
+
!session.sseResponse.writableEnded) {
|
|
130
|
+
if (!latest || session.createdAt > latest.createdAt) {
|
|
131
|
+
latest = session;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return latest;
|
|
136
|
+
}
|
|
137
|
+
/** Push a fire-and-forget JSON-RPC notification to a session's SSE stream. */
|
|
138
|
+
pushNotification(sessionId, payload) {
|
|
139
|
+
const session = this.sessions.get(sessionId);
|
|
140
|
+
if (!session?.sseResponse || session.sseResponse.writableEnded)
|
|
141
|
+
return false;
|
|
142
|
+
session.sseResponse.write(`data: ${JSON.stringify(payload)}\n\n`);
|
|
143
|
+
session.lastActivity = Date.now();
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
/** Register a callback that fires when the SSE connection for sessionId closes. Returns an unregister fn. */
|
|
147
|
+
onSseDisconnect(sessionId, cb) {
|
|
148
|
+
let listeners = this.sseDisconnectListeners.get(sessionId);
|
|
149
|
+
if (!listeners) {
|
|
150
|
+
listeners = new Set();
|
|
151
|
+
this.sseDisconnectListeners.set(sessionId, listeners);
|
|
152
|
+
}
|
|
153
|
+
listeners.add(cb);
|
|
154
|
+
return () => {
|
|
155
|
+
this.sseDisconnectListeners.get(sessionId)?.delete(cb);
|
|
156
|
+
};
|
|
157
|
+
}
|
|
120
158
|
getRegisteredTools(sessionId) {
|
|
121
159
|
const session = this.sessions.get(sessionId);
|
|
122
160
|
if (!session)
|
|
@@ -204,6 +242,18 @@ let BrowserSessionStore = BrowserSessionStore_1 = class BrowserSessionStore {
|
|
|
204
242
|
pending.reject(error);
|
|
205
243
|
return true;
|
|
206
244
|
}
|
|
245
|
+
fireSseDisconnectListeners(sessionId) {
|
|
246
|
+
const listeners = this.sseDisconnectListeners.get(sessionId);
|
|
247
|
+
if (!listeners)
|
|
248
|
+
return;
|
|
249
|
+
for (const cb of listeners) {
|
|
250
|
+
try {
|
|
251
|
+
cb();
|
|
252
|
+
}
|
|
253
|
+
catch { }
|
|
254
|
+
}
|
|
255
|
+
this.sseDisconnectListeners.delete(sessionId);
|
|
256
|
+
}
|
|
207
257
|
cleanupSse(session) {
|
|
208
258
|
if (session.keepaliveTimer) {
|
|
209
259
|
clearInterval(session.keepaliveTimer);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CHROMEX_BLACKLIST_DOMAINS = void 0;
|
|
4
|
+
exports.isBlacklisted = isBlacklisted;
|
|
5
|
+
exports.CHROMEX_BLACKLIST_DOMAINS = [
|
|
6
|
+
// 금융
|
|
7
|
+
'kbanknow.com', 'mybank.wooribank.com', 'banking.shinhan.com',
|
|
8
|
+
'mybanking.kbstar.com', 'ibs.kbstar.com', 'ib.hana.co.kr',
|
|
9
|
+
// 이메일
|
|
10
|
+
'mail.google.com', 'outlook.live.com', 'mail.naver.com',
|
|
11
|
+
// 인증
|
|
12
|
+
'accounts.google.com', 'nid.naver.com', 'auth.kakao.com',
|
|
13
|
+
];
|
|
14
|
+
function isBlacklisted(url) {
|
|
15
|
+
try {
|
|
16
|
+
let hostname = new URL(url).hostname.toLowerCase();
|
|
17
|
+
if (hostname.endsWith('.'))
|
|
18
|
+
hostname = hostname.slice(0, -1);
|
|
19
|
+
// normalize via URL constructor to handle IDN/punycode bypass attempts
|
|
20
|
+
hostname = new URL(`https://${hostname}`).hostname;
|
|
21
|
+
return exports.CHROMEX_BLACKLIST_DOMAINS.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return true; // fail-close on parse error
|
|
25
|
+
}
|
|
26
|
+
}
|