ocean-brain 0.7.5 → 0.8.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/dist/index.js +3 -3
- package/dist/mcp.js +180 -736
- package/package.json +4 -1
- package/server/client/dist/assets/ArrowRight.es-C4zlc54Z.js +1 -0
- package/server/client/dist/assets/{Calendar-Bc2zJueR.js → Calendar-COrN6Hjv.js} +2 -2
- package/server/client/dist/assets/Callout-BK0K9E74.js +1 -0
- package/server/client/dist/assets/Copy.es-zm8f_XXX.js +1 -0
- package/server/client/dist/assets/Graph-BH1fzhue.js +1 -0
- package/server/client/dist/assets/{Image-DxWhlIDG.js → Image-KXuHOG4b.js} +1 -1
- package/server/client/dist/assets/Image.es-BKahRbIm.js +1 -0
- package/server/client/dist/assets/Note-C143cKKw.js +12 -0
- package/server/client/dist/assets/Plus.es-BqeC8T8h.js +1 -0
- package/server/client/dist/assets/Reminders-BYG5rz1M.js +1 -0
- package/server/client/dist/assets/Search-DBWsIMDP.js +1 -0
- package/server/client/dist/assets/{SurfaceCard-CkAVMLPs.js → SurfaceCard-CNSQfYfM.js} +1 -1
- package/server/client/dist/assets/Tag-5aCfSO8Y.js +1 -0
- package/server/client/dist/assets/TagNotes-BPZjN-6l.js +1 -0
- package/server/client/dist/assets/Trash.es-CRQkTnXZ.js +1 -0
- package/server/client/dist/assets/ViewNotes-DGLNF96x.js +1 -0
- package/server/client/dist/assets/ViewSectionDialog-CzskgmMF.js +102 -0
- package/server/client/dist/assets/Views-DR5jT23G.js +1 -0
- package/server/client/dist/assets/{graph-vendor-CUxe67Lr.js → graph-vendor-UTvF2NS_.js} +2 -2
- package/server/client/dist/assets/image.api-8dlMwzMd.js +17 -0
- package/server/client/dist/assets/{index-C5BGzDgt.css → index-BEUqDuAZ.css} +1 -1
- package/server/client/dist/assets/{index-D3BfroIT.js → index-DGIAh3ZY.js} +1 -1
- package/server/client/dist/assets/index-VHp17GQX.js +342 -0
- package/server/client/dist/assets/manage-image-RqtWKas2.js +1 -0
- package/server/client/dist/assets/{manage-image-detail-C8Q7K7I3.js → manage-image-detail-BTSIbLlQ.js} +1 -1
- package/server/client/dist/assets/mcp-EDurbggR.js +4 -0
- package/server/client/dist/assets/mcp-admin-adapter-DmGUo5G8.js +1 -0
- package/server/client/dist/assets/{note-core-Z6kqulGB.js → note-core-BklwyZHk.js} +1 -1
- package/server/client/dist/assets/{note-vendor-i1M2FSI2.js → note-vendor-DkYKUmLo.js} +1 -1
- package/server/client/dist/assets/{placeholder-Cr_nI8UI.js → placeholder-DeKgSBnC.js} +4 -4
- package/server/client/dist/assets/{properties-CvYWJWUM.js → properties-BjrZvOYn.js} +1 -1
- package/server/client/dist/assets/tag.api-Dl8DQd0m.js +21 -0
- package/server/client/dist/assets/trash-C5ntddLu.js +1 -0
- package/server/client/dist/assets/{useReminderMutate-CDmIEwsw.js → useReminderMutate-CoXXqJY2.js} +1 -1
- package/server/client/dist/index.html +5 -5
- package/server/dist/features/auth/http/login-page.js +1 -1
- package/server/dist/features/image/http/upload.js +34 -23
- package/server/dist/features/image/http/upload.js.map +1 -1
- package/server/dist/features/image/services/validation.js +111 -0
- package/server/dist/features/image/services/validation.js.map +1 -0
- package/server/dist/features/mcp-admin/http/handlers.js +11 -3
- package/server/dist/features/mcp-admin/http/handlers.js.map +1 -1
- package/server/dist/features/note/http/mcp.js +8 -43
- package/server/dist/features/note/http/mcp.js.map +1 -1
- package/server/dist/features/note/services/cleanup.js +2 -2
- package/server/dist/features/note/services/cleanup.js.map +1 -1
- package/server/dist/features/note/services/markdown-intent-write.js +11 -43
- package/server/dist/features/note/services/markdown-intent-write.js.map +1 -1
- package/server/dist/features/note/services/markdown-patch.js +4 -123
- package/server/dist/features/note/services/markdown-patch.js.map +1 -1
- package/server/dist/features/note/services/write.js +13 -13
- package/server/dist/features/note/services/write.js.map +1 -1
- package/server/dist/features/tag/graphql/tag.query.resolver.js +16 -1
- package/server/dist/features/tag/graphql/tag.query.resolver.js.map +1 -1
- package/server/dist/features/tag/graphql/tag.type-defs.js +2 -0
- package/server/dist/features/tag/graphql/tag.type-defs.js.map +1 -1
- package/server/dist/modules/app-version.js +105 -0
- package/server/dist/modules/app-version.js.map +1 -0
- package/server/dist/modules/mcp-auth.js +62 -1
- package/server/dist/modules/mcp-auth.js.map +1 -1
- package/server/dist/paths.js +1 -0
- package/server/dist/paths.js.map +1 -1
- package/server/dist/routes/api.js +2 -8
- package/server/dist/routes/api.js.map +1 -1
- package/server/dist/routes/client.js +51 -1
- package/server/dist/routes/client.js.map +1 -1
- package/server/client/dist/assets/Callout-B4kA_Cvr.js +0 -1
- package/server/client/dist/assets/Graph-uq5jfJgt.js +0 -1
- package/server/client/dist/assets/Image.es-BpAOJjJT.js +0 -1
- package/server/client/dist/assets/Note-Bo8ZYRP-.js +0 -22
- package/server/client/dist/assets/Plus.es-Dc_js8AC.js +0 -1
- package/server/client/dist/assets/Reminders-lj7Equld.js +0 -1
- package/server/client/dist/assets/Search-1wcRO5--.js +0 -1
- package/server/client/dist/assets/Tag-CeygwFfv.js +0 -1
- package/server/client/dist/assets/TagNotes-B8DqItxw.js +0 -1
- package/server/client/dist/assets/Trash.es-OuQVb_SX.js +0 -1
- package/server/client/dist/assets/ViewNotes-BH7PbdOD.js +0 -1
- package/server/client/dist/assets/ViewSectionTableRenderer-BJwhd5be.js +0 -102
- package/server/client/dist/assets/Views-eYFtsdFj.js +0 -1
- package/server/client/dist/assets/image.api-C0eYGCeH.js +0 -17
- package/server/client/dist/assets/index-jIfkRdY_.js +0 -361
- package/server/client/dist/assets/manage-image-C0YwkLIP.js +0 -1
- package/server/client/dist/assets/mcp-DHHhTLC8.js +0 -1
- package/server/client/dist/assets/trash-DA27aW7L.js +0 -1
- package/server/dist/features/image/services/remote-fetch.js +0 -176
- package/server/dist/features/image/services/remote-fetch.js.map +0 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { paths } from "../paths.js";
|
|
4
|
+
const OCEAN_BRAIN_RELEASES_URL = "https://github.com/baealex/ocean-brain/releases";
|
|
5
|
+
const OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER = "X-Ocean-Brain-MCP-Compatibility-Version";
|
|
6
|
+
const OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER = "X-Ocean-Brain-MCP-Client-Version";
|
|
7
|
+
const normalizeVersion = (version) => version.trim().replace(/^v/i, "");
|
|
8
|
+
const parseMajorMinorVersion = (version) => {
|
|
9
|
+
const match = normalizeVersion(version).match(/^(\d+)\.(\d+)(?:\.\d+)?(?:[-+].*)?$/);
|
|
10
|
+
if (!match) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
major: Number(match[1]),
|
|
15
|
+
minor: Number(match[2])
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
const formatMcpVersionRequirement = (mcpCompatibilityVersion) => {
|
|
19
|
+
const parsed = parseMajorMinorVersion(mcpCompatibilityVersion);
|
|
20
|
+
return parsed ? `${parsed.major}.${parsed.minor}.x` : "unknown";
|
|
21
|
+
};
|
|
22
|
+
const readPackageMetadata = (packageJsonPath) => {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
25
|
+
} catch {
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const readPackageVersion = (packageJsonPath) => {
|
|
30
|
+
const packageJson = readPackageMetadata(packageJsonPath);
|
|
31
|
+
return typeof packageJson?.version === "string" ? packageJson.version : void 0;
|
|
32
|
+
};
|
|
33
|
+
const readPackageMcpCompatibilityVersion = (packageJsonPath) => {
|
|
34
|
+
const packageJson = readPackageMetadata(packageJsonPath);
|
|
35
|
+
const version = packageJson?.oceanBrain?.mcpCompatibilityVersion;
|
|
36
|
+
return typeof version === "string" ? version : void 0;
|
|
37
|
+
};
|
|
38
|
+
const createMissingMcpCompatibilityVersionError = () => new Error("Ocean Brain MCP compatibility version is required in package metadata.");
|
|
39
|
+
const getVersionCandidatePaths = () => {
|
|
40
|
+
return Array.from(
|
|
41
|
+
/* @__PURE__ */ new Set([
|
|
42
|
+
path.resolve(paths.packageRoot, "..", "package.json"),
|
|
43
|
+
path.resolve(paths.packageRoot, "..", "cli", "package.json"),
|
|
44
|
+
path.resolve(process.cwd(), "packages", "cli", "package.json"),
|
|
45
|
+
path.resolve(process.cwd(), "..", "cli", "package.json")
|
|
46
|
+
])
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
const resolveOceanBrainVersion = () => {
|
|
50
|
+
for (const candidatePath of getVersionCandidatePaths()) {
|
|
51
|
+
const version = readPackageVersion(candidatePath);
|
|
52
|
+
if (version) {
|
|
53
|
+
return normalizeVersion(version);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (process.env.OCEAN_BRAIN_VERSION) {
|
|
57
|
+
return normalizeVersion(process.env.OCEAN_BRAIN_VERSION);
|
|
58
|
+
}
|
|
59
|
+
return "0.0.0";
|
|
60
|
+
};
|
|
61
|
+
const resolveMcpCompatibilityVersionFromPaths = (candidatePaths) => {
|
|
62
|
+
for (const candidatePath of candidatePaths) {
|
|
63
|
+
const version = readPackageMcpCompatibilityVersion(candidatePath);
|
|
64
|
+
if (version) {
|
|
65
|
+
return normalizeVersion(version);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
throw createMissingMcpCompatibilityVersionError();
|
|
69
|
+
};
|
|
70
|
+
const resolveMcpCompatibilityVersion = () => resolveMcpCompatibilityVersionFromPaths(getVersionCandidatePaths());
|
|
71
|
+
const buildOceanBrainVersionInfo = ({
|
|
72
|
+
mcpCompatibilityVersion,
|
|
73
|
+
version
|
|
74
|
+
}) => {
|
|
75
|
+
const compatibilityVersion = normalizeVersion(mcpCompatibilityVersion);
|
|
76
|
+
const compatibilityRequirement = formatMcpVersionRequirement(compatibilityVersion);
|
|
77
|
+
return {
|
|
78
|
+
version: normalizeVersion(version),
|
|
79
|
+
releaseUrl: OCEAN_BRAIN_RELEASES_URL,
|
|
80
|
+
mcpVersionRequirement: compatibilityRequirement,
|
|
81
|
+
mcp: {
|
|
82
|
+
compatibilityVersion,
|
|
83
|
+
compatibilityRequirement,
|
|
84
|
+
compatibilityVersionHeader: OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,
|
|
85
|
+
clientVersionHeader: OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const getOceanBrainVersionInfo = () => buildOceanBrainVersionInfo({
|
|
90
|
+
version: resolveOceanBrainVersion(),
|
|
91
|
+
mcpCompatibilityVersion: resolveMcpCompatibilityVersion()
|
|
92
|
+
});
|
|
93
|
+
export {
|
|
94
|
+
OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER,
|
|
95
|
+
OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,
|
|
96
|
+
OCEAN_BRAIN_RELEASES_URL,
|
|
97
|
+
buildOceanBrainVersionInfo,
|
|
98
|
+
formatMcpVersionRequirement,
|
|
99
|
+
getOceanBrainVersionInfo,
|
|
100
|
+
parseMajorMinorVersion,
|
|
101
|
+
resolveMcpCompatibilityVersion,
|
|
102
|
+
resolveMcpCompatibilityVersionFromPaths,
|
|
103
|
+
resolveOceanBrainVersion
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=app-version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/modules/app-version.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nimport { paths } from '~/paths.js';\n\nexport const OCEAN_BRAIN_RELEASES_URL = 'https://github.com/baealex/ocean-brain/releases';\nexport const OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER = 'X-Ocean-Brain-MCP-Compatibility-Version';\nexport const OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER = 'X-Ocean-Brain-MCP-Client-Version';\n\ninterface MajorMinorVersion {\n major: number;\n minor: number;\n}\n\ninterface PackageMetadata {\n oceanBrain?: {\n mcpCompatibilityVersion?: unknown;\n };\n version?: unknown;\n}\n\nexport interface OceanBrainVersionInfo {\n version: string;\n releaseUrl: string;\n mcpVersionRequirement: string;\n mcp: {\n compatibilityVersion: string;\n compatibilityRequirement: string;\n compatibilityVersionHeader: string;\n clientVersionHeader: string;\n };\n}\n\nconst normalizeVersion = (version: string) => version.trim().replace(/^v/i, '');\n\nexport const parseMajorMinorVersion = (version: string): MajorMinorVersion | null => {\n const match = normalizeVersion(version).match(/^(\\d+)\\.(\\d+)(?:\\.\\d+)?(?:[-+].*)?$/);\n\n if (!match) {\n return null;\n }\n\n return {\n major: Number(match[1]),\n minor: Number(match[2]),\n };\n};\n\nexport const formatMcpVersionRequirement = (mcpCompatibilityVersion: string) => {\n const parsed = parseMajorMinorVersion(mcpCompatibilityVersion);\n return parsed ? `${parsed.major}.${parsed.minor}.x` : 'unknown';\n};\n\nconst readPackageMetadata = (packageJsonPath: string): PackageMetadata | undefined => {\n try {\n return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) as PackageMetadata;\n } catch {\n return undefined;\n }\n};\n\nconst readPackageVersion = (packageJsonPath: string) => {\n const packageJson = readPackageMetadata(packageJsonPath);\n return typeof packageJson?.version === 'string' ? packageJson.version : undefined;\n};\n\nconst readPackageMcpCompatibilityVersion = (packageJsonPath: string) => {\n const packageJson = readPackageMetadata(packageJsonPath);\n const version = packageJson?.oceanBrain?.mcpCompatibilityVersion;\n return typeof version === 'string' ? version : undefined;\n};\n\nconst createMissingMcpCompatibilityVersionError = () =>\n new Error('Ocean Brain MCP compatibility version is required in package metadata.');\n\nconst getVersionCandidatePaths = () => {\n return Array.from(\n new Set([\n path.resolve(paths.packageRoot, '..', 'package.json'),\n path.resolve(paths.packageRoot, '..', 'cli', 'package.json'),\n path.resolve(process.cwd(), 'packages', 'cli', 'package.json'),\n path.resolve(process.cwd(), '..', 'cli', 'package.json'),\n ]),\n );\n};\n\nexport const resolveOceanBrainVersion = () => {\n for (const candidatePath of getVersionCandidatePaths()) {\n const version = readPackageVersion(candidatePath);\n if (version) {\n return normalizeVersion(version);\n }\n }\n\n if (process.env.OCEAN_BRAIN_VERSION) {\n return normalizeVersion(process.env.OCEAN_BRAIN_VERSION);\n }\n\n return '0.0.0';\n};\n\nexport const resolveMcpCompatibilityVersionFromPaths = (candidatePaths: string[]) => {\n for (const candidatePath of candidatePaths) {\n const version = readPackageMcpCompatibilityVersion(candidatePath);\n if (version) {\n return normalizeVersion(version);\n }\n }\n\n throw createMissingMcpCompatibilityVersionError();\n};\n\nexport const resolveMcpCompatibilityVersion = () => resolveMcpCompatibilityVersionFromPaths(getVersionCandidatePaths());\n\nexport const buildOceanBrainVersionInfo = ({\n mcpCompatibilityVersion,\n version,\n}: {\n mcpCompatibilityVersion: string;\n version: string;\n}): OceanBrainVersionInfo => {\n const compatibilityVersion = normalizeVersion(mcpCompatibilityVersion);\n const compatibilityRequirement = formatMcpVersionRequirement(compatibilityVersion);\n\n return {\n version: normalizeVersion(version),\n releaseUrl: OCEAN_BRAIN_RELEASES_URL,\n mcpVersionRequirement: compatibilityRequirement,\n mcp: {\n compatibilityVersion,\n compatibilityRequirement,\n compatibilityVersionHeader: OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,\n clientVersionHeader: OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER,\n },\n };\n};\n\nexport const getOceanBrainVersionInfo = () =>\n buildOceanBrainVersionInfo({\n version: resolveOceanBrainVersion(),\n mcpCompatibilityVersion: resolveMcpCompatibilityVersion(),\n });\n"],"mappings":"AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AAEjB,SAAS,aAAa;AAEf,MAAM,2BAA2B;AACjC,MAAM,+CAA+C;AACrD,MAAM,wCAAwC;AA0BrD,MAAM,mBAAmB,CAAC,YAAoB,QAAQ,KAAK,EAAE,QAAQ,OAAO,EAAE;AAEvE,MAAM,yBAAyB,CAAC,YAA8C;AACjF,QAAM,QAAQ,iBAAiB,OAAO,EAAE,MAAM,qCAAqC;AAEnF,MAAI,CAAC,OAAO;AACR,WAAO;AAAA,EACX;AAEA,SAAO;AAAA,IACH,OAAO,OAAO,MAAM,CAAC,CAAC;AAAA,IACtB,OAAO,OAAO,MAAM,CAAC,CAAC;AAAA,EAC1B;AACJ;AAEO,MAAM,8BAA8B,CAAC,4BAAoC;AAC5E,QAAM,SAAS,uBAAuB,uBAAuB;AAC7D,SAAO,SAAS,GAAG,OAAO,KAAK,IAAI,OAAO,KAAK,OAAO;AAC1D;AAEA,MAAM,sBAAsB,CAAC,oBAAyD;AAClF,MAAI;AACA,WAAO,KAAK,MAAM,GAAG,aAAa,iBAAiB,OAAO,CAAC;AAAA,EAC/D,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAEA,MAAM,qBAAqB,CAAC,oBAA4B;AACpD,QAAM,cAAc,oBAAoB,eAAe;AACvD,SAAO,OAAO,aAAa,YAAY,WAAW,YAAY,UAAU;AAC5E;AAEA,MAAM,qCAAqC,CAAC,oBAA4B;AACpE,QAAM,cAAc,oBAAoB,eAAe;AACvD,QAAM,UAAU,aAAa,YAAY;AACzC,SAAO,OAAO,YAAY,WAAW,UAAU;AACnD;AAEA,MAAM,4CAA4C,MAC9C,IAAI,MAAM,wEAAwE;AAEtF,MAAM,2BAA2B,MAAM;AACnC,SAAO,MAAM;AAAA,IACT,oBAAI,IAAI;AAAA,MACJ,KAAK,QAAQ,MAAM,aAAa,MAAM,cAAc;AAAA,MACpD,KAAK,QAAQ,MAAM,aAAa,MAAM,OAAO,cAAc;AAAA,MAC3D,KAAK,QAAQ,QAAQ,IAAI,GAAG,YAAY,OAAO,cAAc;AAAA,MAC7D,KAAK,QAAQ,QAAQ,IAAI,GAAG,MAAM,OAAO,cAAc;AAAA,IAC3D,CAAC;AAAA,EACL;AACJ;AAEO,MAAM,2BAA2B,MAAM;AAC1C,aAAW,iBAAiB,yBAAyB,GAAG;AACpD,UAAM,UAAU,mBAAmB,aAAa;AAChD,QAAI,SAAS;AACT,aAAO,iBAAiB,OAAO;AAAA,IACnC;AAAA,EACJ;AAEA,MAAI,QAAQ,IAAI,qBAAqB;AACjC,WAAO,iBAAiB,QAAQ,IAAI,mBAAmB;AAAA,EAC3D;AAEA,SAAO;AACX;AAEO,MAAM,0CAA0C,CAAC,mBAA6B;AACjF,aAAW,iBAAiB,gBAAgB;AACxC,UAAM,UAAU,mCAAmC,aAAa;AAChE,QAAI,SAAS;AACT,aAAO,iBAAiB,OAAO;AAAA,IACnC;AAAA,EACJ;AAEA,QAAM,0CAA0C;AACpD;AAEO,MAAM,iCAAiC,MAAM,wCAAwC,yBAAyB,CAAC;AAE/G,MAAM,6BAA6B,CAAC;AAAA,EACvC;AAAA,EACA;AACJ,MAG6B;AACzB,QAAM,uBAAuB,iBAAiB,uBAAuB;AACrE,QAAM,2BAA2B,4BAA4B,oBAAoB;AAEjF,SAAO;AAAA,IACH,SAAS,iBAAiB,OAAO;AAAA,IACjC,YAAY;AAAA,IACZ,uBAAuB;AAAA,IACvB,KAAK;AAAA,MACD;AAAA,MACA;AAAA,MACA,4BAA4B;AAAA,MAC5B,qBAAqB;AAAA,IACzB;AAAA,EACJ;AACJ;AAEO,MAAM,2BAA2B,MACpC,2BAA2B;AAAA,EACvB,SAAS,yBAAyB;AAAA,EAClC,yBAAyB,+BAA+B;AAC5D,CAAC;","names":[]}
|
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
import { GraphQLError } from "graphql";
|
|
2
|
+
import {
|
|
3
|
+
getOceanBrainVersionInfo,
|
|
4
|
+
OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER,
|
|
5
|
+
OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,
|
|
6
|
+
parseMajorMinorVersion
|
|
7
|
+
} from "./app-version.js";
|
|
2
8
|
const JSON_HEADERS = { "Content-Type": "application/json" };
|
|
9
|
+
const OCEAN_BRAIN_MCP_VERSION_HEADER = "X-Ocean-Brain-MCP-Version";
|
|
10
|
+
const readHeaderValue = (req, headerName) => {
|
|
11
|
+
const value = req.headers[headerName.toLowerCase()];
|
|
12
|
+
return Array.isArray(value) ? value[0] : value;
|
|
13
|
+
};
|
|
14
|
+
const readMcpCompatibilityVersion = (req) => readHeaderValue(req, OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER) ?? readHeaderValue(req, OCEAN_BRAIN_MCP_VERSION_HEADER);
|
|
15
|
+
const readMcpClientVersion = (req) => readHeaderValue(req, OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER) ?? readHeaderValue(req, OCEAN_BRAIN_MCP_VERSION_HEADER);
|
|
16
|
+
const isMcpVersionCompatible = (requiredMcpCompatibilityVersion, mcpCompatibilityVersion) => {
|
|
17
|
+
const required = parseMajorMinorVersion(requiredMcpCompatibilityVersion);
|
|
18
|
+
const mcp = parseMajorMinorVersion(mcpCompatibilityVersion);
|
|
19
|
+
return Boolean(required && mcp && required.major === mcp.major && required.minor === mcp.minor);
|
|
20
|
+
};
|
|
21
|
+
const createMcpVersionCompatibilityMessage = ({
|
|
22
|
+
mcpClientVersion,
|
|
23
|
+
mcpCompatibilityVersion,
|
|
24
|
+
requiredMcpCompatibilityVersion,
|
|
25
|
+
serverVersion
|
|
26
|
+
}) => {
|
|
27
|
+
const mcpCompatibilityVersionText = mcpCompatibilityVersion ? `v${mcpCompatibilityVersion}` : "not provided";
|
|
28
|
+
const mcpClientVersionText = mcpClientVersion ? `v${mcpClientVersion}` : "not provided";
|
|
29
|
+
return [
|
|
30
|
+
"Ocean Brain MCP compatibility versions are incompatible.",
|
|
31
|
+
`Server: v${serverVersion}`,
|
|
32
|
+
`Required MCP compatibility: ${requiredMcpCompatibilityVersion}`,
|
|
33
|
+
`MCP compatibility: ${mcpCompatibilityVersionText}`,
|
|
34
|
+
`MCP client: ${mcpClientVersionText}`,
|
|
35
|
+
"",
|
|
36
|
+
"Please update Ocean Brain MCP to a compatible release.",
|
|
37
|
+
getOceanBrainVersionInfo().releaseUrl
|
|
38
|
+
].join("\n");
|
|
39
|
+
};
|
|
3
40
|
const readBearerToken = (authorizationHeader) => {
|
|
4
41
|
if (!authorizationHeader?.startsWith("Bearer ")) {
|
|
5
42
|
return void 0;
|
|
@@ -40,6 +77,28 @@ const createMcpAuthMiddleware = (_authConfig, mcpAdminAuth) => {
|
|
|
40
77
|
}).end();
|
|
41
78
|
return;
|
|
42
79
|
}
|
|
80
|
+
const versionInfo = getOceanBrainVersionInfo();
|
|
81
|
+
const mcpCompatibilityVersion = readMcpCompatibilityVersion(req);
|
|
82
|
+
const mcpClientVersion = readMcpClientVersion(req);
|
|
83
|
+
if (!mcpCompatibilityVersion || !isMcpVersionCompatible(versionInfo.mcp.compatibilityVersion, mcpCompatibilityVersion)) {
|
|
84
|
+
res.status(426).set(JSON_HEADERS).json({
|
|
85
|
+
code: "MCP_VERSION_INCOMPATIBLE",
|
|
86
|
+
message: createMcpVersionCompatibilityMessage({
|
|
87
|
+
serverVersion: versionInfo.version,
|
|
88
|
+
requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,
|
|
89
|
+
mcpCompatibilityVersion,
|
|
90
|
+
mcpClientVersion
|
|
91
|
+
}),
|
|
92
|
+
serverVersion: versionInfo.version,
|
|
93
|
+
mcpVersion: mcpClientVersion ?? mcpCompatibilityVersion ?? null,
|
|
94
|
+
mcpClientVersion: mcpClientVersion ?? null,
|
|
95
|
+
mcpCompatibilityVersion: mcpCompatibilityVersion ?? null,
|
|
96
|
+
requiredMcpVersion: versionInfo.mcpVersionRequirement,
|
|
97
|
+
requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,
|
|
98
|
+
releaseUrl: versionInfo.releaseUrl
|
|
99
|
+
}).end();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
43
102
|
next();
|
|
44
103
|
} catch (error) {
|
|
45
104
|
next(error);
|
|
@@ -64,7 +123,9 @@ const createReadOnlyMcpValidationRule = () => {
|
|
|
64
123
|
};
|
|
65
124
|
};
|
|
66
125
|
export {
|
|
126
|
+
OCEAN_BRAIN_MCP_VERSION_HEADER,
|
|
67
127
|
createMcpAuthMiddleware,
|
|
68
|
-
createReadOnlyMcpValidationRule
|
|
128
|
+
createReadOnlyMcpValidationRule,
|
|
129
|
+
isMcpVersionCompatible
|
|
69
130
|
};
|
|
70
131
|
//# sourceMappingURL=mcp-auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/mcp-auth.ts"],"sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport type { ValidationRule } from 'graphql';\nimport { GraphQLError } from 'graphql';\n\nimport type { AuthConfig } from './auth-mode.js';\n\nexport interface McpTokenValidationResult {\n ok: boolean;\n reason?: 'not_configured' | 'forbidden';\n}\n\nexport interface McpAdminAuthPort {\n getStatus: () => Promise<{ enabled: boolean }>;\n validatePresentedToken: (token: string) => Promise<McpTokenValidationResult>;\n}\n\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\n\nconst readBearerToken = (authorizationHeader?: string) => {\n if (!authorizationHeader?.startsWith('Bearer ')) {\n return undefined;\n }\n\n return authorizationHeader.slice('Bearer '.length).trim() || undefined;\n};\n\nexport const createMcpAuthMiddleware = (_authConfig: AuthConfig, mcpAdminAuth: McpAdminAuthPort): RequestHandler => {\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n const status = await mcpAdminAuth.getStatus();\n if (!status.enabled) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_DISABLED',\n message: 'MCP access is disabled by admin.',\n })\n .end();\n return;\n }\n\n const bearerToken = readBearerToken(req.headers.authorization);\n\n if (!bearerToken) {\n res.status(401)\n .set(JSON_HEADERS)\n .json({\n code: 'UNAUTHORIZED',\n message: 'A valid MCP bearer token is required.',\n })\n .end();\n return;\n }\n\n const validation = await mcpAdminAuth.validatePresentedToken(bearerToken);\n\n if (!validation.ok && validation.reason === 'not_configured') {\n res.status(503)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_AUTH_NOT_CONFIGURED',\n message: 'MCP bearer auth is not configured.',\n })\n .end();\n return;\n }\n\n if (!validation.ok) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'FORBIDDEN',\n message: 'Invalid MCP bearer token.',\n })\n .end();\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n };\n};\n\nexport const createReadOnlyMcpValidationRule = (): ValidationRule => {\n return (context) => {\n return {\n OperationDefinition(node) {\n if (node.operation === 'query') {\n return;\n }\n\n context.reportError(\n new GraphQLError('MCP endpoint is read-only', {\n nodes: [node],\n extensions: { code: 'FORBIDDEN' },\n }),\n );\n },\n };\n };\n};\n"],"mappings":"AAEA,SAAS,oBAAoB;
|
|
1
|
+
{"version":3,"sources":["../../src/modules/mcp-auth.ts"],"sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport type { ValidationRule } from 'graphql';\nimport { GraphQLError } from 'graphql';\n\nimport {\n getOceanBrainVersionInfo,\n OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER,\n OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,\n parseMajorMinorVersion,\n} from './app-version.js';\nimport type { AuthConfig } from './auth-mode.js';\n\nexport interface McpTokenValidationResult {\n ok: boolean;\n reason?: 'not_configured' | 'forbidden';\n}\n\nexport interface McpAdminAuthPort {\n getStatus: () => Promise<{ enabled: boolean }>;\n validatePresentedToken: (token: string) => Promise<McpTokenValidationResult>;\n}\n\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\nexport const OCEAN_BRAIN_MCP_VERSION_HEADER = 'X-Ocean-Brain-MCP-Version';\n\nconst readHeaderValue = (req: Request, headerName: string) => {\n const value = req.headers[headerName.toLowerCase()];\n return Array.isArray(value) ? value[0] : value;\n};\n\nconst readMcpCompatibilityVersion = (req: Request) =>\n readHeaderValue(req, OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER) ??\n readHeaderValue(req, OCEAN_BRAIN_MCP_VERSION_HEADER);\n\nconst readMcpClientVersion = (req: Request) =>\n readHeaderValue(req, OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER) ?? readHeaderValue(req, OCEAN_BRAIN_MCP_VERSION_HEADER);\n\nexport const isMcpVersionCompatible = (requiredMcpCompatibilityVersion: string, mcpCompatibilityVersion: string) => {\n const required = parseMajorMinorVersion(requiredMcpCompatibilityVersion);\n const mcp = parseMajorMinorVersion(mcpCompatibilityVersion);\n\n return Boolean(required && mcp && required.major === mcp.major && required.minor === mcp.minor);\n};\n\nconst createMcpVersionCompatibilityMessage = ({\n mcpClientVersion,\n mcpCompatibilityVersion,\n requiredMcpCompatibilityVersion,\n serverVersion,\n}: {\n mcpClientVersion?: string;\n mcpCompatibilityVersion?: string;\n requiredMcpCompatibilityVersion: string;\n serverVersion: string;\n}) => {\n const mcpCompatibilityVersionText = mcpCompatibilityVersion ? `v${mcpCompatibilityVersion}` : 'not provided';\n const mcpClientVersionText = mcpClientVersion ? `v${mcpClientVersion}` : 'not provided';\n\n return [\n 'Ocean Brain MCP compatibility versions are incompatible.',\n `Server: v${serverVersion}`,\n `Required MCP compatibility: ${requiredMcpCompatibilityVersion}`,\n `MCP compatibility: ${mcpCompatibilityVersionText}`,\n `MCP client: ${mcpClientVersionText}`,\n '',\n 'Please update Ocean Brain MCP to a compatible release.',\n getOceanBrainVersionInfo().releaseUrl,\n ].join('\\n');\n};\n\nconst readBearerToken = (authorizationHeader?: string) => {\n if (!authorizationHeader?.startsWith('Bearer ')) {\n return undefined;\n }\n\n return authorizationHeader.slice('Bearer '.length).trim() || undefined;\n};\n\nexport const createMcpAuthMiddleware = (_authConfig: AuthConfig, mcpAdminAuth: McpAdminAuthPort): RequestHandler => {\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n const status = await mcpAdminAuth.getStatus();\n if (!status.enabled) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_DISABLED',\n message: 'MCP access is disabled by admin.',\n })\n .end();\n return;\n }\n\n const bearerToken = readBearerToken(req.headers.authorization);\n\n if (!bearerToken) {\n res.status(401)\n .set(JSON_HEADERS)\n .json({\n code: 'UNAUTHORIZED',\n message: 'A valid MCP bearer token is required.',\n })\n .end();\n return;\n }\n\n const validation = await mcpAdminAuth.validatePresentedToken(bearerToken);\n\n if (!validation.ok && validation.reason === 'not_configured') {\n res.status(503)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_AUTH_NOT_CONFIGURED',\n message: 'MCP bearer auth is not configured.',\n })\n .end();\n return;\n }\n\n if (!validation.ok) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'FORBIDDEN',\n message: 'Invalid MCP bearer token.',\n })\n .end();\n return;\n }\n\n const versionInfo = getOceanBrainVersionInfo();\n const mcpCompatibilityVersion = readMcpCompatibilityVersion(req);\n const mcpClientVersion = readMcpClientVersion(req);\n\n if (\n !mcpCompatibilityVersion ||\n !isMcpVersionCompatible(versionInfo.mcp.compatibilityVersion, mcpCompatibilityVersion)\n ) {\n res.status(426)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_VERSION_INCOMPATIBLE',\n message: createMcpVersionCompatibilityMessage({\n serverVersion: versionInfo.version,\n requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,\n mcpCompatibilityVersion,\n mcpClientVersion,\n }),\n serverVersion: versionInfo.version,\n mcpVersion: mcpClientVersion ?? mcpCompatibilityVersion ?? null,\n mcpClientVersion: mcpClientVersion ?? null,\n mcpCompatibilityVersion: mcpCompatibilityVersion ?? null,\n requiredMcpVersion: versionInfo.mcpVersionRequirement,\n requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,\n releaseUrl: versionInfo.releaseUrl,\n })\n .end();\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n };\n};\n\nexport const createReadOnlyMcpValidationRule = (): ValidationRule => {\n return (context) => {\n return {\n OperationDefinition(node) {\n if (node.operation === 'query') {\n return;\n }\n\n context.reportError(\n new GraphQLError('MCP endpoint is read-only', {\n nodes: [node],\n extensions: { code: 'FORBIDDEN' },\n }),\n );\n },\n };\n };\n};\n"],"mappings":"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","names":[]}
|
package/server/dist/paths.js
CHANGED
|
@@ -4,6 +4,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
4
4
|
const PACKAGE_ROOT = process.env.OCEAN_BRAIN_PACKAGE_ROOT || path.resolve(__dirname, "..");
|
|
5
5
|
const IMAGE_DIR = process.env.OCEAN_BRAIN_IMAGE_DIR || path.resolve(process.env.OCEAN_BRAIN_DATA_DIR || ".", "assets/images");
|
|
6
6
|
const paths = {
|
|
7
|
+
packageRoot: path.resolve(PACKAGE_ROOT),
|
|
7
8
|
clientDist: path.resolve(PACKAGE_ROOT, "client/dist"),
|
|
8
9
|
clientIndex: path.resolve(PACKAGE_ROOT, "client/dist/index.html"),
|
|
9
10
|
imageDir: path.resolve(IMAGE_DIR)
|
package/server/dist/paths.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/paths.ts"],"sourcesContent":["import path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// 패키지 루트 (dist/paths.js 기준 상위)\nconst PACKAGE_ROOT = process.env.OCEAN_BRAIN_PACKAGE_ROOT || path.resolve(__dirname, '..');\n\n// 이미지 디렉토리 (별도 지정 가능, Docker에서 분리 볼륨용)\nconst IMAGE_DIR =\n process.env.OCEAN_BRAIN_IMAGE_DIR || path.resolve(process.env.OCEAN_BRAIN_DATA_DIR || '.', 'assets/images');\n\nexport const paths = {\n clientDist: path.resolve(PACKAGE_ROOT, 'client/dist'),\n clientIndex: path.resolve(PACKAGE_ROOT, 'client/dist/index.html'),\n imageDir: path.resolve(IMAGE_DIR),\n};\n"],"mappings":"AAAA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAE9B,MAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAG7D,MAAM,eAAe,QAAQ,IAAI,4BAA4B,KAAK,QAAQ,WAAW,IAAI;AAGzF,MAAM,YACF,QAAQ,IAAI,yBAAyB,KAAK,QAAQ,QAAQ,IAAI,wBAAwB,KAAK,eAAe;AAEvG,MAAM,QAAQ;AAAA,EACjB,YAAY,KAAK,QAAQ,cAAc,aAAa;AAAA,EACpD,aAAa,KAAK,QAAQ,cAAc,wBAAwB;AAAA,EAChE,UAAU,KAAK,QAAQ,SAAS;AACpC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/paths.ts"],"sourcesContent":["import path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// 패키지 루트 (dist/paths.js 기준 상위)\nconst PACKAGE_ROOT = process.env.OCEAN_BRAIN_PACKAGE_ROOT || path.resolve(__dirname, '..');\n\n// 이미지 디렉토리 (별도 지정 가능, Docker에서 분리 볼륨용)\nconst IMAGE_DIR =\n process.env.OCEAN_BRAIN_IMAGE_DIR || path.resolve(process.env.OCEAN_BRAIN_DATA_DIR || '.', 'assets/images');\n\nexport const paths = {\n packageRoot: path.resolve(PACKAGE_ROOT),\n clientDist: path.resolve(PACKAGE_ROOT, 'client/dist'),\n clientIndex: path.resolve(PACKAGE_ROOT, 'client/dist/index.html'),\n imageDir: path.resolve(IMAGE_DIR),\n};\n"],"mappings":"AAAA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAE9B,MAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAG7D,MAAM,eAAe,QAAQ,IAAI,4BAA4B,KAAK,QAAQ,WAAW,IAAI;AAGzF,MAAM,YACF,QAAQ,IAAI,yBAAyB,KAAK,QAAQ,QAAQ,IAAI,wBAAwB,KAAK,eAAe;AAEvG,MAAM,QAAQ;AAAA,EACjB,aAAa,KAAK,QAAQ,YAAY;AAAA,EACtC,YAAY,KAAK,QAAQ,cAAc,aAAa;AAAA,EACpD,aAAa,KAAK,QAAQ,cAAc,wBAAwB;AAAA,EAChE,UAAU,KAAK,QAAQ,SAAS;AACpC;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Router } from "express";
|
|
2
2
|
import { createLoginHandler, createLogoutHandler, createSessionStatusHandler } from "../features/auth/http/api.js";
|
|
3
|
-
import {
|
|
3
|
+
import { createUploadImageHandler } from "../features/image/http/upload.js";
|
|
4
4
|
import {
|
|
5
5
|
createMcpAdminRevokeTokenHandler,
|
|
6
6
|
createMcpAdminRotateTokenHandler,
|
|
@@ -46,13 +46,7 @@ const createApiRouter = (authConfig, mcpAdminService) => {
|
|
|
46
46
|
requireSession,
|
|
47
47
|
csrfProtection,
|
|
48
48
|
useAsync(createMcpAdminRevokeTokenHandler(mcpAdminService))
|
|
49
|
-
).post("/image", sessionAccessRateLimit, requireSession, csrfProtection, useAsync(createUploadImageHandler())).
|
|
50
|
-
"/image-from-src",
|
|
51
|
-
sessionAccessRateLimit,
|
|
52
|
-
requireSession,
|
|
53
|
-
csrfProtection,
|
|
54
|
-
useAsync(createUploadImageFromSrcHandler())
|
|
55
|
-
).get("/events", sessionAccessRateLimit, requireSession, csrfProtection, createServerEventsHandler());
|
|
49
|
+
).post("/image", sessionAccessRateLimit, requireSession, csrfProtection, useAsync(createUploadImageHandler())).get("/events", sessionAccessRateLimit, requireSession, csrfProtection, createServerEventsHandler());
|
|
56
50
|
};
|
|
57
51
|
export {
|
|
58
52
|
createApiRouter
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/routes/api.ts"],"sourcesContent":["import { Router } from 'express';\nimport { createLoginHandler, createLogoutHandler, createSessionStatusHandler } from '../features/auth/http/api.js';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/routes/api.ts"],"sourcesContent":["import { Router } from 'express';\nimport { createLoginHandler, createLogoutHandler, createSessionStatusHandler } from '../features/auth/http/api.js';\nimport { createUploadImageHandler } from '../features/image/http/upload.js';\nimport {\n createMcpAdminRevokeTokenHandler,\n createMcpAdminRotateTokenHandler,\n createMcpAdminSetEnabledHandler,\n createMcpAdminStatusHandler,\n} from '../features/mcp-admin/http/handlers.js';\nimport type { McpAdminService } from '../features/mcp-admin/service.js';\nimport { createCsrfProtection, requireSessionForWrite } from '../modules/auth-guard.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { createAuthAttemptRateLimit, createSessionAccessRateLimit } from '../modules/rate-limit.js';\nimport { createServerEventsHandler } from '../modules/server-events-handler.js';\nimport useAsync from '../modules/use-async.js';\nimport { createMcpRouter } from './mcp.js';\n\ntype McpAdminApiService = Pick<\n McpAdminService,\n 'getStatus' | 'setEnabled' | 'rotateToken' | 'revokeActiveToken' | 'validatePresentedToken'\n>;\n\nexport const createApiRouter = (authConfig: AuthConfig, mcpAdminService: McpAdminApiService) => {\n const csrfProtection = createCsrfProtection(authConfig);\n const requireSession = requireSessionForWrite(authConfig);\n const sessionAccessRateLimit = createSessionAccessRateLimit();\n\n return Router()\n .use('/mcp', createMcpRouter(authConfig, mcpAdminService))\n .get('/auth/session', csrfProtection, useAsync(createSessionStatusHandler(authConfig)))\n .post('/auth/login', csrfProtection, createAuthAttemptRateLimit(), useAsync(createLoginHandler(authConfig)))\n .post(\n '/auth/logout',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createLogoutHandler(authConfig)),\n )\n .get(\n '/mcp-admin/status',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createMcpAdminStatusHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/enabled',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createMcpAdminSetEnabledHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/token/rotate',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createMcpAdminRotateTokenHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/token/revoke',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createMcpAdminRevokeTokenHandler(mcpAdminService)),\n )\n .post('/image', sessionAccessRateLimit, requireSession, csrfProtection, useAsync(createUploadImageHandler()))\n .get('/events', sessionAccessRateLimit, requireSession, csrfProtection, createServerEventsHandler());\n};\n"],"mappings":"AAAA,SAAS,cAAc;AACvB,SAAS,oBAAoB,qBAAqB,kCAAkC;AACpF,SAAS,gCAAgC;AACzC;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAEP,SAAS,sBAAsB,8BAA8B;AAE7D,SAAS,4BAA4B,oCAAoC;AACzE,SAAS,iCAAiC;AAC1C,OAAO,cAAc;AACrB,SAAS,uBAAuB;AAOzB,MAAM,kBAAkB,CAAC,YAAwB,oBAAwC;AAC5F,QAAM,iBAAiB,qBAAqB,UAAU;AACtD,QAAM,iBAAiB,uBAAuB,UAAU;AACxD,QAAM,yBAAyB,6BAA6B;AAE5D,SAAO,OAAO,EACT,IAAI,QAAQ,gBAAgB,YAAY,eAAe,CAAC,EACxD,IAAI,iBAAiB,gBAAgB,SAAS,2BAA2B,UAAU,CAAC,CAAC,EACrF,KAAK,eAAe,gBAAgB,2BAA2B,GAAG,SAAS,mBAAmB,UAAU,CAAC,CAAC,EAC1G;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,oBAAoB,UAAU,CAAC;AAAA,EAC5C,EACC;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,4BAA4B,eAAe,CAAC;AAAA,EACzD,EACC;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,gCAAgC,eAAe,CAAC;AAAA,EAC7D,EACC;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,iCAAiC,eAAe,CAAC;AAAA,EAC9D,EACC;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,iCAAiC,eAAe,CAAC;AAAA,EAC9D,EACC,KAAK,UAAU,wBAAwB,gBAAgB,gBAAgB,SAAS,yBAAyB,CAAC,CAAC,EAC3G,IAAI,WAAW,wBAAwB,gBAAgB,gBAAgB,0BAA0B,CAAC;AAC3G;","names":[]}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import express, { Router } from "express";
|
|
2
|
+
import { rateLimit } from "express-rate-limit";
|
|
2
3
|
import path from "path";
|
|
3
4
|
import { createCsrfProtection, isAuthenticatedRequest } from "../modules/auth-guard.js";
|
|
4
5
|
import { paths } from "../paths.js";
|
|
6
|
+
const IMAGE_ASSET_RATE_LIMIT_MESSAGE = "Too many image asset requests. Please try again later.";
|
|
5
7
|
const shouldBlockClientRoute = (authConfig, requestPath, authenticated) => {
|
|
6
8
|
if (authConfig.mode !== "password" || authenticated) {
|
|
7
9
|
return false;
|
|
@@ -11,6 +13,35 @@ const shouldBlockClientRoute = (authConfig, requestPath, authenticated) => {
|
|
|
11
13
|
}
|
|
12
14
|
return path.extname(requestPath) === "";
|
|
13
15
|
};
|
|
16
|
+
const createImageAssetAuthRateLimit = (authConfig) => rateLimit({
|
|
17
|
+
windowMs: 15 * 60 * 1e3,
|
|
18
|
+
limit: 10,
|
|
19
|
+
standardHeaders: true,
|
|
20
|
+
legacyHeaders: false,
|
|
21
|
+
skip: (req) => authConfig.mode !== "password" || isAuthenticatedRequest(req),
|
|
22
|
+
handler: (_req, res) => {
|
|
23
|
+
res.setHeader("Cache-Control", "no-store");
|
|
24
|
+
res.status(429).json({
|
|
25
|
+
code: "IMAGE_ASSET_RATE_LIMITED",
|
|
26
|
+
message: IMAGE_ASSET_RATE_LIMIT_MESSAGE
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
const createProtectedImageAssetsMiddleware = (authConfig) => {
|
|
31
|
+
return (req, res, next) => {
|
|
32
|
+
if (authConfig.mode !== "password" || isAuthenticatedRequest(req)) {
|
|
33
|
+
next();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
res.setHeader("Cache-Control", "no-store");
|
|
37
|
+
if (req.headers.accept?.includes("text/html")) {
|
|
38
|
+
const redirectPath = encodeURIComponent(req.originalUrl || req.url || "/");
|
|
39
|
+
res.redirect(303, `/login?next=${redirectPath}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
res.status(401).end();
|
|
43
|
+
};
|
|
44
|
+
};
|
|
14
45
|
const createClientRouteCsrfTokenMiddleware = (authConfig) => {
|
|
15
46
|
const csrfProtection = createCsrfProtection(authConfig);
|
|
16
47
|
return (req, res, next) => {
|
|
@@ -21,7 +52,26 @@ const createClientRouteCsrfTokenMiddleware = (authConfig) => {
|
|
|
21
52
|
csrfProtection(req, res, next);
|
|
22
53
|
};
|
|
23
54
|
};
|
|
24
|
-
const createClientRouter = (authConfig) => Router().use(
|
|
55
|
+
const createClientRouter = (authConfig) => Router().use(
|
|
56
|
+
"/assets/images",
|
|
57
|
+
createImageAssetAuthRateLimit(authConfig),
|
|
58
|
+
createProtectedImageAssetsMiddleware(authConfig),
|
|
59
|
+
express.static(paths.imageDir, {
|
|
60
|
+
setHeaders: (res) => {
|
|
61
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
62
|
+
if (authConfig.mode === "password") {
|
|
63
|
+
res.setHeader("Cache-Control", "no-store");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}),
|
|
67
|
+
(_req, res) => {
|
|
68
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
69
|
+
if (authConfig.mode === "password") {
|
|
70
|
+
res.setHeader("Cache-Control", "no-store");
|
|
71
|
+
}
|
|
72
|
+
res.status(404).end();
|
|
73
|
+
}
|
|
74
|
+
).use((req, res, next) => {
|
|
25
75
|
if (shouldBlockClientRoute(authConfig, req.path, isAuthenticatedRequest(req))) {
|
|
26
76
|
const redirectPath = encodeURIComponent(req.originalUrl || "/");
|
|
27
77
|
res.redirect(303, `/login?next=${redirectPath}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/routes/client.ts"],"sourcesContent":["import express, { Router } from 'express';\nimport path from 'path';\nimport { createCsrfProtection, isAuthenticatedRequest } from '../modules/auth-guard.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { paths } from '../paths.js';\n\nconst shouldBlockClientRoute = (authConfig: AuthConfig, requestPath: string, authenticated: boolean) => {\n if (authConfig.mode !== 'password' || authenticated) {\n return false;\n }\n\n if (\n requestPath.startsWith('/api') ||\n requestPath.startsWith('/graphql') ||\n requestPath === '/login' ||\n requestPath === '/logout'\n ) {\n return false;\n }\n\n return path.extname(requestPath) === '';\n};\n\nconst createClientRouteCsrfTokenMiddleware = (authConfig: AuthConfig) => {\n const csrfProtection = createCsrfProtection(authConfig);\n\n return (req: express.Request, res: express.Response, next: express.NextFunction) => {\n if (path.extname(req.path) !== '') {\n next();\n return;\n }\n\n csrfProtection(req, res, next);\n };\n};\n\nexport const createClientRouter = (authConfig: AuthConfig) =>\n Router()\n .use('/assets/images'
|
|
1
|
+
{"version":3,"sources":["../../src/routes/client.ts"],"sourcesContent":["import express, { type RequestHandler, Router } from 'express';\nimport { rateLimit } from 'express-rate-limit';\nimport path from 'path';\nimport { createCsrfProtection, isAuthenticatedRequest } from '../modules/auth-guard.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { paths } from '../paths.js';\n\nconst IMAGE_ASSET_RATE_LIMIT_MESSAGE = 'Too many image asset requests. Please try again later.';\n\nconst shouldBlockClientRoute = (authConfig: AuthConfig, requestPath: string, authenticated: boolean) => {\n if (authConfig.mode !== 'password' || authenticated) {\n return false;\n }\n\n if (\n requestPath.startsWith('/api') ||\n requestPath.startsWith('/graphql') ||\n requestPath === '/login' ||\n requestPath === '/logout'\n ) {\n return false;\n }\n\n return path.extname(requestPath) === '';\n};\n\nconst createImageAssetAuthRateLimit = (authConfig: AuthConfig) =>\n rateLimit({\n windowMs: 15 * 60 * 1000,\n limit: 10,\n standardHeaders: true,\n legacyHeaders: false,\n skip: (req) => authConfig.mode !== 'password' || isAuthenticatedRequest(req),\n handler: (_req, res) => {\n res.setHeader('Cache-Control', 'no-store');\n res.status(429).json({\n code: 'IMAGE_ASSET_RATE_LIMITED',\n message: IMAGE_ASSET_RATE_LIMIT_MESSAGE,\n });\n },\n });\n\nconst createProtectedImageAssetsMiddleware = (authConfig: AuthConfig): RequestHandler => {\n return (req, res, next) => {\n if (authConfig.mode !== 'password' || isAuthenticatedRequest(req)) {\n next();\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n\n if (req.headers.accept?.includes('text/html')) {\n const redirectPath = encodeURIComponent(req.originalUrl || req.url || '/');\n res.redirect(303, `/login?next=${redirectPath}`);\n return;\n }\n\n res.status(401).end();\n };\n};\n\nconst createClientRouteCsrfTokenMiddleware = (authConfig: AuthConfig) => {\n const csrfProtection = createCsrfProtection(authConfig);\n\n return (req: express.Request, res: express.Response, next: express.NextFunction) => {\n if (path.extname(req.path) !== '') {\n next();\n return;\n }\n\n csrfProtection(req, res, next);\n };\n};\n\nexport const createClientRouter = (authConfig: AuthConfig) =>\n Router()\n .use(\n '/assets/images',\n createImageAssetAuthRateLimit(authConfig),\n createProtectedImageAssetsMiddleware(authConfig),\n express.static(paths.imageDir, {\n setHeaders: (res) => {\n res.setHeader('X-Content-Type-Options', 'nosniff');\n\n if (authConfig.mode === 'password') {\n res.setHeader('Cache-Control', 'no-store');\n }\n },\n }),\n (_req, res) => {\n res.setHeader('X-Content-Type-Options', 'nosniff');\n\n if (authConfig.mode === 'password') {\n res.setHeader('Cache-Control', 'no-store');\n }\n\n res.status(404).end();\n },\n )\n .use((req, res, next) => {\n if (shouldBlockClientRoute(authConfig, req.path, isAuthenticatedRequest(req))) {\n const redirectPath = encodeURIComponent(req.originalUrl || '/');\n res.redirect(303, `/login?next=${redirectPath}`);\n return;\n }\n\n next();\n })\n .use(createClientRouteCsrfTokenMiddleware(authConfig))\n .use(express.static(paths.clientDist, { extensions: ['html'] }))\n .get(/.*/, (_req, res) => {\n res.sendFile(paths.clientIndex);\n });\n"],"mappings":"AAAA,OAAO,WAAgC,cAAc;AACrD,SAAS,iBAAiB;AAC1B,OAAO,UAAU;AACjB,SAAS,sBAAsB,8BAA8B;AAE7D,SAAS,aAAa;AAEtB,MAAM,iCAAiC;AAEvC,MAAM,yBAAyB,CAAC,YAAwB,aAAqB,kBAA2B;AACpG,MAAI,WAAW,SAAS,cAAc,eAAe;AACjD,WAAO;AAAA,EACX;AAEA,MACI,YAAY,WAAW,MAAM,KAC7B,YAAY,WAAW,UAAU,KACjC,gBAAgB,YAChB,gBAAgB,WAClB;AACE,WAAO;AAAA,EACX;AAEA,SAAO,KAAK,QAAQ,WAAW,MAAM;AACzC;AAEA,MAAM,gCAAgC,CAAC,eACnC,UAAU;AAAA,EACN,UAAU,KAAK,KAAK;AAAA,EACpB,OAAO;AAAA,EACP,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,MAAM,CAAC,QAAQ,WAAW,SAAS,cAAc,uBAAuB,GAAG;AAAA,EAC3E,SAAS,CAAC,MAAM,QAAQ;AACpB,QAAI,UAAU,iBAAiB,UAAU;AACzC,QAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MACjB,MAAM;AAAA,MACN,SAAS;AAAA,IACb,CAAC;AAAA,EACL;AACJ,CAAC;AAEL,MAAM,uCAAuC,CAAC,eAA2C;AACrF,SAAO,CAAC,KAAK,KAAK,SAAS;AACvB,QAAI,WAAW,SAAS,cAAc,uBAAuB,GAAG,GAAG;AAC/D,WAAK;AACL;AAAA,IACJ;AAEA,QAAI,UAAU,iBAAiB,UAAU;AAEzC,QAAI,IAAI,QAAQ,QAAQ,SAAS,WAAW,GAAG;AAC3C,YAAM,eAAe,mBAAmB,IAAI,eAAe,IAAI,OAAO,GAAG;AACzE,UAAI,SAAS,KAAK,eAAe,YAAY,EAAE;AAC/C;AAAA,IACJ;AAEA,QAAI,OAAO,GAAG,EAAE,IAAI;AAAA,EACxB;AACJ;AAEA,MAAM,uCAAuC,CAAC,eAA2B;AACrE,QAAM,iBAAiB,qBAAqB,UAAU;AAEtD,SAAO,CAAC,KAAsB,KAAuB,SAA+B;AAChF,QAAI,KAAK,QAAQ,IAAI,IAAI,MAAM,IAAI;AAC/B,WAAK;AACL;AAAA,IACJ;AAEA,mBAAe,KAAK,KAAK,IAAI;AAAA,EACjC;AACJ;AAEO,MAAM,qBAAqB,CAAC,eAC/B,OAAO,EACF;AAAA,EACG;AAAA,EACA,8BAA8B,UAAU;AAAA,EACxC,qCAAqC,UAAU;AAAA,EAC/C,QAAQ,OAAO,MAAM,UAAU;AAAA,IAC3B,YAAY,CAAC,QAAQ;AACjB,UAAI,UAAU,0BAA0B,SAAS;AAEjD,UAAI,WAAW,SAAS,YAAY;AAChC,YAAI,UAAU,iBAAiB,UAAU;AAAA,MAC7C;AAAA,IACJ;AAAA,EACJ,CAAC;AAAA,EACD,CAAC,MAAM,QAAQ;AACX,QAAI,UAAU,0BAA0B,SAAS;AAEjD,QAAI,WAAW,SAAS,YAAY;AAChC,UAAI,UAAU,iBAAiB,UAAU;AAAA,IAC7C;AAEA,QAAI,OAAO,GAAG,EAAE,IAAI;AAAA,EACxB;AACJ,EACC,IAAI,CAAC,KAAK,KAAK,SAAS;AACrB,MAAI,uBAAuB,YAAY,IAAI,MAAM,uBAAuB,GAAG,CAAC,GAAG;AAC3E,UAAM,eAAe,mBAAmB,IAAI,eAAe,GAAG;AAC9D,QAAI,SAAS,KAAK,eAAe,YAAY,EAAE;AAC/C;AAAA,EACJ;AAEA,OAAK;AACT,CAAC,EACA,IAAI,qCAAqC,UAAU,CAAC,EACpD,IAAI,QAAQ,OAAO,MAAM,YAAY,EAAE,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,EAC9D,IAAI,MAAM,CAAC,MAAM,QAAQ;AACtB,MAAI,SAAS,MAAM,WAAW;AAClC,CAAC;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{j as n}from"./note-vendor-i1M2FSI2.js";import{p as m,T as d}from"./index-jIfkRdY_.js";import{a as e}from"./graph-vendor-CUxe67Lr.js";const p=new Map([["bold",e.createElement(e.Fragment,null,e.createElement("path",{d:"M108,84a16,16,0,1,1,16,16A16,16,0,0,1,108,84Zm128,44A108,108,0,1,1,128,20,108.12,108.12,0,0,1,236,128Zm-24,0a84,84,0,1,0-84,84A84.09,84.09,0,0,0,212,128Zm-72,36.68V132a20,20,0,0,0-20-20,12,12,0,0,0-4,23.32V168a20,20,0,0,0,20,20,12,12,0,0,0,4-23.32Z"}))],["duotone",e.createElement(e.Fragment,null,e.createElement("path",{d:"M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z",opacity:"0.2"}),e.createElement("path",{d:"M144,176a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176Zm88-48A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128ZM124,96a12,12,0,1,0-12-12A12,12,0,0,0,124,96Z"}))],["fill",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm-4,48a12,12,0,1,1-12,12A12,12,0,0,1,124,72Zm12,112a16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40a8,8,0,0,1,0,16Z"}))],["light",e.createElement(e.Fragment,null,e.createElement("path",{d:"M142,176a6,6,0,0,1-6,6,14,14,0,0,1-14-14V128a2,2,0,0,0-2-2,6,6,0,0,1,0-12,14,14,0,0,1,14,14v40a2,2,0,0,0,2,2A6,6,0,0,1,142,176ZM124,94a10,10,0,1,0-10-10A10,10,0,0,0,124,94Zm106,34A102,102,0,1,1,128,26,102.12,102.12,0,0,1,230,128Zm-12,0a90,90,0,1,0-90,90A90.1,90.1,0,0,0,218,128Z"}))],["regular",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"}))],["thin",e.createElement(e.Fragment,null,e.createElement("path",{d:"M140,176a4,4,0,0,1-4,4,12,12,0,0,1-12-12V128a4,4,0,0,0-4-4,4,4,0,0,1,0-8,12,12,0,0,1,12,12v40a4,4,0,0,0,4,4A4,4,0,0,1,140,176ZM124,92a8,8,0,1,0-8-8A8,8,0,0,0,124,92Zm104,36A100,100,0,1,1,128,28,100.11,100.11,0,0,1,228,128Zm-8,0a92,92,0,1,0-92,92A92.1,92.1,0,0,0,220,128Z"}))]]),g=new Map([["bold",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,20A108,108,0,1,0,236,128,108.12,108.12,0,0,0,128,20Zm0,192a84,84,0,1,1,84-84A84.09,84.09,0,0,1,128,212Zm-12-80V80a12,12,0,0,1,24,0v52a12,12,0,0,1-24,0Zm28,40a16,16,0,1,1-16-16A16,16,0,0,1,144,172Z"}))],["duotone",e.createElement(e.Fragment,null,e.createElement("path",{d:"M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z",opacity:"0.2"}),e.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z"}))],["fill",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm-8,56a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm8,104a12,12,0,1,1,12-12A12,12,0,0,1,128,184Z"}))],["light",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,26A102,102,0,1,0,230,128,102.12,102.12,0,0,0,128,26Zm0,192a90,90,0,1,1,90-90A90.1,90.1,0,0,1,128,218Zm-6-82V80a6,6,0,0,1,12,0v56a6,6,0,0,1-12,0Zm16,36a10,10,0,1,1-10-10A10,10,0,0,1,138,172Z"}))],["regular",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z"}))],["thin",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,28A100,100,0,1,0,228,128,100.11,100.11,0,0,0,128,28Zm0,192a92,92,0,1,1,92-92A92.1,92.1,0,0,1,128,220Zm-4-84V80a4,4,0,0,1,8,0v56a4,4,0,0,1-8,0Zm12,36a8,8,0,1,1-8-8A8,8,0,0,1,136,172Z"}))]]),l=e.forwardRef((a,t)=>e.createElement(m,{ref:t,...a,weights:p}));l.displayName="InfoIcon";const E=l,c=e.forwardRef((a,t)=>e.createElement(m,{ref:t,...a,weights:g}));c.displayName="WarningCircleIcon";const i=c,f=({children:a,className:t="",tone:r="info"})=>{const o=r==="danger"?i:E,Z=r==="danger"?"border-border-error/70 bg-accent-soft-danger/60":"border-border-subtle bg-hover-subtle/50",s=r==="danger"?"text-fg-error":"text-fg-tertiary",A=r==="danger"?"error":"secondary";return n.jsxs("div",{className:`flex items-center gap-2.5 rounded-[12px] border px-4 py-3 ${Z} ${t}`,children:[n.jsx(o,{className:`h-4 w-4 shrink-0 ${s}`}),n.jsx(d,{as:"div",variant:"meta",weight:"medium",tone:A,children:a})]})};export{f as C,i as m};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{j as t}from"./note-vendor-i1M2FSI2.js";import{a0 as _,P as C,a3 as U,i as G,a4 as J,a5 as Y,a6 as X,a7 as ee,q as ne,N as te,J as re,T as N,a8 as oe}from"./index-jIfkRdY_.js";import{a as i,F as se}from"./graph-vendor-CUxe67Lr.js";import"./note-core-Z6kqulGB.js";const ae=s=>{let a=0;for(let r=0;r<s.length;r++)a+=s.charCodeAt(r);return a},ie={background:"#f4f6f8",nodeHub:"#2c2f36",nodeSelected:"#111318",nodeConnected:"#636b76",nodeDefault:["#d6dbe1","#c8ced6","#b7bec7","#a7afb9"],nodeDimmed:["rgba(214,219,225,0.28)","rgba(200,206,214,0.24)","rgba(183,190,199,0.24)","rgba(167,175,185,0.2)"],nodeHubDimmed:"rgba(44,47,54,0.16)",nodeStroke:"#eef1f4",nodeSelectedStroke:"#a6b0bc",labelBackground:"rgba(246,248,250,0.92)",labelText:"#222831",labelFontFamily:"Pretendard Variable, Pretendard, system-ui, sans-serif",linkIdle:"rgba(99, 107, 117, 0.34)",linkConnected:"#68717c",linkDimmed:"rgba(127, 136, 146, 0.12)",legendHub:"#2c2f36"},le={background:"#121316",nodeHub:"#d6dce3",nodeSelected:"#eef1f5",nodeConnected:"#9099a4",nodeDefault:["#343a43","#2d333c","#262c35","#20262f"],nodeDimmed:["rgba(52,58,67,0.28)","rgba(45,51,60,0.24)","rgba(38,44,53,0.22)","rgba(32,38,47,0.2)"],nodeHubDimmed:"rgba(214,220,227,0.16)",nodeStroke:"#171c23",nodeSelectedStroke:"#7f8a97",labelBackground:"rgba(16,18,22,0.9)",labelText:"#eef2f6",labelFontFamily:"Pretendard Variable, Pretendard, system-ui, sans-serif",linkIdle:"rgba(118, 127, 138, 0.42)",linkConnected:"#a2abb6",linkDimmed:"rgba(118, 127, 138, 0.1)",legendHub:"#d6dce3"};function D(s){return s==="dark"?le:ie}function de(s,a){const r=D(s),{connections:g,colorIndex:y,selectedNodeId:d,nodeId:b,isConnected:c}=a,h=d===b;return d!==null&&!h&&!c?g>3?r.nodeHubDimmed:r.nodeDimmed[y%r.nodeDimmed.length]:h?r.nodeSelected:c?r.nodeConnected:g>3?r.nodeHub:r.nodeDefault[y%r.nodeDefault.length]}function ce(s,a){const r=D(s);return a.selectedNodeId!==null&&!a.isConnected?r.linkDimmed:a.isConnected?r.linkConnected:r.linkIdle}function ue(s,a){const r=D(s);return`${a.emphasize?"700":"400"} ${a.fontSize}px ${r.labelFontFamily}`}function z(s){return s<=1?3.5:s<=3?4.5:s<=6?5.5:Math.min(7,5.5+Math.sqrt(s)*.5)}const fe=t.jsx(C,{title:"Knowledge Graph",description:t.jsx(G,{width:184,height:16,className:"rounded-full"}),children:t.jsx("div",{className:"flex h-[600px] items-center justify-center",children:t.jsx(G,{width:"100%",height:"100%"})})});function he(){const s=J(),a=i.useRef(null),r=i.useRef(null),[g,y]=i.useState({width:800,height:600}),[d,b]=i.useState(null),{theme:c}=Y(e=>e),h=D(c),v=i.useRef(h);v.current=h;const{data:w}=X({queryKey:ne.notes.graph(),queryFn:async()=>{const e=await ee();if(e.type==="error")throw e;return e.noteGraph}}),l=i.useMemo(()=>{if(w.nodes.length===0)return null;const e=w.nodes.filter(o=>o.connections>0);if(e.length===0)return null;const n=new Set(e.map(o=>o.id));return{nodes:e,links:w.links.filter(o=>n.has(o.source)&&n.has(o.target))}},[w]);i.useEffect(()=>{if(!l)return;const e=()=>{if(!a.current)return;const n=a.current.getBoundingClientRect();y({width:n.width,height:Math.max(600,window.innerHeight-150)})};return e(),window.addEventListener("resize",e),()=>window.removeEventListener("resize",e)},[l]),i.useEffect(()=>{if(!l||!r.current)return;const e=window.setTimeout(()=>{r.current?.zoomToFit(400,50)},500);return()=>window.clearTimeout(e)},[l]);const R=i.useRef(d);R.current=d;const B=i.useCallback(e=>{if(R.current===e.id){s({to:te,params:{id:e.id}});return}b(e.id)},[s]),L=i.useCallback(()=>{b(null)},[]),K=i.useCallback(e=>{a.current&&(a.current.style.cursor=e?"pointer":"default")},[]),I=i.useRef(!1),q=i.useCallback(()=>{I.current||(I.current=!0,r.current?.enableZoomInteraction(!1))},[]),A=i.useCallback(()=>{I.current=!1,r.current?.enableZoomInteraction(!0)},[]),H=i.useRef(new Map),O=i.useMemo(()=>{const e=new Map;if(l)for(const n of l.links)e.has(n.source)||e.set(n.source,new Set),e.has(n.target)||e.set(n.target,new Set),e.get(n.source)?.add(n.target),e.get(n.target)?.add(n.source);return e},[l]);H.current=O;const Z=i.useCallback((e,n,o)=>{const u=v.current,f=d,p=H.current,m=z(e.connections),x=e.x||0,S=e.y||0,k=f===e.id,T=f?p.get(f)?.has(e.id)??!1:!1,V=f!==null&&!k&&!T,W=ae(e.id);if(n.beginPath(),n.arc(x,S,m,0,Math.PI*2),n.fillStyle=de(c,{connections:e.connections,colorIndex:W,selectedNodeId:f,nodeId:e.id,isConnected:T}),n.fill(),V)return;n.strokeStyle=u.nodeStroke,n.lineWidth=(k?2:1)/o,n.stroke(),k&&(n.beginPath(),n.arc(x,S,m+2/o,0,Math.PI*2),n.strokeStyle=u.nodeSelectedStroke,n.lineWidth=1.5/o,n.stroke());const Q=T&&e.connections>=4;if(k||Q||o>2.5){const P=e.title||"Untitled",E=Math.max(10/o,2.5);n.font=ue(c,{fontSize:E,emphasize:k}),n.textAlign="center",n.textBaseline="top";const M=n.measureText(P).width,j=2/o,F=S+m+3/o;n.fillStyle=u.labelBackground,n.fillRect(x-M/2-j,F,M+j*2,E+j*2),n.fillStyle=u.labelText,n.fillText(P,x,F+j)}},[d,c]),$=i.useCallback((e,n,o)=>{const u=d,f=e.source,p=e.target,m=u?f.id===u||p.id===u:!1;n.beginPath(),n.moveTo(f.x||0,f.y||0),n.lineTo(p.x||0,p.y||0),n.strokeStyle=ce(c,{selectedNodeId:u,isConnected:m}),n.lineWidth=m?2/o:.5/o,n.stroke()},[d,c]);return l?t.jsx(C,{title:"Knowledge Graph",description:`${l.nodes.length} linked notes, ${l.links.length} connections`,children:t.jsxs("div",{ref:a,className:"surface-base graph-canvas relative overflow-hidden",style:{"--graph-bg":h.background},children:[d&&(()=>{const e=l.nodes.find(n=>n.id===d);return e?t.jsxs("div",{className:"surface-floating absolute top-3 left-3 z-10 flex items-center gap-2 px-3 py-2",children:[t.jsx(N,{as:"span",variant:"meta",weight:"semibold",truncate:!0,className:"max-w-48",children:e.title}),t.jsxs(N,{as:"span",variant:"label",tone:"tertiary",children:[e.connections," links"]}),t.jsx("button",{type:"button",onClick:()=>b(null),className:"focus-ring-soft ml-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-[8px] text-fg-tertiary transition-colors hover:bg-hover-subtle hover:text-fg-default","aria-label":"Deselect node",children:t.jsx(oe,{className:"h-3.5 w-3.5"})})]}):null})(),t.jsxs("div",{className:"surface-floating absolute top-3 right-3 z-10 flex flex-col gap-1.5 px-3 py-2.5",children:[t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx("span",{className:"legend-dot h-2.5 w-2.5 shrink-0 rounded-full",style:{"--legend-color":h.legendHub}}),t.jsx(N,{as:"span",variant:"label",weight:"medium",tone:"secondary",children:"Hub notes (4+ connections)"})]}),t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx("span",{className:"legend-dot h-2.5 w-2.5 shrink-0 rounded-full",style:{"--legend-color":h.nodeConnected}}),t.jsx(N,{as:"span",variant:"label",weight:"medium",tone:"secondary",children:"Connected notes"})]})]}),t.jsx(se,{ref:r,graphData:l,width:g.width,height:g.height,nodeId:"id",nodeLabel:"",nodeCanvasObject:Z,nodePointerAreaPaint:(e,n,o)=>{o.beginPath(),o.arc(e.x||0,e.y||0,Math.max(z(e.connections)+4,10),0,2*Math.PI),o.fillStyle=n,o.fill()},linkCanvasObject:$,linkCanvasObjectMode:()=>"replace",linkDirectionalParticles:0,onNodeClick:B,onNodeHover:K,onBackgroundClick:L,onNodeDrag:q,onNodeDragEnd:A,warmupTicks:30,cooldownTicks:80,d3AlphaDecay:.05,d3VelocityDecay:.3,enableZoomInteraction:!0,enablePanInteraction:!0,minZoom:.3,maxZoom:5})]})}):t.jsx(C,{title:"Knowledge Graph",description:"0 linked notes, 0 connections",children:t.jsx(re,{title:"No constellations yet",description:"Link your notes together and watch your own starry sky unfold"})})}function ke(){return t.jsx(_,{fallback:fe,errorTitle:"Failed to load graph",errorDescription:"Retry loading your linked note constellation",renderError:({error:s,retry:a})=>t.jsx(C,{title:"Knowledge Graph",children:t.jsx(U,{title:"Failed to load graph",description:"Retry loading your linked note constellation",error:s,onRetry:a})}),children:t.jsx(he,{})})}export{ke as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a}from"./graph-vendor-CUxe67Lr.js";import{p as m}from"./index-jIfkRdY_.js";const n=new Map([["bold",a.createElement(a.Fragment,null,a.createElement("path",{d:"M144,96a16,16,0,1,1,16,16A16,16,0,0,1,144,96Zm92-40V200a20,20,0,0,1-20,20H40a20,20,0,0,1-20-20V56A20,20,0,0,1,40,36H216A20,20,0,0,1,236,56ZM44,60v79.72l33.86-33.86a20,20,0,0,1,28.28,0L147.31,147l17.18-17.17a20,20,0,0,1,28.28,0L212,149.09V60Zm0,136H162.34L92,125.66l-48,48Zm168,0V183l-33.37-33.37L164.28,164l32,32Z"}))],["duotone",a.createElement(a.Fragment,null,a.createElement("path",{d:"M224,56V178.06l-39.72-39.72a8,8,0,0,0-11.31,0L147.31,164,97.66,114.34a8,8,0,0,0-11.32,0L32,168.69V56a8,8,0,0,1,8-8H216A8,8,0,0,1,224,56Z",opacity:"0.2"}),a.createElement("path",{d:"M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,16V158.75l-26.07-26.06a16,16,0,0,0-22.63,0l-20,20-44-44a16,16,0,0,0-22.62,0L40,149.37V56ZM40,172l52-52,80,80H40Zm176,28H194.63l-36-36,20-20L216,181.38V200ZM144,100a12,12,0,1,1,12,12A12,12,0,0,1,144,100Z"}))],["fill",a.createElement(a.Fragment,null,a.createElement("path",{d:"M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40ZM156,88a12,12,0,1,1-12,12A12,12,0,0,1,156,88Zm60,112H40V160.69l46.34-46.35a8,8,0,0,1,11.32,0h0L165,181.66a8,8,0,0,0,11.32-11.32l-17.66-17.65L173,138.34a8,8,0,0,1,11.31,0L216,170.07V200Z"}))],["light",a.createElement(a.Fragment,null,a.createElement("path",{d:"M216,42H40A14,14,0,0,0,26,56V200a14,14,0,0,0,14,14H216a14,14,0,0,0,14-14V56A14,14,0,0,0,216,42ZM40,54H216a2,2,0,0,1,2,2V163.57L188.53,134.1a14,14,0,0,0-19.8,0l-21.42,21.42L101.9,110.1a14,14,0,0,0-19.8,0L38,154.2V56A2,2,0,0,1,40,54ZM38,200V171.17l52.58-52.58a2,2,0,0,1,2.84,0L176.83,202H40A2,2,0,0,1,38,200Zm178,2H193.8l-38-38,21.41-21.42a2,2,0,0,1,2.83,0l38,38V200A2,2,0,0,1,216,202ZM146,100a10,10,0,1,1,10,10A10,10,0,0,1,146,100Z"}))],["regular",a.createElement(a.Fragment,null,a.createElement("path",{d:"M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,16V158.75l-26.07-26.06a16,16,0,0,0-22.63,0l-20,20-44-44a16,16,0,0,0-22.62,0L40,149.37V56ZM40,172l52-52,80,80H40Zm176,28H194.63l-36-36,20-20L216,181.38V200ZM144,100a12,12,0,1,1,12,12A12,12,0,0,1,144,100Z"}))],["thin",a.createElement(a.Fragment,null,a.createElement("path",{d:"M216,44H40A12,12,0,0,0,28,56V200a12,12,0,0,0,12,12H216a12,12,0,0,0,12-12V56A12,12,0,0,0,216,44ZM40,52H216a4,4,0,0,1,4,4V168.4l-32.89-32.89a12,12,0,0,0-17,0l-22.83,22.83-46.82-46.83a12,12,0,0,0-17,0L36,159V56A4,4,0,0,1,40,52ZM36,200V170.34l53.17-53.17a4,4,0,0,1,5.66,0L181.66,204H40A4,4,0,0,1,36,200Zm180,4H193l-40-40,22.83-22.83a4,4,0,0,1,5.66,0L220,179.71V200A4,4,0,0,1,216,204ZM148,100a8,8,0,1,1,8,8A8,8,0,0,1,148,100Z"}))]]),e=a.forwardRef((l,t)=>a.createElement(m,{ref:t,...l,weights:n}));e.displayName="ImageIcon";const Z=e;export{Z as I};
|