Initial release v1.0.0 — Nextcloud Collabora Node-RED sidecar

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Hannes Ahlin
2026-08-22 14:56:38 +02:00
commit d2f70ee2a7
38 changed files with 2239 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
'use strict';
const { requestBuffer } = require('./http-client');
const { normalizeNcPath, filenameFromPath } = require('./utils');
function authHeader(config) {
const token = Buffer.from(`${config.username}:${config.password}`).toString('base64');
return `Basic ${token}`;
}
function webDavBase(config) {
const base = config.nextcloudUrl.replace(/\/$/, '');
return `${base}/remote.php/dav/files/${encodeURIComponent(config.username)}`;
}
function encodePathSegments(ncPath) {
const normalized = normalizeNcPath(ncPath);
return normalized
.split('/')
.filter(Boolean)
.map((segment) => encodeURIComponent(segment))
.join('/');
}
function webDavUrl(config, ncPath) {
const rel = encodePathSegments(ncPath);
return rel ? `${webDavBase(config)}/${rel}` : webDavBase(config);
}
function tlsOptions(config) {
return { rejectUnauthorized: config.verifyTls !== false && config.verifyTls !== 'false' };
}
async function webdavGet(config, ncPath) {
const url = webDavUrl(config, ncPath);
const res = await requestBuffer({
url,
method: 'GET',
headers: { Authorization: authHeader(config) },
rejectUnauthorized: tlsOptions(config).rejectUnauthorized
});
if (res.statusCode === 404) {
throw new Error(`WebDAV 404: filen finns inte: ${normalizeNcPath(ncPath)}`);
}
if (res.statusCode === 401 || res.statusCode === 403) {
throw new Error(`WebDAV ${res.statusCode}: kontrollera app-lösenord och användare`);
}
if (res.statusCode < 200 || res.statusCode >= 300) {
throw new Error(`WebDAV GET misslyckades (${res.statusCode}) för ${normalizeNcPath(ncPath)}`);
}
return {
buffer: res.body,
filename: filenameFromPath(ncPath),
ncPath: normalizeNcPath(ncPath),
contentType: res.headers['content-type']
};
}
async function ensureFolder(config, folderPath) {
const normalized = normalizeNcPath(folderPath);
if (!normalized || normalized === '/') {
return;
}
const parts = normalized.split('/').filter(Boolean);
let current = '';
for (const part of parts) {
current += '/' + part;
const url = webDavUrl(config, current);
const res = await requestBuffer({
url,
method: 'MKCOL',
headers: { Authorization: authHeader(config) },
rejectUnauthorized: tlsOptions(config).rejectUnauthorized
});
if (res.statusCode !== 201 && res.statusCode !== 405 && res.statusCode !== 301) {
if (res.statusCode >= 400) {
throw new Error(`WebDAV MKCOL misslyckades (${res.statusCode}) för ${current}`);
}
}
}
}
async function webdavPut(config, ncPath, buffer, contentType) {
const dest = normalizeNcPath(ncPath);
const folder = dest.substring(0, dest.lastIndexOf('/')) || '/';
await ensureFolder(config, folder);
const url = webDavUrl(config, dest);
const res = await requestBuffer({
url,
method: 'PUT',
headers: {
Authorization: authHeader(config),
'Content-Type': contentType || 'application/octet-stream',
'Content-Length': buffer.length
},
body: buffer,
rejectUnauthorized: tlsOptions(config).rejectUnauthorized
});
if (res.statusCode === 401 || res.statusCode === 403) {
throw new Error(`WebDAV ${res.statusCode}: kontrollera app-lösenord`);
}
if (res.statusCode < 200 || res.statusCode >= 300) {
throw new Error(`WebDAV PUT misslyckades (${res.statusCode}) för ${dest}`);
}
return {
statusCode: res.statusCode,
destNcPath: dest,
filename: filenameFromPath(dest)
};
}
async function webdavPropfindFileId(config, ncPath) {
const url = webDavUrl(config, ncPath);
const body = `<?xml version="1.0" encoding="UTF-8"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
<d:prop><oc:fileid/></d:prop>
</d:propfind>`;
const res = await requestBuffer({
url,
method: 'PROPFIND',
headers: {
Authorization: authHeader(config),
Depth: '0',
'Content-Type': 'application/xml; charset=utf-8'
},
body,
rejectUnauthorized: tlsOptions(config).rejectUnauthorized
});
if (res.statusCode < 200 || res.statusCode >= 300) {
throw new Error(`PROPFIND misslyckades (${res.statusCode}) för ${normalizeNcPath(ncPath)}`);
}
const text = res.body.toString('utf8');
const match = text.match(/<oc:fileid>(\d+)<\/oc:fileid>/i) || text.match(/<nc:fileid>(\d+)<\/nc:fileid>/i);
if (!match) {
throw new Error(`Kunde inte hitta fileId för ${normalizeNcPath(ncPath)}`);
}
return parseInt(match[1], 10);
}
module.exports = {
webDavUrl,
webdavGet,
webdavPut,
webdavPropfindFileId,
ensureFolder
};