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

This commit is contained in:
Hannes Ahlin
2026-08-22 14:56:38 +02:00
commit a95dae88c7
38 changed files with 2239 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
<script type="text/javascript">
RED.nodes.registerType('collabora-bridge', {
category: 'nextcloud collabora',
color: '#e67e22',
defaults: {
name: { value: '' },
config: { type: 'nc-collab-config', required: true },
operation: { value: 'ncFileToPdf' }
},
inputs: 1,
outputs: 1,
icon: 'font-awesome/fa-exchange',
label: function () {
return this.name || this.operation;
}
});
</script>
<script type="text/html" data-template-name="collabora-bridge">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Namn</label>
<input type="text" id="node-input-name">
</div>
<div class="form-row">
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
<input type="text" id="node-input-config">
</div>
<div class="form-row">
<label for="node-input-operation"><i class="fa fa-wrench"></i> Pipeline</label>
<select id="node-input-operation">
<option value="ncFileToPdf">ncFileToPdf</option>
<option value="ncFileToPng">ncFileToPng</option>
<option value="ncFileToHtml">ncFileToHtml</option>
<option value="htmlToOffice">htmlToOffice</option>
<option value="fillTemplateSave">fillTemplateSave</option>
<option value="compareVersionsPdf">compareVersionsPdf</option>
<option value="extractToPayload">extractToPayload</option>
<option value="createAssetLink">createAssetLink</option>
</select>
</div>
</script>
<script type="text/html" data-help-name="collabora-bridge">
<p>Agent-pipelines: NC-fil → konvertering → WebDAV PUT under <code>/Dokument/OfficeOut/</code>.</p>
<p>Skriver aldrig över original. Sätt <code>msg.allowOverwrite=true</code> för annan destNcPath.</p>
<p>Överstyr via <code>msg.operation</code>. Standardfält: <code>ncPath</code>, <code>destNcPath</code>, <code>format</code>, <code>fields</code>, <code>compareNcPath</code>.</p>
</script>
+64
View File
@@ -0,0 +1,64 @@
'use strict';
const bridge = require('../lib/bridge');
const { getConfig, setError, setSuccess, clearStatus, pickMsgField } = require('../lib/utils');
const OPERATIONS = {
ncFileToPdf: bridge.ncFileToPdf,
ncFileToPng: bridge.ncFileToPng,
ncFileToHtml: bridge.ncFileToHtml,
htmlToOffice: bridge.htmlToOffice,
fillTemplateSave: bridge.fillTemplateSave,
compareVersionsPdf: bridge.compareVersionsPdf,
extractToPayload: bridge.extractToPayload,
createAssetLink: bridge.createAssetLink
};
module.exports = function (RED) {
function CollaboraBridgeNode(config) {
RED.nodes.createNode(this, config);
const node = this;
node.configNode = RED.nodes.getNode(config.config);
node.operation = config.operation || 'ncFileToPdf';
node.on('input', async (msg, send, done) => {
send = send || function () { node.send.apply(node, arguments); };
done = done || function (err) { if (err) { node.error(err, msg); } };
clearStatus(node);
try {
const cfg = getConfig(node);
const op = pickMsgField(msg, 'operation') || node.operation;
const fn = OPERATIONS[op];
if (!fn) {
throw new Error(`Okänd bridge-operation: ${op}`);
}
const result = await fn(cfg, msg);
Object.assign(msg, result);
if (result.payload !== undefined) {
msg.payload = result.payload;
} else {
msg.payload = {
destNcPath: result.destNcPath,
filename: result.filename,
format: result.format,
assetUrl: result.assetUrl,
fields: result.fields,
structure: result.structure
};
}
setSuccess(node, msg, op);
send(msg);
done();
} catch (err) {
setError(node, msg, err);
send(msg);
done(err);
}
});
}
RED.nodes.registerType('collabora-bridge', CollaboraBridgeNode);
};
+48
View File
@@ -0,0 +1,48 @@
<script type="text/javascript">
RED.nodes.registerType('collabora-convert', {
category: 'nextcloud collabora',
color: '#0082c9',
defaults: {
name: { value: '' },
config: { type: 'nc-collab-config', required: true },
operation: { value: 'convert' },
format: { value: 'pdf' }
},
inputs: 1,
outputs: 1,
icon: 'font-awesome/fa-file-pdf-o',
label: function () {
return this.name || `convert → ${this.format}`;
}
});
</script>
<script type="text/html" data-template-name="collabora-convert">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Namn</label>
<input type="text" id="node-input-name">
</div>
<div class="form-row">
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
<input type="text" id="node-input-config">
</div>
<div class="form-row">
<label for="node-input-format"><i class="fa fa-file"></i> Format</label>
<select id="node-input-format">
<option value="pdf">pdf</option>
<option value="png">png</option>
<option value="docx">docx</option>
<option value="odt">odt</option>
<option value="html">html</option>
<option value="txt">txt</option>
<option value="xlsx">xlsx</option>
<option value="pptx">pptx</option>
</select>
</div>
</script>
<script type="text/html" data-help-name="collabora-convert">
<p>Låg nivå: <code>/cool/convert-to</code> mot CODE.</p>
<p>Ingång: <code>msg.ncPath</code> eller <code>msg.payload</code> (Buffer). Valfritt <code>msg.format</code>, <code>msg.templateNcPath</code>, <code>msg.compareNcPath</code>.</p>
<p>Utdata: <code>msg.payload</code> (Buffer). Sparar inte till Nextcloud — använd bridge eller WebDAV PUT efteråt.</p>
</script>
+60
View File
@@ -0,0 +1,60 @@
'use strict';
const { getConfig, setError, setSuccess, clearStatus, pickMsgField } = require('../lib/utils');
const { convertTo } = require('../lib/collabora');
const { loadSourceBuffer } = require('../lib/bridge');
const { webdavGet } = require('../lib/webdav');
module.exports = function (RED) {
function CollaboraConvertNode(config) {
RED.nodes.createNode(this, config);
const node = this;
node.configNode = RED.nodes.getNode(config.config);
node.operation = config.operation || 'convert';
node.format = config.format || 'pdf';
node.on('input', async (msg, send, done) => {
send = send || function () { node.send.apply(node, arguments); };
done = done || function (err) { if (err) { node.error(err, msg); } };
clearStatus(node);
try {
const cfg = getConfig(node);
const format = (pickMsgField(msg, 'format') || node.format).toLowerCase();
const source = await loadSourceBuffer(cfg, msg);
const extraFields = [];
if (msg.templateNcPath) {
const tpl = await webdavGet(cfg, msg.templateNcPath);
extraFields.push({ name: 'template', buffer: tpl.buffer, filename: tpl.filename });
}
if (msg.compareNcPath) {
const cmp = await webdavGet(cfg, msg.compareNcPath);
extraFields.push({ name: 'compare', buffer: cmp.buffer, filename: cmp.filename });
}
const result = await convertTo(cfg, {
buffer: source.buffer,
filename: source.filename,
format,
extraFields
});
msg.payload = result.buffer;
msg.format = format;
msg.contentType = result.contentType;
msg.statusCode = result.statusCode;
msg.filename = source.filename;
setSuccess(node, msg, `convert → ${format}`);
send(msg);
done();
} catch (err) {
setError(node, msg, err);
send(msg);
done(err);
}
});
}
RED.nodes.registerType('collabora-convert', CollaboraConvertNode);
};
+37
View File
@@ -0,0 +1,37 @@
<script type="text/javascript">
RED.nodes.registerType('collabora-extract', {
category: 'nextcloud collabora',
color: '#0082c9',
defaults: {
name: { value: '' },
config: { type: 'nc-collab-config', required: true },
filter: { value: 'contentcontrol' }
},
inputs: 1,
outputs: 1,
icon: 'font-awesome/fa-sitemap',
label: function () {
return this.name || 'extract structure';
}
});
</script>
<script type="text/html" data-template-name="collabora-extract">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Namn</label>
<input type="text" id="node-input-name">
</div>
<div class="form-row">
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
<input type="text" id="node-input-config">
</div>
<div class="form-row">
<label for="node-input-filter"><i class="fa fa-filter"></i> Filter</label>
<input type="text" id="node-input-filter" placeholder="contentcontrol">
</div>
</script>
<script type="text/html" data-help-name="collabora-extract">
<p><code>/cool/extract-document-structure</code></p>
<p>Ingång: <code>msg.ncPath</code> eller Buffer i <code>msg.payload</code>. Valfritt <code>msg.filter</code>.</p>
</script>
+43
View File
@@ -0,0 +1,43 @@
'use strict';
const { getConfig, setError, setSuccess, clearStatus, pickMsgField } = require('../lib/utils');
const { extractStructure } = require('../lib/collabora');
const { loadSourceBuffer } = require('../lib/bridge');
module.exports = function (RED) {
function CollaboraExtractNode(config) {
RED.nodes.createNode(this, config);
const node = this;
node.configNode = RED.nodes.getNode(config.config);
node.filter = config.filter || 'contentcontrol';
node.on('input', async (msg, send, done) => {
send = send || function () { node.send.apply(node, arguments); };
done = done || function (err) { if (err) { node.error(err, msg); } };
clearStatus(node);
try {
const cfg = getConfig(node);
const source = await loadSourceBuffer(cfg, msg);
const filter = pickMsgField(msg, 'filter') || node.filter;
const structure = await extractStructure(cfg, {
buffer: source.buffer,
filename: source.filename,
filter
});
msg.payload = structure;
msg.structure = structure;
setSuccess(node, msg, 'extract');
send(msg);
done();
} catch (err) {
setError(node, msg, err);
send(msg);
done(err);
}
});
}
RED.nodes.registerType('collabora-extract', CollaboraExtractNode);
};
+32
View File
@@ -0,0 +1,32 @@
<script type="text/javascript">
RED.nodes.registerType('collabora-transform', {
category: 'nextcloud collabora',
color: '#0082c9',
defaults: {
name: { value: '' },
config: { type: 'nc-collab-config', required: true }
},
inputs: 1,
outputs: 1,
icon: 'font-awesome/fa-magic',
label: function () {
return this.name || 'transform structure';
}
});
</script>
<script type="text/html" data-template-name="collabora-transform">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Namn</label>
<input type="text" id="node-input-name">
</div>
<div class="form-row">
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
<input type="text" id="node-input-config">
</div>
</script>
<script type="text/html" data-help-name="collabora-transform">
<p><code>/cool/transform-document-structure</code></p>
<p>Ingång: källa via <code>msg.ncPath</code> eller <code>msg.payload</code>, plus <code>msg.transform</code> (JSON).</p>
</script>
+46
View File
@@ -0,0 +1,46 @@
'use strict';
const { getConfig, setError, setSuccess, clearStatus, pickMsgField } = require('../lib/utils');
const { transformStructure } = require('../lib/collabora');
const { loadSourceBuffer } = require('../lib/bridge');
module.exports = function (RED) {
function CollaboraTransformNode(config) {
RED.nodes.createNode(this, config);
const node = this;
node.configNode = RED.nodes.getNode(config.config);
node.on('input', async (msg, send, done) => {
send = send || function () { node.send.apply(node, arguments); };
done = done || function (err) { if (err) { node.error(err, msg); } };
clearStatus(node);
try {
const cfg = getConfig(node);
const transform = pickMsgField(msg, 'transform') || msg.fields;
if (!transform) {
throw new Error('Ange msg.transform eller msg.fields');
}
const source = await loadSourceBuffer(cfg, msg);
const result = await transformStructure(cfg, {
buffer: source.buffer,
filename: source.filename,
transform
});
msg.payload = result.buffer;
msg.contentType = result.contentType;
setSuccess(node, msg, 'transform');
send(msg);
done();
} catch (err) {
setError(node, msg, err);
send(msg);
done(err);
}
});
}
RED.nodes.registerType('collabora-transform', CollaboraTransformNode);
};
+43
View File
@@ -0,0 +1,43 @@
<script type="text/javascript">
RED.nodes.registerType('collabora-wopi', {
category: 'nextcloud collabora',
color: '#0082c9',
defaults: {
name: { value: '' },
config: { type: 'nc-collab-config', required: true },
operation: { value: 'capabilities' }
},
inputs: 1,
outputs: 1,
icon: 'font-awesome/fa-search',
label: function () {
return this.name || `wopi ${this.operation}`;
}
});
</script>
<script type="text/html" data-template-name="collabora-wopi">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Namn</label>
<input type="text" id="node-input-name">
</div>
<div class="form-row">
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
<input type="text" id="node-input-config">
</div>
<div class="form-row">
<label for="node-input-operation"><i class="fa fa-wrench"></i> Operation</label>
<select id="node-input-operation">
<option value="capabilities">capabilities</option>
<option value="discovery">discovery</option>
<option value="buildEditor">buildEditor</option>
<option value="checkFileInfo">checkFileInfo (debug)</option>
<option value="getFile">getFile (debug)</option>
</select>
</div>
</script>
<script type="text/html" data-help-name="collabora-wopi">
<p>WOPI discovery/capabilities och editor-URL. Agenten ska nästan aldrig PutFile — använd WebDAV PUT.</p>
<p><code>buildEditor</code>: <code>msg.wopiSrc</code> + <code>msg.token</code> → <code>msg.editorUrl</code>.</p>
</script>
+69
View File
@@ -0,0 +1,69 @@
'use strict';
const { getConfig, setError, setSuccess, clearStatus } = require('../lib/utils');
const { getCapabilities, getDiscovery } = require('../lib/collabora');
const { requestBuffer } = require('../lib/http-client');
module.exports = function (RED) {
function CollaboraWopiNode(config) {
RED.nodes.createNode(this, config);
const node = this;
node.configNode = RED.nodes.getNode(config.config);
node.operation = config.operation || 'capabilities';
node.on('input', async (msg, send, done) => {
send = send || function () { node.send.apply(node, arguments); };
done = done || function (err) { if (err) { node.error(err, msg); } };
clearStatus(node);
try {
const cfg = getConfig(node);
const op = msg.operation || node.operation;
if (op === 'capabilities') {
msg.payload = await getCapabilities(cfg);
} else if (op === 'discovery') {
msg.payload = await getDiscovery(cfg);
} else if (op === 'buildEditor') {
const publicBase = cfg.collaboraPublicUrl || cfg.collaboraUrl;
const wopiSrc = encodeURIComponent(msg.wopiSrc || msg.WOPISrc || '');
const token = encodeURIComponent(msg.token || msg.access_token || '');
if (!wopiSrc || !token) {
throw new Error('buildEditor kräver msg.wopiSrc och msg.token');
}
msg.editorUrl = `${publicBase}/browser/dist/cool.html?WOPISrc=${wopiSrc}&access_token=${token}`;
msg.payload = { editorUrl: msg.editorUrl };
} else if (op === 'checkFileInfo' || op === 'getFile') {
const wopiUrl = msg.wopiUrl;
if (!wopiUrl) {
throw new Error(`${op} kräver msg.wopiUrl (felsökning)`);
}
const token = msg.token || msg.access_token || '';
const res = await requestBuffer({
url: wopiUrl,
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'X-WOPI-Override': op === 'getFile' ? 'GET' : undefined
},
rejectUnauthorized: cfg.verifyTls !== false
});
msg.statusCode = res.statusCode;
msg.payload = op === 'getFile' ? res.body : JSON.parse(res.body.toString('utf8'));
} else {
throw new Error(`Okänd wopi-operation: ${op}`);
}
setSuccess(node, msg, op);
send(msg);
done();
} catch (err) {
setError(node, msg, err);
send(msg);
done(err);
}
});
}
RED.nodes.registerType('collabora-wopi', CollaboraWopiNode);
};
@@ -0,0 +1,5 @@
{
"nextcloud-collabora": {
"nextcloud collabora": "Nextcloud Collabora"
}
}
+55
View File
@@ -0,0 +1,55 @@
<script type="text/javascript">
RED.nodes.registerType('nc-collab-config', {
category: 'config',
defaults: {
name: { value: '' },
nextcloudUrl: { value: 'https://cloud.hannesahlin.se', required: true },
collaboraUrl: { value: 'http://host.docker.internal:9980', required: true },
collaboraPublicUrl: { value: '' },
username: { value: '', required: true },
verifyTls: { value: false }
},
credentials: {
password: { type: 'password' }
},
label: function () {
return this.name || `NC Collab (${this.username || '?'})`;
}
});
</script>
<script type="text/html" data-template-name="nc-collab-config">
<div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> Namn</label>
<input type="text" id="node-config-input-name" placeholder="t.ex. cloud.hannesahlin.se">
</div>
<div class="form-row">
<label for="node-config-input-nextcloudUrl"><i class="fa fa-cloud"></i> Nextcloud URL</label>
<input type="text" id="node-config-input-nextcloudUrl">
</div>
<div class="form-row">
<label for="node-config-input-collaboraUrl"><i class="fa fa-server"></i> Collabora intern URL</label>
<input type="text" id="node-config-input-collaboraUrl">
</div>
<div class="form-row">
<label for="node-config-input-collaboraPublicUrl"><i class="fa fa-globe"></i> Collabora publik URL (valfri)</label>
<input type="text" id="node-config-input-collaboraPublicUrl">
</div>
<div class="form-row">
<label for="node-config-input-username"><i class="fa fa-user"></i> Användare</label>
<input type="text" id="node-config-input-username">
</div>
<div class="form-row">
<label for="node-config-input-password"><i class="fa fa-key"></i> App-lösenord</label>
<input type="password" id="node-config-input-password">
</div>
<div class="form-row">
<label for="node-config-input-verifyTls"><i class="fa fa-lock"></i> Verifiera TLS</label>
<input type="checkbox" id="node-config-input-verifyTls" style="width:auto">
</div>
</script>
<script type="text/html" data-help-name="nc-collab-config">
<p>En instans per miljö. Använd <strong>app-lösenord</strong>, aldrig vanligt kontolösen.</p>
<p>Fält: <code>nextcloudUrl</code>, <code>collaboraUrl</code>, <code>username</code>, app-lösen, <code>verifyTls</code>.</p>
</script>
+19
View File
@@ -0,0 +1,19 @@
'use strict';
module.exports = function (RED) {
function NcCollabConfigNode(n) {
RED.nodes.createNode(this, n);
this.nextcloudUrl = (n.nextcloudUrl || '').replace(/\/$/, '');
this.collaboraUrl = (n.collaboraUrl || '').replace(/\/$/, '');
this.collaboraPublicUrl = (n.collaboraPublicUrl || '').replace(/\/$/, '');
this.username = n.username || '';
this.verifyTls = n.verifyTls !== false && n.verifyTls !== 'false';
this.password = this.credentials.password || '';
}
RED.nodes.registerType('nc-collab-config', NcCollabConfigNode, {
credentials: {
password: { type: 'password' }
}
});
};
+46
View File
@@ -0,0 +1,46 @@
<script type="text/javascript">
RED.nodes.registerType('nc-office', {
category: 'nextcloud collabora',
color: '#46ba61',
defaults: {
name: { value: '' },
config: { type: 'nc-collab-config', required: true },
operation: { value: 'templates:list' }
},
inputs: 1,
outputs: 1,
icon: 'font-awesome/fa-file-text-o',
label: function () {
return this.name || `nc-office ${this.operation}`;
}
});
</script>
<script type="text/html" data-template-name="nc-office">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Namn</label>
<input type="text" id="node-input-name">
</div>
<div class="form-row">
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
<input type="text" id="node-input-config">
</div>
<div class="form-row">
<label for="node-input-operation"><i class="fa fa-wrench"></i> Operation</label>
<select id="node-input-operation">
<option value="templates:list">templates:list</option>
<option value="templates:create">templates:create</option>
<option value="template:extractFields">template:extractFields</option>
<option value="template:fillFields">template:fillFields</option>
<option value="document:direct">document:direct</option>
<option value="document:public">document:public</option>
<option value="files:convert">files:convert</option>
<option value="assets:create">assets:create</option>
</select>
</div>
</script>
<script type="text/html" data-help-name="nc-office">
<p>Richdocuments OCS — mallar, fält, direkt-URL, assets.</p>
<p>Överstyr via <code>msg.operation</code>. Gemensamma fält: <code>ncPath</code>, <code>fileId</code>, <code>destNcPath</code>, <code>fields</code>.</p>
</script>
+146
View File
@@ -0,0 +1,146 @@
'use strict';
const { getConfig, setError, setSuccess, clearStatus, pickMsgField } = require('../lib/utils');
const { ocsRequest } = require('../lib/ocs');
const bridge = require('../lib/bridge');
const { resolveFileId } = bridge;
const { webdavGet, webdavPut } = require('../lib/webdav');
const { convertTo } = require('../lib/collabora');
const {
resolveDestNcPath,
assertSafeDestination,
contentTypeForFormat,
normalizeNcPath
} = require('../lib/utils');
module.exports = function (RED) {
function NcOfficeNode(config) {
RED.nodes.createNode(this, config);
const node = this;
node.configNode = RED.nodes.getNode(config.config);
node.operation = config.operation || 'templates:list';
node.on('input', async (msg, send, done) => {
send = send || function () { node.send.apply(node, arguments); };
done = done || function (err) { if (err) { node.error(err, msg); } };
clearStatus(node);
try {
const cfg = getConfig(node);
const op = msg.operation || node.operation;
let result;
switch (op) {
case 'templates:list': {
const type = pickMsgField(msg, 'templateType') || 'document';
result = await ocsRequest(cfg, {
method: 'GET',
path: `/ocs/v2.php/apps/richdocuments/api/v1/templates/${type}`
});
msg.payload = result.data;
break;
}
case 'templates:create': {
result = await ocsRequest(cfg, {
method: 'POST',
path: '/ocs/v2.php/apps/richdocuments/api/v1/templates/new',
body: {
templateId: msg.templateId,
fileName: msg.fileName || msg.destName,
templatePath: msg.templatePath
}
});
msg.payload = result.data;
break;
}
case 'template:extractFields': {
const fileId = await resolveFileId(cfg, msg);
result = await ocsRequest(cfg, {
method: 'GET',
path: `/ocs/v2.php/apps/richdocuments/api/v1/template/fields/extract/${fileId}`
});
msg.fileId = fileId;
msg.payload = result.data;
break;
}
case 'template:fillFields': {
const fileId = await resolveFileId(cfg, msg);
const fields = msg.fields || msg.transform;
const body = { fields };
if (msg.destNcPath) {
body.destination = normalizeNcPath(msg.destNcPath).replace(/^\//, '');
}
result = await ocsRequest(cfg, {
method: 'POST',
path: `/ocs/v2.php/apps/richdocuments/api/v1/template/fields/fill/${fileId}`,
body
});
msg.fileId = fileId;
msg.payload = result.data;
break;
}
case 'document:direct': {
const fileId = await resolveFileId(cfg, msg);
result = await ocsRequest(cfg, {
method: 'POST',
path: '/ocs/v2.php/apps/richdocuments/api/v1/document',
body: { fileid: fileId }
});
msg.fileId = fileId;
msg.editorUrl = result.data?.url || result.data;
msg.payload = { url: msg.editorUrl, fileId };
break;
}
case 'document:public': {
if (!msg.shareToken) {
throw new Error('document:public kräver msg.shareToken');
}
result = await ocsRequest(cfg, {
method: 'POST',
path: '/ocs/v2.php/apps/richdocuments/api/v1/document',
body: { shareToken: msg.shareToken }
});
msg.editorUrl = result.data?.url || result.data;
msg.payload = { url: msg.editorUrl };
break;
}
case 'files:convert': {
const format = (pickMsgField(msg, 'format') || 'pdf').toLowerCase();
const source = msg.ncPath
? await webdavGet(cfg, msg.ncPath)
: { buffer: Buffer.from(msg.payload), filename: msg.filename || 'file.bin' };
const converted = await convertTo(cfg, {
buffer: source.buffer,
filename: source.filename,
format
});
const dest = assertSafeDestination(resolveDestNcPath(msg, msg.ncPath, format), msg);
const put = await webdavPut(cfg, dest, converted.buffer, contentTypeForFormat(format));
msg.destNcPath = put.destNcPath;
msg.payload = converted.buffer;
msg.format = format;
break;
}
case 'assets:create': {
const link = await bridge.createAssetLink(cfg, msg);
msg.assetUrl = link.assetUrl;
msg.payload = { assetUrl: link.assetUrl, ncPath: link.ncPath };
break;
}
default:
throw new Error(`Okänd nc-office operation: ${op}`);
}
setSuccess(node, msg, op);
send(msg);
done();
} catch (err) {
setError(node, msg, err);
send(msg);
done(err);
}
});
}
RED.nodes.registerType('nc-office', NcOfficeNode);
};