@seleniumbox/sbox-mcp 0.2.0
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/PUBLISHING.md +115 -0
- package/README.md +134 -0
- package/dist/adapters/auth.adapter.d.ts +46 -0
- package/dist/adapters/auth.adapter.js +54 -0
- package/dist/adapters/browser.adapter.d.ts +43 -0
- package/dist/adapters/browser.adapter.js +39 -0
- package/dist/adapters/device.adapter.d.ts +60 -0
- package/dist/adapters/device.adapter.js +40 -0
- package/dist/adapters/diagnostics.adapter.d.ts +73 -0
- package/dist/adapters/diagnostics.adapter.js +89 -0
- package/dist/adapters/index.d.ts +16 -0
- package/dist/adapters/index.js +20 -0
- package/dist/adapters/project.adapter.d.ts +38 -0
- package/dist/adapters/project.adapter.js +39 -0
- package/dist/adapters/sbox-api.client.d.ts +31 -0
- package/dist/adapters/sbox-api.client.js +104 -0
- package/dist/adapters/session.adapter.d.ts +77 -0
- package/dist/adapters/session.adapter.js +95 -0
- package/dist/adapters/stats.adapter.d.ts +72 -0
- package/dist/adapters/stats.adapter.js +108 -0
- package/dist/adapters/upload.adapter.d.ts +16 -0
- package/dist/adapters/upload.adapter.js +25 -0
- package/dist/adapters/user.adapter.d.ts +24 -0
- package/dist/adapters/user.adapter.js +25 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.js +16 -0
- package/dist/config/env.d.ts +21 -0
- package/dist/config/env.js +55 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +8 -0
- package/dist/controllers/analytics.controller.d.ts +10 -0
- package/dist/controllers/analytics.controller.js +127 -0
- package/dist/controllers/auth-device.controller.d.ts +7 -0
- package/dist/controllers/auth-device.controller.js +60 -0
- package/dist/controllers/auth.controller.d.ts +5 -0
- package/dist/controllers/auth.controller.js +20 -0
- package/dist/controllers/browser.controller.d.ts +5 -0
- package/dist/controllers/browser.controller.js +23 -0
- package/dist/controllers/device.controller.d.ts +5 -0
- package/dist/controllers/device.controller.js +23 -0
- package/dist/controllers/index.d.ts +6 -0
- package/dist/controllers/index.js +28 -0
- package/dist/controllers/project-stats.controller.d.ts +5 -0
- package/dist/controllers/project-stats.controller.js +29 -0
- package/dist/controllers/session.controller.d.ts +9 -0
- package/dist/controllers/session.controller.js +120 -0
- package/dist/controllers/upload.controller.d.ts +5 -0
- package/dist/controllers/upload.controller.js +44 -0
- package/dist/controllers/user.controller.d.ts +5 -0
- package/dist/controllers/user.controller.js +29 -0
- package/dist/device-flow.store.d.ts +7 -0
- package/dist/device-flow.store.js +23 -0
- package/dist/dto/auth.dto.d.ts +26 -0
- package/dist/dto/auth.dto.js +9 -0
- package/dist/dto/browser.dto.d.ts +13 -0
- package/dist/dto/browser.dto.js +2 -0
- package/dist/dto/device.dto.d.ts +15 -0
- package/dist/dto/device.dto.js +2 -0
- package/dist/dto/index.d.ts +6 -0
- package/dist/dto/index.js +22 -0
- package/dist/dto/project-stats.dto.d.ts +8 -0
- package/dist/dto/project-stats.dto.js +2 -0
- package/dist/dto/session.dto.d.ts +50 -0
- package/dist/dto/session.dto.js +9 -0
- package/dist/dto/user.dto.d.ts +6 -0
- package/dist/dto/user.dto.js +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +34 -0
- package/dist/mcp/tools/auth-tools.d.ts +5 -0
- package/dist/mcp/tools/auth-tools.js +132 -0
- package/dist/mcp/tools/helpers.d.ts +26 -0
- package/dist/mcp/tools/helpers.js +53 -0
- package/dist/mcp/tools/index.d.ts +5 -0
- package/dist/mcp/tools/index.js +12 -0
- package/dist/mcp/tools/rest-tools.d.ts +5 -0
- package/dist/mcp/tools/rest-tools.js +540 -0
- package/dist/mcp-server.d.ts +6 -0
- package/dist/mcp-server.js +28 -0
- package/dist/middleware/auth.middleware.d.ts +10 -0
- package/dist/middleware/auth.middleware.js +23 -0
- package/dist/middleware/debug-log.middleware.d.ts +6 -0
- package/dist/middleware/debug-log.middleware.js +84 -0
- package/dist/middleware/error.middleware.d.ts +8 -0
- package/dist/middleware/error.middleware.js +24 -0
- package/dist/middleware/validate.middleware.d.ts +7 -0
- package/dist/middleware/validate.middleware.js +42 -0
- package/dist/routes/analytics.routes.d.ts +1 -0
- package/dist/routes/analytics.routes.js +15 -0
- package/dist/routes/auth.routes.d.ts +1 -0
- package/dist/routes/auth.routes.js +15 -0
- package/dist/routes/browser.routes.d.ts +1 -0
- package/dist/routes/browser.routes.js +11 -0
- package/dist/routes/device.routes.d.ts +1 -0
- package/dist/routes/device.routes.js +11 -0
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.js +27 -0
- package/dist/routes/mcp.routes.d.ts +6 -0
- package/dist/routes/mcp.routes.js +70 -0
- package/dist/routes/project-stats.routes.d.ts +1 -0
- package/dist/routes/project-stats.routes.js +11 -0
- package/dist/routes/session.routes.d.ts +1 -0
- package/dist/routes/session.routes.js +23 -0
- package/dist/routes/upload.routes.d.ts +1 -0
- package/dist/routes/upload.routes.js +20 -0
- package/dist/routes/user.routes.d.ts +1 -0
- package/dist/routes/user.routes.js +11 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +17 -0
- package/dist/services/analytics.service.d.ts +88 -0
- package/dist/services/analytics.service.js +98 -0
- package/dist/services/auth.service.d.ts +27 -0
- package/dist/services/auth.service.js +68 -0
- package/dist/services/browser.service.d.ts +25 -0
- package/dist/services/browser.service.js +43 -0
- package/dist/services/device.service.d.ts +18 -0
- package/dist/services/device.service.js +58 -0
- package/dist/services/diagnostics.service.d.ts +87 -0
- package/dist/services/diagnostics.service.js +92 -0
- package/dist/services/enrichment.service.d.ts +9 -0
- package/dist/services/enrichment.service.js +112 -0
- package/dist/services/index.d.ts +20 -0
- package/dist/services/index.js +31 -0
- package/dist/services/project.service.d.ts +22 -0
- package/dist/services/project.service.js +31 -0
- package/dist/services/session.service.d.ts +62 -0
- package/dist/services/session.service.js +104 -0
- package/dist/services/sessions-per-project.service.d.ts +18 -0
- package/dist/services/sessions-per-project.service.js +57 -0
- package/dist/services/upload.service.d.ts +20 -0
- package/dist/services/upload.service.js +29 -0
- package/dist/services/user.service.d.ts +17 -0
- package/dist/services/user.service.js +39 -0
- package/dist/token-cache.d.ts +12 -0
- package/dist/token-cache.js +37 -0
- package/dist/utils/logger.d.ts +10 -0
- package/dist/utils/logger.js +27 -0
- package/dist/utils/open-browser.d.ts +6 -0
- package/dist/utils/open-browser.js +23 -0
- package/dist/utils/time-range.d.ts +11 -0
- package/dist/utils/time-range.js +16 -0
- package/package.json +42 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Adapter for SBOX user APIs. Calls /users and uses session data for active counts.
|
|
4
|
+
* No business logic; only HTTP and response typing.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.userAdapter = void 0;
|
|
8
|
+
const sbox_api_client_1 = require("./sbox-api.client");
|
|
9
|
+
async function searchUsers(params, token) {
|
|
10
|
+
const q = new URLSearchParams();
|
|
11
|
+
q.set("realmId", params.realmId);
|
|
12
|
+
if (params.filter)
|
|
13
|
+
q.set("filter", params.filter);
|
|
14
|
+
if (params.size != null)
|
|
15
|
+
q.set("size", String(params.size));
|
|
16
|
+
const path = `users?${q.toString()}`;
|
|
17
|
+
const { data, status } = await (0, sbox_api_client_1.get)(path, token);
|
|
18
|
+
if (status !== 200) {
|
|
19
|
+
const msg = (0, sbox_api_client_1.isSboxError)(data) ? data.message : "Failed to search users";
|
|
20
|
+
return { status, users: [], error: msg };
|
|
21
|
+
}
|
|
22
|
+
const list = Array.isArray(data) ? data : [];
|
|
23
|
+
return { status, users: list };
|
|
24
|
+
}
|
|
25
|
+
exports.userAdapter = { searchUsers };
|
package/dist/app.d.ts
ADDED
package/dist/app.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const express_1 = __importDefault(require("express"));
|
|
7
|
+
const routes_1 = require("./routes");
|
|
8
|
+
const error_middleware_1 = require("./middleware/error.middleware");
|
|
9
|
+
const debug_log_middleware_1 = require("./middleware/debug-log.middleware");
|
|
10
|
+
const app = (0, express_1.default)();
|
|
11
|
+
app.use(express_1.default.json());
|
|
12
|
+
app.use(express_1.default.urlencoded({ extended: true }));
|
|
13
|
+
app.use(debug_log_middleware_1.debugLogMiddleware);
|
|
14
|
+
app.use("/", routes_1.routes);
|
|
15
|
+
app.use(error_middleware_1.errorMiddleware);
|
|
16
|
+
exports.default = app;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment and configuration values for MCP module.
|
|
3
|
+
* Reads from process.env; no dependency on SBOX backend config files.
|
|
4
|
+
*/
|
|
5
|
+
export declare const config: {
|
|
6
|
+
readonly port: number;
|
|
7
|
+
readonly nodeEnv: string;
|
|
8
|
+
readonly sboxApiBaseUrl: string;
|
|
9
|
+
readonly sboxApiPrefix: "/e34/api";
|
|
10
|
+
readonly jwtHeader: "Authorization";
|
|
11
|
+
readonly requestTimeoutMs: number;
|
|
12
|
+
readonly mcpVersion: string;
|
|
13
|
+
readonly mcpServerHeaderName: "sbox-mcp-server";
|
|
14
|
+
/** When true, log each request and response (for troubleshooting). Default off. Set MCP_DEBUG=1 or DEBUG=1 */
|
|
15
|
+
readonly debug: boolean;
|
|
16
|
+
};
|
|
17
|
+
/** SBOX UI login page URL used for API/internal use (e.g. redirects). */
|
|
18
|
+
export declare function getSboxLoginPageUrl(): string;
|
|
19
|
+
/** Login page URL to return to the user (works in their browser). Same as hub base when running locally. */
|
|
20
|
+
export declare function getPublicLoginPageUrl(): string;
|
|
21
|
+
export declare function getSboxApiUrl(path: string): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Environment and configuration values for MCP module.
|
|
4
|
+
* Reads from process.env; no dependency on SBOX backend config files.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.config = void 0;
|
|
8
|
+
exports.getSboxLoginPageUrl = getSboxLoginPageUrl;
|
|
9
|
+
exports.getPublicLoginPageUrl = getPublicLoginPageUrl;
|
|
10
|
+
exports.getSboxApiUrl = getSboxApiUrl;
|
|
11
|
+
const fs_1 = require("fs");
|
|
12
|
+
const path_1 = require("path");
|
|
13
|
+
function getMcpVersion() {
|
|
14
|
+
if (process.env.SBOX_MCP_VERSION)
|
|
15
|
+
return process.env.SBOX_MCP_VERSION;
|
|
16
|
+
try {
|
|
17
|
+
const pkgPath = (0, path_1.join)(__dirname, "../../package.json");
|
|
18
|
+
const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, "utf-8"));
|
|
19
|
+
return pkg.version ?? "1.0.0";
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return "1.0.0";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function isDebugEnabled() {
|
|
26
|
+
const v = process.env.MCP_DEBUG ?? process.env.DEBUG ?? "";
|
|
27
|
+
return v === "1" || v === "true" || v.toLowerCase() === "yes";
|
|
28
|
+
}
|
|
29
|
+
/** Base URL of the SBOX hub (SBOX_API or SBOX_API_BASE_URL). No trailing slash. */
|
|
30
|
+
const sboxApiBaseUrl = (process.env.SBOX_API ?? process.env.SBOX_API_BASE_URL ?? "http://localhost:4444").replace(/\/$/, "");
|
|
31
|
+
exports.config = {
|
|
32
|
+
port: parseInt(process.env.PORT ?? process.env.MCP_PORT ?? "3344", 10),
|
|
33
|
+
nodeEnv: process.env.NODE_ENV ?? "development",
|
|
34
|
+
sboxApiBaseUrl,
|
|
35
|
+
sboxApiPrefix: "/e34/api",
|
|
36
|
+
jwtHeader: "Authorization",
|
|
37
|
+
requestTimeoutMs: parseInt(process.env.MCP_REQUEST_TIMEOUT_MS ?? "30000", 10),
|
|
38
|
+
mcpVersion: getMcpVersion(),
|
|
39
|
+
mcpServerHeaderName: "sbox-mcp-server",
|
|
40
|
+
/** When true, log each request and response (for troubleshooting). Default off. Set MCP_DEBUG=1 or DEBUG=1 */
|
|
41
|
+
debug: isDebugEnabled(),
|
|
42
|
+
};
|
|
43
|
+
/** SBOX UI login page URL used for API/internal use (e.g. redirects). */
|
|
44
|
+
function getSboxLoginPageUrl() {
|
|
45
|
+
return `${exports.config.sboxApiBaseUrl}/ui/login`;
|
|
46
|
+
}
|
|
47
|
+
/** Login page URL to return to the user (works in their browser). Same as hub base when running locally. */
|
|
48
|
+
function getPublicLoginPageUrl() {
|
|
49
|
+
return `${exports.config.sboxApiBaseUrl}/ui/login`;
|
|
50
|
+
}
|
|
51
|
+
function getSboxApiUrl(path) {
|
|
52
|
+
const base = exports.config.sboxApiBaseUrl + exports.config.sboxApiPrefix;
|
|
53
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
54
|
+
return base + p;
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { config, getSboxApiUrl, getSboxLoginPageUrl, getPublicLoginPageUrl } from "./env";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPublicLoginPageUrl = exports.getSboxLoginPageUrl = exports.getSboxApiUrl = exports.config = void 0;
|
|
4
|
+
var env_1 = require("./env");
|
|
5
|
+
Object.defineProperty(exports, "config", { enumerable: true, get: function () { return env_1.config; } });
|
|
6
|
+
Object.defineProperty(exports, "getSboxApiUrl", { enumerable: true, get: function () { return env_1.getSboxApiUrl; } });
|
|
7
|
+
Object.defineProperty(exports, "getSboxLoginPageUrl", { enumerable: true, get: function () { return env_1.getSboxLoginPageUrl; } });
|
|
8
|
+
Object.defineProperty(exports, "getPublicLoginPageUrl", { enumerable: true, get: function () { return env_1.getPublicLoginPageUrl; } });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics controller: sessions per browser, by build, failed count, most active project/user.
|
|
3
|
+
* Query: value (number), unit (hour|minute|day). Requires both.
|
|
4
|
+
*/
|
|
5
|
+
import { Request, Response } from "express";
|
|
6
|
+
export declare function getSessionsPerBrowser(req: Request, res: Response): void;
|
|
7
|
+
export declare function getSessionsByBuild(req: Request, res: Response): void;
|
|
8
|
+
export declare function getFailedCount(req: Request, res: Response): void;
|
|
9
|
+
export declare function getMostActiveProject(req: Request, res: Response): void;
|
|
10
|
+
export declare function getMostActiveUser(req: Request, res: Response): void;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Analytics controller: sessions per browser, by build, failed count, most active project/user.
|
|
4
|
+
* Query: value (number), unit (hour|minute|day). Requires both.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.getSessionsPerBrowser = getSessionsPerBrowser;
|
|
8
|
+
exports.getSessionsByBuild = getSessionsByBuild;
|
|
9
|
+
exports.getFailedCount = getFailedCount;
|
|
10
|
+
exports.getMostActiveProject = getMostActiveProject;
|
|
11
|
+
exports.getMostActiveUser = getMostActiveUser;
|
|
12
|
+
const zod_1 = require("zod");
|
|
13
|
+
const services_1 = require("../services");
|
|
14
|
+
const time_range_1 = require("../utils/time-range");
|
|
15
|
+
const QuerySchema = zod_1.z.object({
|
|
16
|
+
value: zod_1.z.string().transform((s) => parseInt(s, 10)),
|
|
17
|
+
unit: zod_1.z.enum(["hour", "minute", "day"]),
|
|
18
|
+
});
|
|
19
|
+
function token(req) {
|
|
20
|
+
return req.token;
|
|
21
|
+
}
|
|
22
|
+
function parseTime(req) {
|
|
23
|
+
const parsed = QuerySchema.safeParse(req.query);
|
|
24
|
+
if (!parsed.success || isNaN(parsed.data.value) || parsed.data.value < 1)
|
|
25
|
+
return null;
|
|
26
|
+
return { value: parsed.data.value, unit: parsed.data.unit };
|
|
27
|
+
}
|
|
28
|
+
function getSessionsPerBrowser(req, res) {
|
|
29
|
+
const t = token(req);
|
|
30
|
+
if (!t) {
|
|
31
|
+
res.status(401).json({ success: false, message: "Authorization required", code: 401 });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const time = parseTime(req);
|
|
35
|
+
if (!time) {
|
|
36
|
+
res.status(400).json({ success: false, message: "Query required: value (number) and unit (hour|minute|day)", code: 400 });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
services_1.analyticsService.sessionsPerBrowser(t, (0, time_range_1.parseTimeToRange)(time.value, time.unit)).then((out) => {
|
|
40
|
+
if (out.success)
|
|
41
|
+
res.status(200).json({ success: true, data: out.data });
|
|
42
|
+
else
|
|
43
|
+
res.status(out.status).json({ success: false, message: out.message, code: out.status });
|
|
44
|
+
}).catch((err) => {
|
|
45
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Error", code: 500 });
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function getSessionsByBuild(req, res) {
|
|
49
|
+
const t = token(req);
|
|
50
|
+
if (!t) {
|
|
51
|
+
res.status(401).json({ success: false, message: "Authorization required", code: 401 });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const time = parseTime(req);
|
|
55
|
+
if (!time) {
|
|
56
|
+
res.status(400).json({ success: false, message: "Query required: value and unit (hour|minute|day)", code: 400 });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
services_1.analyticsService.sessionsByBuild(t, (0, time_range_1.parseTimeToRange)(time.value, time.unit)).then((out) => {
|
|
60
|
+
if (out.success)
|
|
61
|
+
res.status(200).json({ success: true, data: out.data });
|
|
62
|
+
else
|
|
63
|
+
res.status(out.status).json({ success: false, message: out.message, code: out.status });
|
|
64
|
+
}).catch((err) => {
|
|
65
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Error", code: 500 });
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function getFailedCount(req, res) {
|
|
69
|
+
const t = token(req);
|
|
70
|
+
if (!t) {
|
|
71
|
+
res.status(401).json({ success: false, message: "Authorization required", code: 401 });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const time = parseTime(req);
|
|
75
|
+
if (!time) {
|
|
76
|
+
res.status(400).json({ success: false, message: "Query required: value and unit (hour|minute|day)", code: 400 });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
services_1.analyticsService.failedCount(t, (0, time_range_1.parseTimeToRange)(time.value, time.unit)).then((out) => {
|
|
80
|
+
if (out.success)
|
|
81
|
+
res.status(200).json({ success: true, failedCount: out.count });
|
|
82
|
+
else
|
|
83
|
+
res.status(out.status).json({ success: false, message: out.message, code: out.status });
|
|
84
|
+
}).catch((err) => {
|
|
85
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Error", code: 500 });
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function getMostActiveProject(req, res) {
|
|
89
|
+
const t = token(req);
|
|
90
|
+
if (!t) {
|
|
91
|
+
res.status(401).json({ success: false, message: "Authorization required", code: 401 });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const time = parseTime(req);
|
|
95
|
+
if (!time) {
|
|
96
|
+
res.status(400).json({ success: false, message: "Query required: value and unit (hour|minute|day)", code: 400 });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
services_1.analyticsService.mostActiveProject(t, (0, time_range_1.parseTimeToRange)(time.value, time.unit)).then((out) => {
|
|
100
|
+
if (out.success)
|
|
101
|
+
res.status(200).json({ success: true, projectName: out.projectName, count: out.count });
|
|
102
|
+
else
|
|
103
|
+
res.status(out.status).json({ success: false, message: out.message, code: out.status });
|
|
104
|
+
}).catch((err) => {
|
|
105
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Error", code: 500 });
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function getMostActiveUser(req, res) {
|
|
109
|
+
const t = token(req);
|
|
110
|
+
if (!t) {
|
|
111
|
+
res.status(401).json({ success: false, message: "Authorization required", code: 401 });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const time = parseTime(req);
|
|
115
|
+
if (!time) {
|
|
116
|
+
res.status(400).json({ success: false, message: "Query required: value and unit (hour|minute|day)", code: 400 });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
services_1.analyticsService.mostActiveUser(t, (0, time_range_1.parseTimeToRange)(time.value, time.unit)).then((out) => {
|
|
120
|
+
if (out.success)
|
|
121
|
+
res.status(200).json({ success: true, userName: out.userName, count: out.count });
|
|
122
|
+
else
|
|
123
|
+
res.status(out.status).json({ success: false, message: out.message, code: out.status });
|
|
124
|
+
}).catch((err) => {
|
|
125
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Error", code: 500 });
|
|
126
|
+
});
|
|
127
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device-flow auth: POST /auth/initiate and GET /auth/status for IDE-based login.
|
|
3
|
+
* No token required; used when IDE opens browser and polls for token.
|
|
4
|
+
*/
|
|
5
|
+
import { Request, Response } from "express";
|
|
6
|
+
export declare function initiate(_req: Request, res: Response): Promise<void>;
|
|
7
|
+
export declare function status(req: Request, res: Response): Promise<void>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Device-flow auth: POST /auth/initiate and GET /auth/status for IDE-based login.
|
|
4
|
+
* No token required; used when IDE opens browser and polls for token.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.initiate = initiate;
|
|
8
|
+
exports.status = status;
|
|
9
|
+
const crypto_1 = require("crypto");
|
|
10
|
+
const services_1 = require("../services");
|
|
11
|
+
const config_1 = require("../config");
|
|
12
|
+
const device_flow_store_1 = require("../device-flow.store");
|
|
13
|
+
const EXPIRES_IN_SEC = 300;
|
|
14
|
+
async function initiate(_req, res) {
|
|
15
|
+
try {
|
|
16
|
+
const enabled = await services_1.authService.isMcpAddonEnabled();
|
|
17
|
+
if (!enabled) {
|
|
18
|
+
res.status(503).json({
|
|
19
|
+
success: false,
|
|
20
|
+
message: "MCP add-on is not enabled on this SBOX hub.",
|
|
21
|
+
});
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const pollId = (0, crypto_1.randomUUID)();
|
|
25
|
+
const authUrl = (0, config_1.getPublicLoginPageUrl)() + "?mcp_poll_id=" + encodeURIComponent(pollId);
|
|
26
|
+
(0, device_flow_store_1.registerPollSession)(pollId, EXPIRES_IN_SEC);
|
|
27
|
+
res.status(200).json({
|
|
28
|
+
auth_url: authUrl,
|
|
29
|
+
poll_id: pollId,
|
|
30
|
+
expires_in: EXPIRES_IN_SEC,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
res.status(500).json({
|
|
35
|
+
success: false,
|
|
36
|
+
message: e instanceof Error ? e.message : "Failed to initiate auth",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function status(req, res) {
|
|
41
|
+
const pollId = req.query.poll_id?.trim();
|
|
42
|
+
if (!pollId) {
|
|
43
|
+
res.status(400).json({ status: "expired" });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!(0, device_flow_store_1.hasPollSession)(pollId)) {
|
|
47
|
+
res.status(200).json({ status: "expired" });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if ((0, device_flow_store_1.isExpired)(pollId)) {
|
|
51
|
+
res.status(200).json({ status: "expired" });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const token = await services_1.authService.pollForToken(pollId);
|
|
55
|
+
if (token) {
|
|
56
|
+
res.status(200).json({ status: "authenticated", token });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
res.status(200).json({ status: "pending" });
|
|
60
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Auth controller: login (local/LDAP via same SBOX endpoint). No business logic.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.login = login;
|
|
7
|
+
const auth_dto_1 = require("../dto/auth.dto");
|
|
8
|
+
const services_1 = require("../services");
|
|
9
|
+
function login(req, res) {
|
|
10
|
+
const body = auth_dto_1.LoginBodySchema.parse(req.body);
|
|
11
|
+
services_1.authService.login(body).then((out) => {
|
|
12
|
+
if (out.success) {
|
|
13
|
+
res.status(200).json({ success: true, data: out.data });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
res.status(out.status).json({ success: false, message: out.message, code: out.status });
|
|
17
|
+
}).catch((err) => {
|
|
18
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Login failed", code: 500 });
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Browser controller: list supported browsers. No business logic.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.listBrowsers = listBrowsers;
|
|
7
|
+
const services_1 = require("../services");
|
|
8
|
+
function listBrowsers(req, res) {
|
|
9
|
+
const token = req.token;
|
|
10
|
+
if (!token) {
|
|
11
|
+
res.status(401).json({ success: false, message: "Authorization required", code: 401 });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
services_1.browserService.listSupportedBrowsers(token).then((out) => {
|
|
15
|
+
if (out.success) {
|
|
16
|
+
res.status(200).json({ success: true, data: out.data });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
res.status(out.status).json({ success: false, message: out.message, code: out.status });
|
|
20
|
+
}).catch((err) => {
|
|
21
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Error", code: 500 });
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Device controller: list supported devices. No business logic.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.listDevices = listDevices;
|
|
7
|
+
const services_1 = require("../services");
|
|
8
|
+
function listDevices(req, res) {
|
|
9
|
+
const token = req.token;
|
|
10
|
+
if (!token) {
|
|
11
|
+
res.status(401).json({ success: false, message: "Authorization required", code: 401 });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
services_1.deviceService.listSupportedDevices(token).then((out) => {
|
|
15
|
+
if (out.success) {
|
|
16
|
+
res.status(200).json({ success: true, data: out.data });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
res.status(out.status).json({ success: false, message: out.message, code: out.status });
|
|
20
|
+
}).catch((err) => {
|
|
21
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Error", code: 500 });
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from "./auth.controller";
|
|
2
|
+
export { listSessions, getSessionDetail, updateSessionStatus, deleteSession, startManualSession } from "./session.controller";
|
|
3
|
+
export * from "./browser.controller";
|
|
4
|
+
export * from "./device.controller";
|
|
5
|
+
export * from "./user.controller";
|
|
6
|
+
export * from "./project-stats.controller";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.startManualSession = exports.deleteSession = exports.updateSessionStatus = exports.getSessionDetail = exports.listSessions = void 0;
|
|
18
|
+
__exportStar(require("./auth.controller"), exports);
|
|
19
|
+
var session_controller_1 = require("./session.controller");
|
|
20
|
+
Object.defineProperty(exports, "listSessions", { enumerable: true, get: function () { return session_controller_1.listSessions; } });
|
|
21
|
+
Object.defineProperty(exports, "getSessionDetail", { enumerable: true, get: function () { return session_controller_1.getSessionDetail; } });
|
|
22
|
+
Object.defineProperty(exports, "updateSessionStatus", { enumerable: true, get: function () { return session_controller_1.updateSessionStatus; } });
|
|
23
|
+
Object.defineProperty(exports, "deleteSession", { enumerable: true, get: function () { return session_controller_1.deleteSession; } });
|
|
24
|
+
Object.defineProperty(exports, "startManualSession", { enumerable: true, get: function () { return session_controller_1.startManualSession; } });
|
|
25
|
+
__exportStar(require("./browser.controller"), exports);
|
|
26
|
+
__exportStar(require("./device.controller"), exports);
|
|
27
|
+
__exportStar(require("./user.controller"), exports);
|
|
28
|
+
__exportStar(require("./project-stats.controller"), exports);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Project stats controller: sessions per project (last X days). No business logic.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getSessionsPerProject = getSessionsPerProject;
|
|
7
|
+
const zod_1 = require("zod");
|
|
8
|
+
const services_1 = require("../services");
|
|
9
|
+
const QuerySchema = zod_1.z.object({
|
|
10
|
+
days: zod_1.z.string().optional().transform((s) => (s ? parseInt(s, 10) : 14)),
|
|
11
|
+
});
|
|
12
|
+
function getSessionsPerProject(req, res) {
|
|
13
|
+
const token = req.token;
|
|
14
|
+
if (!token) {
|
|
15
|
+
res.status(401).json({ success: false, message: "Authorization required", code: 401 });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const parsed = QuerySchema.safeParse(req.query);
|
|
19
|
+
const days = parsed.success && !isNaN(parsed.data.days) ? Math.min(Math.max(parsed.data.days, 1), 365) : 14;
|
|
20
|
+
services_1.sessionsPerProjectService.getSessionsPerProject(token, days).then((out) => {
|
|
21
|
+
if (out.success) {
|
|
22
|
+
res.status(200).json({ success: true, data: out.data });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
res.status(out.status).json({ success: false, message: out.message, code: out.status });
|
|
26
|
+
}).catch((err) => {
|
|
27
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Error", code: 500 });
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session controller: get, update status, delete, start manual. No business logic.
|
|
3
|
+
*/
|
|
4
|
+
import { Request, Response } from "express";
|
|
5
|
+
export declare function listSessions(req: Request, res: Response): void;
|
|
6
|
+
export declare function getSessionDetail(req: Request, res: Response): void;
|
|
7
|
+
export declare function updateSessionStatus(req: Request, res: Response): void;
|
|
8
|
+
export declare function deleteSession(req: Request, res: Response): void;
|
|
9
|
+
export declare function startManualSession(req: Request, res: Response): void;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Session controller: get, update status, delete, start manual. No business logic.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.listSessions = listSessions;
|
|
7
|
+
exports.getSessionDetail = getSessionDetail;
|
|
8
|
+
exports.updateSessionStatus = updateSessionStatus;
|
|
9
|
+
exports.deleteSession = deleteSession;
|
|
10
|
+
exports.startManualSession = startManualSession;
|
|
11
|
+
const services_1 = require("../services");
|
|
12
|
+
const session_dto_1 = require("../dto/session.dto");
|
|
13
|
+
const UpdateBodySchema = session_dto_1.UpdateSessionStatusBodySchema;
|
|
14
|
+
function token(req) {
|
|
15
|
+
return req.token;
|
|
16
|
+
}
|
|
17
|
+
function listSessions(req, res) {
|
|
18
|
+
const t = token(req);
|
|
19
|
+
if (!t) {
|
|
20
|
+
res.status(401).json({ success: false, message: "Authorization required", code: 401 });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const q = req.query;
|
|
24
|
+
const limit = q.limit != null ? parseInt(q.limit, 10) : undefined;
|
|
25
|
+
const offset = q.offset != null ? parseInt(q.offset, 10) : undefined;
|
|
26
|
+
services_1.sessionService.listSessions({
|
|
27
|
+
limit: isNaN(limit) ? undefined : limit,
|
|
28
|
+
offset: isNaN(offset) ? undefined : offset,
|
|
29
|
+
projectName: q.projectName,
|
|
30
|
+
status: q.status,
|
|
31
|
+
browser: q.browser,
|
|
32
|
+
build: q.build,
|
|
33
|
+
sortBy: q.sortBy,
|
|
34
|
+
sortDirection: q.sortDirection,
|
|
35
|
+
}, t).then((out) => {
|
|
36
|
+
if (out.success) {
|
|
37
|
+
res.status(200).json({ success: true, data: out.data });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
res.status(out.status).json({ success: false, message: out.message, code: out.status });
|
|
41
|
+
}).catch((err) => {
|
|
42
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Error", code: 500 });
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function getSessionDetail(req, res) {
|
|
46
|
+
const ekey = req.params.ekey;
|
|
47
|
+
const t = token(req);
|
|
48
|
+
if (!t) {
|
|
49
|
+
res.status(401).json({ success: false, message: "Authorization required", code: 401 });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
services_1.sessionService.getSessionDetail(ekey, t).then((out) => {
|
|
53
|
+
if (out.success) {
|
|
54
|
+
res.status(200).json({ success: true, data: out.data });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
res.status(out.status).json({ success: false, message: out.message, code: out.status });
|
|
58
|
+
}).catch((err) => {
|
|
59
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Error", code: 500 });
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function updateSessionStatus(req, res) {
|
|
63
|
+
const body = UpdateBodySchema.parse(req.body);
|
|
64
|
+
const t = token(req);
|
|
65
|
+
if (!t) {
|
|
66
|
+
res.status(401).json({ success: false, message: "Authorization required", code: 401 });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
services_1.sessionService.updateSessionStatus(body, t).then((out) => {
|
|
70
|
+
if (out.success) {
|
|
71
|
+
res.status(out.status).json({ success: true });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
res.status(out.status).json({ success: false, message: out.message ?? "Update failed", code: out.status });
|
|
75
|
+
}).catch((err) => {
|
|
76
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Error", code: 500 });
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function deleteSession(req, res) {
|
|
80
|
+
const sessionId = req.query.sessionId;
|
|
81
|
+
const t = token(req);
|
|
82
|
+
if (!t) {
|
|
83
|
+
res.status(401).json({ success: false, message: "Authorization required", code: 401 });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!sessionId) {
|
|
87
|
+
res.status(400).json({ success: false, message: "sessionId required", code: 400 });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
services_1.sessionService.deleteSession(sessionId, t).then((out) => {
|
|
91
|
+
if (out.success) {
|
|
92
|
+
res.status(200).json({ success: true, data: out.data });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
res.status(out.status).json({ success: false, message: out.message, code: out.status });
|
|
96
|
+
}).catch((err) => {
|
|
97
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Error", code: 500 });
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function startManualSession(req, res) {
|
|
101
|
+
const body = req.body;
|
|
102
|
+
const t = token(req);
|
|
103
|
+
if (!t) {
|
|
104
|
+
res.status(401).json({ success: false, message: "Authorization required", code: 401 });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!body || typeof body.projectName !== "string") {
|
|
108
|
+
res.status(400).json({ success: false, message: "projectName is required", code: 400 });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
services_1.sessionService.startManualSession(body, t).then((out) => {
|
|
112
|
+
if (out.success) {
|
|
113
|
+
res.status(200).json({ success: true, data: out.data });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
res.status(out.status).json({ success: false, message: out.message, code: out.status });
|
|
117
|
+
}).catch((err) => {
|
|
118
|
+
res.status(500).json({ success: false, message: err instanceof Error ? err.message : "Error", code: 500 });
|
|
119
|
+
});
|
|
120
|
+
}
|