Files
SRCmail/lib/collabora/client.ts
T
Bernd Rodler b98ab59f0d fix: Phase 2 QA — all 25 remaining HIGH/MEDIUM/LOW issues
HIGH fixes (7):
- H1: VNCdirectory admin i18n — 30+ translation keys added
- H2: handleSave try/catch with error toast
- H3: Free/busy accountId scoping
- H4: cancelEventBookings filter by eventId
- H5: Resource picker static apiFetch import
- H6: Sharing-store toast messages via lastMessage state
- H7: roleLabel for all resource types

MEDIUM fixes (11):
- M1: identitySignatureMap cleanup on delete
- M2: Now-line relative positioning
- M3: Radial menu disabled item keyboard nav
- M4: Radial menu stable event listener via refs
- M5: cancelBooking error on missing booking
- M6: PasswordRow isMasked state flag
- M7: Extract shared rights into lib/sharing-rights.ts
- M8: VNCtalk client server-side guard
- M9: Collabora configManager instead of process.env
- M10: CONFIG_ENV_MAP VNCdirectory fields
- M11: SENSITIVE_CONFIG_KEYS field name unification

LOW fixes (7):
- L1-L3: Unused imports removed
- L4: aria-labels on close, clear, search, spinner
- L5-L7: Comments for intentional patterns, null guard
2026-08-07 14:21:07 +02:00

90 lines
2.6 KiB
TypeScript

import { configManager } from "@/lib/admin/config-manager";
export async function getCollaboraEditUrl(
fileId: string,
fileName: string
): Promise<string> {
const serverUrl =
configManager.get<string>("collaboraServerUrl") ||
process.env.COLLABORA_SERVER_URL ||
"";
if (!serverUrl) {
throw new Error("COLLABORA_SERVER_URL is not configured");
}
const base = serverUrl.replace(/\/+$/, "");
const fileExt = fileName.split(".").pop()?.toLowerCase() || "";
// Collabora WOPI host discovery endpoint
const response = await fetch(`${base}/hosting/discovery`, {
headers: { Accept: "application/json" },
});
if (!response.ok) {
throw new Error(`Collabora discovery failed: ${response.status}`);
}
const discovery = await response.json();
// Find the WOPI action URL for the file extension
let actionUrl: string | null = null;
const mimeMap: Record<string, string> = {
docx: "text",
doc: "text",
odt: "text",
xlsx: "spreadsheet",
xls: "spreadsheet",
ods: "spreadsheet",
pptx: "presentation",
ppt: "presentation",
odp: "presentation",
};
const docType = mimeMap[fileExt] || "text";
if (discovery.net?.zone) {
const zones = Array.isArray(discovery.net.zone)
? discovery.net.zone
: [discovery.net.zone];
for (const zone of zones) {
const apps = Array.isArray(zone.app) ? zone.app : zone.app ? [zone.app] : [];
for (const app of apps) {
if (
app.name &&
docType &&
app.name.toLowerCase().includes(docType.toLowerCase())
) {
const actions = Array.isArray(app.action)
? app.action
: app.action
? [app.action]
: [];
for (const action of actions) {
if (action.name === "edit" && action.urlsrc) {
actionUrl = action.urlsrc;
break;
}
}
}
if (actionUrl) break;
}
if (actionUrl) break;
}
}
if (!actionUrl) {
// Fallback: construct URL manually
actionUrl = `${base}/loleaflet/dist/loleaflet.html`;
}
// For now, return the base edit URL. A full WOPI implementation would
// generate a WOPI src URL with an access token pointing back to this server.
const appUrl = configManager.get<string>("appUrl") || process.env.NEXT_PUBLIC_APP_URL;
const port = configManager.get<string>("port") || process.env.PORT || "3000";
const wopiSrcUrl = `${actionUrl}?WOPISrc=${encodeURIComponent(
`${appUrl || `http://localhost:${port}`}/api/collabora/wopi/files/${encodeURIComponent(fileId)}`
)}`;
return wopiSrcUrl;
}