@binlebin/c2pa-ssl
Version:
Node.js wrapper for C2PA content authentication using Rust CLI backend
133 lines (111 loc) • 4.04 kB
JavaScript
const { spawn } = require('child_process');
const fs = require('fs-extra');
const path = require('path');
class C2PAService {
constructor() {
// No longer need to manage ports or HTTP servers
this.binaryPath = this.getBinaryPath();
}
getBinaryPath() {
// Platform detection for available binaries
const platform = process.platform;
if (platform === 'darwin') {
return path.join(__dirname, 'bin', 'c2pa-cli');
} else if (platform === 'linux') {
return path.join(__dirname, 'bin', 'c2pa-cli-linux');
} else {
throw new Error(
`Platform '${platform}' is not currently supported. ` +
`This package currently supports macOS (darwin) and Linux. ` +
`For Windows support, we need to set up cross-compilation.`
);
}
}
async start() {
// For CLI approach, we need the private key and use bundled certificate
const privateKey = process.env.C2PA_PRIVATE_KEY;
if (!privateKey) {
throw new Error('C2PA_PRIVATE_KEY environment variable is required');
}
// Create test_certs directory
const certsDir = path.resolve(__dirname, 'test_certs');
await fs.ensureDir(certsDir);
// Write private key to where CLI binary expects it
const keyPath = path.join(certsDir, 'anh_ec.key');
// Check if private key file already exists
if (!await fs.pathExists(keyPath)) {
await fs.writeFile(keyPath, privateKey);
console.log('✅ Private key written for CLI usage');
} else {
console.log('✅ Private key already exists, skipping write');
}
// Check if bundled certificate exists
const certPath = path.join(certsDir, 'anh_ec.pem');
if (!await fs.pathExists(certPath)) {
throw new Error(`Certificate file not found in package.
Please ensure anh_ec.pem is included.`);
}
console.log('✅ Certificate file found');
// Check if binary exists
if (!await fs.pathExists(this.binaryPath)) {
throw new Error(`CLI binary not found at ${this.binaryPath}`);
}
console.log('✅ C2PA CLI ready');
}
async stop() {
// Clean up only the private key file (certificate stays as it's bundled)
const certsDir = path.resolve(__dirname, 'test_certs');
const keyPath = path.join(certsDir, 'anh_ec.key');
try {
await fs.remove(keyPath);
console.log('🧹 Cleaned up private key file');
} catch (error) {
// Ignore cleanup errors
}
}
async signFile(inputPath, outputPath, metadata = {}) {
return new Promise((resolve, reject) => {
// Prepare CLI arguments
const args = [
inputPath,
outputPath,
'--title', metadata.title || 'Signed Content',
'--description', metadata.description || 'Content signed with C2PA',
'--author', metadata.author || 'visflow.ai'
];
console.log(`🚀 Signing file: ${inputPath} -> ${outputPath}`);
// Execute CLI
const process = spawn(this.binaryPath, args, {
cwd: __dirname,
stdio: ['ignore', 'pipe', 'pipe']
});
let stdout = '';
let stderr = '';
process.stdout.on('data', (data) => {
stdout += data.toString();
});
process.stderr.on('data', (data) => {
stderr += data.toString();
});
process.on('close', (code) => {
if (code === 0) {
console.log('✅ File signed successfully');
resolve({
success: true,
message: 'File signed successfully',
stdout: stdout.trim(),
stderr: stderr.trim()
});
} else {
console.error('❌ CLI signing failed:', stderr);
reject(new Error(`CLI signing failed with code ${code}: ${stderr}`));
}
});
process.on('error', (error) => {
console.error('❌ Failed to execute CLI:', error);
reject(new Error(`Failed to execute CLI: ${error.message}`));
});
});
}
}
module.exports = C2PAService;