@smartdcc/duis-sign-wrap
Version:
Wrapper library for signing/validating DUIS
166 lines • 5.27 kB
JavaScript
;
/*
* Created on Fri Feb 06 2026
*
* Copyright (c) 2026 Smart DCC Limited
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.port = void 0;
exports.setLogger = setLogger;
exports.startBackend = startBackend;
exports.checkPort = checkPort;
exports.stopBackend = stopBackend;
exports.buildUrl = buildUrl;
exports.makeRequest = makeRequest;
exports.makeSignDuisRequest = makeSignDuisRequest;
exports.makeVerifyDuisRequest = makeVerifyDuisRequest;
const path_1 = require("path");
const node_child_process_1 = require("node:child_process");
const node_net_1 = require("node:net");
const jarFile = (0, path_1.resolve)(__dirname, 'tool.jar');
exports.port = Math.round(Math.random() * 16384) + 32768;
let child;
let logger;
function setLogger(f) {
logger = f;
}
async function startBackend() {
if (child !== undefined && child.exitCode === null) {
/* already running */
return;
}
child = (0, node_child_process_1.spawn)('java', [
'-cp',
jarFile,
'uk.co.smartdcc.boxed.xmldsig.Server',
'-p',
exports.port.toString(10),
], {
stdio: ['ignore', 'inherit', 'pipe'],
});
child.stderr?.on('data', (data) => {
if (logger) {
logger(data.toString());
}
else {
console.error(data);
}
});
let ctr = 0;
do {
if (ctr > 10) {
child?.kill();
throw new Error('timeout waiting for server to start');
}
await new Promise((resolve) => {
setTimeout(resolve, 50);
});
if (child.exitCode !== null) {
throw new Error('server failed to start');
}
ctr++;
} while ((await checkPort(exports.port)) === false);
}
function checkPort(port) {
return new Promise((resolve) => {
const socket = new node_net_1.Socket();
socket.setTimeout(100);
socket.connect(port, '127.0.0.1', () => {
socket.end();
resolve(true);
});
socket.on('error', () => {
resolve(false);
});
});
}
function stopBackend() {
if (child === undefined || child.exitCode !== null) {
return;
}
child.kill();
child = undefined;
}
process.on('exit', stopBackend);
process.on('SIGINT', stopBackend);
process.on('SIGTERM', stopBackend);
function buildUrl(backend, mode) {
if (backend === true) {
return new URL(`http://localhost:${exports.port}/${mode}`);
}
return new URL(mode, backend);
}
async function makeRequest(url, options) {
const body = {
message: Buffer.from(options.xml).toString('base64'),
};
if (typeof options.preserveCounter === 'boolean') {
body.preserveCounter = options.preserveCounter;
}
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(options.headers ?? {}),
},
body: JSON.stringify(body),
});
if (response.status !== 200) {
const error = (await response.json());
throw new Error(error.errorCode ?? `HTTP ${response.status}`, {
cause: error.error,
});
}
const data = (await response.json());
if (typeof data?.message !== 'string') {
throw new Error('invalid response');
}
return Buffer.from(data.message, 'base64').toString('utf8');
}
/**
* Signs a DUIS message using the HTTP backend.
*
* @param options - Configuration options including XML content and backend URL
* @returns Promise resolving to the signed DUIS XML string
* @throws Error if the backend returns a non-200 status or invalid response
*/
async function makeSignDuisRequest(options) {
if (options.backend === true) {
await startBackend();
}
return makeRequest(buildUrl(options.backend, 'sign'), {
xml: options.xml,
preserveCounter: options.preserveCounter,
headers: options.headers,
});
}
/**
* Validates and verifies a signed DUIS message using the HTTP backend.
*
* @param options - Configuration options including XML content and backend URL
* @returns Promise resolving to the validated DUIS XML string without signature
* @throws Error if the backend returns a non-200 status or invalid response
*/
async function makeVerifyDuisRequest(options) {
if (options.backend === true) {
await startBackend();
}
return makeRequest(buildUrl(options.backend, 'verify'), {
xml: options.xml,
headers: options.headers,
});
}
//# sourceMappingURL=server.js.map