Title: “Error while loading document in Collabora: Invalid URI and WebSocket closed errors”
Hi everyone,
I’m trying to integrate Collabora Online (CODE) with a custom backend that implements WOPI APIs.
I’m running into some issues while trying to load documents inside the iframe.
Here are the important parts of my backend implementation:
// GET file metadata
collaboraRouter.get('/files/:fileId', async (req, res) => {
try {
const { fileId } = req.params;
const { access_token } = req.query;
if (!access_token || !ACCESS_TOKENS.has(access_token)) {
return res.status(401).json({ error: 'Invalid access token' });
}
const tokenData = ACCESS_TOKENS.get(access_token);
if (tokenData.fileId !== fileId) {
return res.status(403).json({ error: 'Token not valid for this file' });
}
const file = bucket.file(`submissions/${fileId}`);
const [metadata] = await file.getMetadata();
res.json({
BaseFileName: fileId,
Size: parseInt(metadata.size),
OwnerId: "user1",
UserId: "user1",
Version: metadata.generation || '1',
LastModifiedTime: metadata.updated,
UserFriendlyName: "User",
UserCanWrite: true,
DisablePrint: false,
DisableExport: false,
DisableCopy: false,
HideSaveOption: false,
PostMessageOrigin: "http://localhost:5173"
});
} catch (error) {
console.error('[WOPI] Error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// GET file contents
collaboraRouter.get('/files/:fileId/contents', async (req, res) => {
try {
const { fileId } = req.params;
const { access_token } = req.query;
if (!access_token || !ACCESS_TOKENS.has(access_token)) {
return res.status(401).json({ error: 'Invalid access token' });
}
const file = bucket.file(`submissions/${fileId}`);
const stream = file.createReadStream();
stream.pipe(res);
} catch (error) {
console.error('[WOPI] Error streaming file:', error);
res.status(500).json({ error: 'Failed to stream file' });
}
});
// GET discovery XML
collaboraRouter.get('/hosting/discovery', async (req, res) => {
try {
console.log('Fetching discovery.xml from Collabora');
const response = await axios.get(`${COLLABORA_HOST}/hosting/discovery`);
res.set('Content-Type', 'application/xml');
res.send(response.data);
} catch (error) {
console.error('Error getting discovery XML:', error);
res.status(500).json({ error: 'Failed to get discovery XML' });
}
});
// GET file metadata
collaboraRouter.get('/files/:fileId', async (req, res) => {
try {
const { fileId } = req.params;
const { access_token } = req.query;
if (!access_token || !ACCESS_TOKENS.has(access_token)) {
return res.status(401).json({ error: 'Invalid access token' });
}
const tokenData = ACCESS_TOKENS.get(access_token);
if (tokenData.fileId !== fileId) {
return res.status(403).json({ error: 'Token not valid for this file' });
}
const file = bucket.file(`submissions/${fileId}`);
const [metadata] = await file.getMetadata();
res.json({
BaseFileName: fileId,
Size: parseInt(metadata.size),
OwnerId: "user1",
UserId: "user1",
Version: metadata.generation || '1',
LastModifiedTime: metadata.updated,
UserFriendlyName: "User",
UserCanWrite: true,
DisablePrint: false,
DisableExport: false,
DisableCopy: false,
HideSaveOption: false,
PostMessageOrigin: "http://localhost:5173"
});
} catch (error) {
console.error('[WOPI] Error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// GET file contents
collaboraRouter.get('/files/:fileId/contents', async (req, res) => {
try {
const { fileId } = req.params;
const { access_token } = req.query;
if (!access_token || !ACCESS_TOKENS.has(access_token)) {
return res.status(401).json({ error: 'Invalid access token' });
}
const file = bucket.file(`submissions/${fileId}`);
const stream = file.createReadStream();
stream.pipe(res);
} catch (error) {
console.error('[WOPI] Error streaming file:', error);
res.status(500).json({ error: 'Failed to stream file' });
}
});
// GET discovery XML
collaboraRouter.get('/hosting/discovery', async (req, res) => {
try {
console.log('Fetching discovery.xml from Collabora');
const response = await axios.get(`${COLLABORA_HOST}/hosting/discovery`);
res.set('Content-Type', 'application/xml');
res.send(response.data);
} catch (error) {
console.error('Error getting discovery XML:', error);
res.status(500).json({ error: 'Failed to get discovery XML' });
}
});
Frontend Console Logs:
Loading document with fileId: temp.docx
WOPI source URL: http://localhost:8080/api/wopi/files/temp.docx
Found action URL: http://localhost:9980/browser/.../cool.html?
Final iframe URL: http://localhost:9980/browser/.../cool.html?WOPISrc=http%3A%2F%2Flocalhost%3A8080%2Fapi%2Fwopi%2Ffiles%2Ftemp.docx&access_token=******
When the iframe tries to load, I get WebSocket connection errors:
WebSocket connection to ‘ws://localhost:9980/cool/http%3A%2F%2Flocalhost%3A8080%2Fapi%2Fwopi%2Ffiles%2Ftemp.docx%3Faccess_token%3D******_ttl%3D0%26permission%3Dedit/ws?WOPISrc=http%3A%2F%2Flocalhost%3A8080%2Fapi%2Fwopi%2Ffiles%2Ftemp.docx&compat=/ws’ failed: WebSocket is closed before the connection is established.
Collabora Server (Docker) Logs:
wsd-00001-00031 2025-04-23 16:48:11.844812 +0000 [ websrv_poll ] ERR #42: Error while handling Client WS Request: Invalid URI.| wsd/ClientRequestDispatcher.cpp:2259
wsd-00001-00031 2025-04-23 16:48:12.205977 +0000 [ websrv_poll ] ERR #43: Error while handling Client WS Request: Invalid URI.| wsd/ClientRequestDispatcher.cpp:2259
Summary:
- Discovery XML is fetched successfully.
- File metadata and file contents API endpoints seem to respond properly when tested individually.
- iframe URL is constructed and loaded, but WebSocket connections are failing.
- Docker container logs show
Invalid URI
errors. - Testing locally on
localhost
, Collabora server is atlocalhost:9980
, backend API is atlocalhost:8080
.
Environment Details:
- Backend: Node.js with Express
- Collabora CODE: Docker image (latest)
- Frontend: React
- Running everything on local environment (localhost)