feat: wire remote-desktop plugin into core and rename i18n keys

Repoints core consumers (dispatcher, collab, session-sharing, tab
utils) at the plugin, adds isTabTypeAvailable gating for rdp/vnc/
telnet, widens hostCapability to accept an array, and renames the
guacamole i18n namespace to remoteDesktop.
This commit is contained in:
LukeGus committed 2026-09-21 20:26:09 -05:00
1 parent c6bcfdb80d
commit 76b2e5739a
41 files changed
+361 -686

No files matched your search

+6 -5
View File
@@ -6,11 +6,12 @@
* tsc only emits .ts, so without a copy step the plugins directory simply
* would not exist in dist and the loader would find nothing.
*
* docker and host-metrics are the exception: their backends are real
* TypeScript, physically relocated from src/backend/hosts/docker/ and
* src/backend/hosts/metrics/ rather than kept as hand-written JS, because
* each is thousands of lines of typed SSH/session logic that is not worth
* hand-transpiling. tsconfig.plugins.json compiles each plugin backend's
* docker, host-metrics and remote-desktop are the exception: their backends
* are real TypeScript, physically relocated from src/backend/hosts/docker/,
* src/backend/hosts/metrics/ and src/backend/hosts/guacamole/ rather than
* kept as hand-written JS, because each is thousands of lines of typed
* SSH/session (or, for remote-desktop, guacd protocol) logic that is not
* worth hand-transpiling. tsconfig.plugins.json compiles each plugin backend's
* TypeScript to a sibling .js next to its source. Their imports into core
* (e.g. "../../../src/backend/utils/logger.js") are written relative to the
* TypeScript SOURCE tree so tsc can type-check them, since plugins/ is not
+26 -8
View File
@@ -154,14 +154,32 @@
}
},
"hostCapability": {
"type": "object",
"additionalProperties": false,
"required": ["key", "labelKey", "editorTab"],
"properties": {
"key": { "type": "string", "minLength": 1 },
"labelKey": { "type": "string", "minLength": 1 },
"editorTab": { "type": "string", "minLength": 1 }
}
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"required": ["key", "labelKey", "editorTab"],
"properties": {
"key": { "type": "string", "minLength": 1 },
"labelKey": { "type": "string", "minLength": 1 },
"editorTab": { "type": "string", "minLength": 1 }
}
},
{
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["key", "labelKey", "editorTab"],
"properties": {
"key": { "type": "string", "minLength": 1 },
"labelKey": { "type": "string", "minLength": 1 },
"editorTab": { "type": "string", "minLength": 1 }
}
}
}
]
},
"permissionGroup": {
"type": "object",
+19 -4
View File
@@ -396,11 +396,26 @@ function validateContributes(contributes) {
}
if ("hostCapability" in contributes) {
for (const field of ["key", "labelKey", "editorTab"]) {
if (!(field in (contributes.hostCapability || {}))) {
errors.push(`contributes.hostCapability.${field} is required`);
const raw = contributes.hostCapability;
const entries = Array.isArray(raw) ? raw : [raw];
const seenKeys = new Set();
entries.forEach((entry, index) => {
const hc = entry || {};
const prefix = Array.isArray(raw)
? `contributes.hostCapability[${index}]`
: "contributes.hostCapability";
for (const field of ["key", "labelKey", "editorTab"]) {
if (!(field in hc)) {
errors.push(`${prefix}.${field} is required`);
}
}
}
if (typeof hc.key === "string") {
if (seenKeys.has(hc.key)) {
errors.push(`${prefix}.key is a duplicate: "${hc.key}"`);
}
seenKeys.add(hc.key);
}
});
}
if ("permissionGroup" in contributes) {