From 887e52d15c8900f24a07d88b1e610051651f3867 Mon Sep 17 00:00:00 2001 From: yushulx Date: Tue, 25 Mar 2025 17:37:38 +0800 Subject: [PATCH 1/8] dwtv19 --- examples/REST/app.js | 8 +- examples/command-line/app.js | 20 ++-- examples/web/app.js | 55 +++++----- index.js | 199 ++++++++++++++++++++--------------- 4 files changed, 160 insertions(+), 122 deletions(-) diff --git a/examples/REST/app.js b/examples/REST/app.js index f9e2dc3..3216fda 100644 --- a/examples/REST/app.js +++ b/examples/REST/app.js @@ -21,11 +21,11 @@ app.get('/devices', (req, res) => { }); app.post('/scandocument', async (req, res) => { - const json = req.body; + const data = req.body; let parameters = { license: "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==", - device: json['scan'], + device: data['scan'], }; parameters.config = { @@ -38,7 +38,9 @@ app.post('/scandocument', async (req, res) => { IfDuplexEnabled: false, }; - let jobId = await docscan4nodejs.scanDocument(dynamsoftService, parameters); + let job = await docscan4nodejs.scanDocument(dynamsoftService, parameters); + let json = JSON.parse(job); + let jobId = json.jobuid; let filename = await docscan4nodejs.getImageFile(dynamsoftService, jobId, './public'); res.send(JSON.stringify({ diff --git a/examples/command-line/app.js b/examples/command-line/app.js index 5dd96d8..664407e 100644 --- a/examples/command-line/app.js +++ b/examples/command-line/app.js @@ -2,9 +2,12 @@ const docscan4nodejs = require("../../index.js") const readline = require('readline'); let devices = []; -// let host = 'http://192.168.8.119:18622'; let host = 'http://127.0.0.1:18622'; +docscan4nodejs.getServerInfo(host).then((info) => { + console.log('Server info:', info); +}); + const questions = ` Please select an operation: 1. Get scanners @@ -63,19 +66,22 @@ function askQuestion() { IfDuplexEnabled: false, }; - docscan4nodejs.scanDocument(host, parameters).then((jobId) => { - if (jobId !== '') { - console.log('job id: ' + jobId); + docscan4nodejs.scanDocument(host, parameters).then((job) => { + try { + let json = JSON.parse(job); + let jobid = json.jobuid; (async () => { - let images = await docscan4nodejs.getImageFiles(host, jobId, './'); + let images = await docscan4nodejs.getImageFiles(host, jobid, './'); for (let i = 0; i < images.length; i++) { console.log('Image ' + i + ': ' + images[i]); } - // await docscan4nodejs.deleteJob(host, jobId); + await docscan4nodejs.deleteJob(host, jobid); askQuestion(); })(); } - + catch (error) { + console.error('Job creation failed:', error.message); + } }); } } diff --git a/examples/web/app.js b/examples/web/app.js index 4be02f0..42f0d76 100644 --- a/examples/web/app.js +++ b/examples/web/app.js @@ -27,12 +27,12 @@ io.on('connection', (socket) => { }); socket.on('message', async (message) => { - let json = JSON.parse(message); - if (json) { - if (json['scan']) { + let data = JSON.parse(message); + if (data) { + if (data['scan']) { let parameters = { license: "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==", - device: json['scan'], + device: data['scan'], }; parameters.config = { @@ -45,34 +45,33 @@ io.on('connection', (socket) => { IfDuplexEnabled: false, }; - let jobId = await docscan4nodejs.scanDocument(host, parameters); + let job = await docscan4nodejs.scanDocument(host, parameters); + let json = JSON.parse(job); + let jobId = json.jobuid; - if (jobId !== '') { - console.log('job id: ' + jobId); - let streams = await docscan4nodejs.getImageStreams(host, jobId); - for (let i = 0; i < streams.length; i++) { - await new Promise((resolve, reject) => { - try { - const passThrough = new PassThrough(); - const chunks = []; + let streams = await docscan4nodejs.getImageStreams(host, jobId); + for (let i = 0; i < streams.length; i++) { + await new Promise((resolve, reject) => { + try { + const passThrough = new PassThrough(); + const chunks = []; - streams[i].pipe(passThrough); + streams[i].pipe(passThrough); - passThrough.on('data', (chunk) => { - chunks.push(chunk); - }); + passThrough.on('data', (chunk) => { + chunks.push(chunk); + }); - passThrough.on('end', () => { - const buffer = Buffer.concat(chunks); - socket.emit('image', buffer); - resolve(); - }); - } - catch (error) { - reject(error); - } - }); - } + passThrough.on('end', () => { + const buffer = Buffer.concat(chunks); + socket.emit('image', buffer); + resolve(); + }); + } + catch (error) { + reject(error); + } + }); } } } diff --git a/index.js b/index.js index 90d27ab..bd7990a 100644 --- a/index.js +++ b/index.js @@ -59,7 +59,16 @@ function request(options) { }); }); - req.on('error', reject); + req.on('error', (err) => { + if (err.code === 'ECONNRESET') { + console.error('ECONNRESET!!!', { + url: options.url, + retryCount: options.retryCount || 0 + }); + } + reject(err); + }); + if (options.body) { req.write(typeof options.body === 'string' ? options.body @@ -69,10 +78,26 @@ function request(options) { }); } +// Get server version information +async function getServerInfo(host) { + try { + const response = await request({ + url: `${host}/api/server/version`, + method: 'GET', + json: true + }); + return response.data; + } catch (error) { + return { + version: error.message, + compatible: false + }; + } +} + // Fetch single image file and save to directory async function getImageFile(host, jobId, directory) { - const url = `${host}/DWTAPI/ScanJobs/${jobId}/NextDocument`; - + const url = `${host}/api/device/scanners/jobs/${jobId}/next-page`; try { const response = await request({ url, @@ -120,7 +145,7 @@ async function getImageFile(host, jobId, directory) { // Fetch single image stream async function getImageStream(host, jobId) { - const url = `${host}/DWTAPI/ScanJobs/${jobId}/NextDocument`; + const url = `${host}/api/device/scanners/jobs/${jobId}/next-page`; try { const response = await request({ @@ -138,93 +163,99 @@ async function getImageStream(host, jobId) { return null; } -module.exports = { - // Get available scanning devices - getDevices: async function (host, scannerType) { - let url = `${host}/DWTAPI/Scanners`; - if (scannerType != null) url += `?type=${scannerType}`; - - try { - const response = await request({ - url, - json: true - }); +// Get available scanning devices +async function getDevices(host, scannerType) { + let url = `${host}/api/device/scanners`; + if (scannerType != null) url += `?type=${scannerType}`; - if (response.data.length > 0) { - console.log('Available scanners:', response.data.length); - return response.data; - } - } catch (error) { - console.error('Device discovery failed:', error.message); - } - return []; - }, - - // Create new scan job - scanDocument: async function (host, parameters, timeout = 30) { - const url = `${host}/DWTAPI/ScanJobs?timeout=${timeout}`; - - try { - const response = await request({ - url, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(JSON.stringify(parameters)) - }, - body: parameters - }); + try { + const response = await request({ + url, + json: true + }); - return response.status === 201 ? response.data : ''; - } catch (error) { - console.error('Scan job creation failed:', error.message); - return ''; + if (response.data.length > 0) { + console.log('Available scanners:', response.data.length); + return response.data; } - }, + } catch (error) { + console.error('Device discovery failed:', error.message); + } + return []; +} - // Delete existing scan job - deleteJob: async function (host, jobId) { - if (!jobId) return; +// Create new scan job +async function scanDocument(host, parameters) { + const url = `${host}/api/device/scanners/jobs`; - const url = `${host}/DWTAPI/ScanJobs/${jobId}`; - try { - await request({ - url, - method: 'DELETE' - }); - } catch (error) { - console.error('Job deletion failed:', error.message); - } - }, + try { + const response = await request({ + url, + method: 'POST', + headers: { + 'X-DICS-LICENSE-KEY': parameters.license, + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(JSON.stringify(parameters)) + }, + body: parameters + }); - getImageFile, - getImageStream, + return response.status === 201 ? response.data : ''; + } catch (error) { + console.error('Scan job creation failed:', error.message); + return ''; + } +} - // Get multiple image files - getImageFiles: async function (host, jobId, directory) { - const images = []; - console.log('Starting image download...'); +// Delete existing scan job +async function deleteJob(host, jobId) { + if (!jobId) return; - while (true) { - const filename = await getImageFile(host, jobId, directory); - if (!filename) break; - images.push(filename); - } - return images; - }, - - // Get multiple image streams - getImageStreams: async function (host, jobId) { - const streams = []; - console.log('Starting stream collection...'); - - while (true) { - const stream = await getImageStream(host, jobId); - if (!stream) break; - streams.push(stream); - } - return streams; - }, + const url = `${host}/api/device/scanners/jobs/${jobId}`; + try { + await request({ + url, + method: 'DELETE' + }); + } catch (error) { + console.error('Job deletion failed:', error.message); + } +} + +// Get multiple image files +async function getImageFiles(host, jobId, directory) { + const images = []; + console.log('Starting image download...'); + + while (true) { + const filename = await getImageFile(host, jobId, directory); + if (!filename) break; + images.push(filename); + } + return images; +} - ScannerType +// Get multiple image streams +async function getImageStreams(host, jobId) { + const streams = []; + console.log('Starting stream collection...'); + + while (true) { + const stream = await getImageStream(host, jobId); + if (!stream) break; + streams.push(stream); + } + return streams; +} + +module.exports = { + getDevices, + scanDocument, + deleteJob, + getImageFile, + getImageStream, + getImageFiles, + getImageStreams, + ScannerType, + getServerInfo }; \ No newline at end of file From a51c8c19b1c77f11290f12190d3aa95cda4204c7 Mon Sep 17 00:00:00 2001 From: yushulx Date: Wed, 26 Mar 2025 10:20:17 +0800 Subject: [PATCH 2/8] Add new methods --- README.md | 2 +- examples/REST/app.js | 4 +- examples/REST/public/index.html | 2 +- examples/command-line/app.js | 22 ++++++++-- examples/web/app.js | 5 ++- index.js | 76 +++++++++++++++++++++++++++++++-- 6 files changed, 98 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 89d7ead..49b31cf 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ By default, the REST API's host address is set to `http://127.0.0.1:18622`. ## Node.js API - `getDevices(host, scannerType)` - Get all available scanners. It returns an array of scanner objects. -- `scanDocument(host, parameters, timeout)` - Create a scanner job by feeding one or multiple physical documents. It returns the job id. +- `createJob(host, parameters)` - Create a scanner job by feeding one or multiple physical documents. It returns the job id. - `getImageFile(host, jobId, directory)` - Get one document image by job id. The directory specifies the physical location to save the images. It returns the image path. - `getImageFiles(host, jobId, directory)` - Get document images by job id. The directory specifies the physical location to save the images. It returns an array of image paths. - `deleteJob(host, jobId)` - Delete a scan job by job id. It can interrupt the scan process. diff --git a/examples/REST/app.js b/examples/REST/app.js index 3216fda..b4557f5 100644 --- a/examples/REST/app.js +++ b/examples/REST/app.js @@ -20,7 +20,7 @@ app.get('/devices', (req, res) => { }); }); -app.post('/scandocument', async (req, res) => { +app.post('/createJob', async (req, res) => { const data = req.body; let parameters = { @@ -38,7 +38,7 @@ app.post('/scandocument', async (req, res) => { IfDuplexEnabled: false, }; - let job = await docscan4nodejs.scanDocument(dynamsoftService, parameters); + let job = await docscan4nodejs.createJob(dynamsoftService, parameters); let json = JSON.parse(job); let jobId = json.jobuid; let filename = await docscan4nodejs.getImageFile(dynamsoftService, jobId, './public'); diff --git a/examples/REST/public/index.html b/examples/REST/public/index.html index c904771..c703491 100644 --- a/examples/REST/public/index.html +++ b/examples/REST/public/index.html @@ -62,7 +62,7 @@

Document Scanning Sample

async function acquireImage() { if (devices.length > 0 && selectSources.selectedIndex >= 0) { - const response = await fetch('/scandocument', { + const response = await fetch('/createJob', { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/examples/command-line/app.js b/examples/command-line/app.js index 664407e..2ef5f25 100644 --- a/examples/command-line/app.js +++ b/examples/command-line/app.js @@ -54,6 +54,7 @@ function askQuestion() { let parameters = { license: "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==", device: devices[index].device, + autoRun: false }; parameters.config = { @@ -66,21 +67,34 @@ function askQuestion() { IfDuplexEnabled: false, }; - docscan4nodejs.scanDocument(host, parameters).then((job) => { + docscan4nodejs.createJob(host, parameters).then((job) => { try { let json = JSON.parse(job); - let jobid = json.jobuid; + let jobId = json.jobuid; + (async () => { - let images = await docscan4nodejs.getImageFiles(host, jobid, './'); + let status = await docscan4nodejs.checkJob(host, jobId); + console.log('Job status:', status); + + let caps = await docscan4nodejs.getScannerCapabilities(host, jobId); + console.log('Capabilities:', caps); + + let updateStatus = await docscan4nodejs.updateJob(host, jobId, { + status: docscan4nodejs.JobStatus.RUNNING + }); + console.log('Update status:', updateStatus); + + let images = await docscan4nodejs.getImageFiles(host, jobId, './'); for (let i = 0; i < images.length; i++) { console.log('Image ' + i + ': ' + images[i]); } - await docscan4nodejs.deleteJob(host, jobid); + await docscan4nodejs.deleteJob(host, jobId); askQuestion(); })(); } catch (error) { console.error('Job creation failed:', error.message); + askQuestion(); } }); } diff --git a/examples/web/app.js b/examples/web/app.js index 42f0d76..dae1dfc 100644 --- a/examples/web/app.js +++ b/examples/web/app.js @@ -45,10 +45,13 @@ io.on('connection', (socket) => { IfDuplexEnabled: false, }; - let job = await docscan4nodejs.scanDocument(host, parameters); + let job = await docscan4nodejs.createJob(host, parameters); let json = JSON.parse(job); let jobId = json.jobuid; + let status = await docscan4nodejs.checkJob(host, jobId); + console.log('Job status:', status); + let streams = await docscan4nodejs.getImageStreams(host, jobId); for (let i = 0; i < streams.length; i++) { await new Promise((resolve, reject) => { diff --git a/index.js b/index.js index bd7990a..d9ae4fc 100644 --- a/index.js +++ b/index.js @@ -15,6 +15,11 @@ const ScannerType = { WIATWAINSCANNER: 0x800 }; +const JobStatus = { + RUNNING: 'running', + CANCELED: 'canceled', +}; + // Unified HTTP/HTTPS request handler function request(options) { return new Promise((resolve, reject) => { @@ -171,6 +176,7 @@ async function getDevices(host, scannerType) { try { const response = await request({ url, + method: 'GET', json: true }); @@ -185,7 +191,7 @@ async function getDevices(host, scannerType) { } // Create new scan job -async function scanDocument(host, parameters) { +async function createJob(host, parameters) { const url = `${host}/api/device/scanners/jobs`; try { @@ -207,6 +213,24 @@ async function scanDocument(host, parameters) { } } +// Retrive the job status +async function checkJob(host, jobId) { + const url = `${host}/api/device/scanners/jobs/${jobId}`; + + try { + const response = await request({ + url, + method: 'GET', + json: true + }); + + return response.status === 200 ? response.data : ''; + } catch (error) { + console.error('Scan job creation failed:', error.message); + return ''; + } +} + // Delete existing scan job async function deleteJob(host, jobId) { if (!jobId) return; @@ -222,6 +246,46 @@ async function deleteJob(host, jobId) { } } +// Update existing scan job status (e.g. 'running', canceled) +async function updateJob(host, jobId, parameters) { + const url = `${host}/api/device/scanners/jobs/${jobId}`; + + try { + const response = await request({ + url, + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(JSON.stringify(parameters)) + }, + body: parameters + }); + + return response.status === 200 ? response.data : ''; + } catch (error) { + console.error('Scan job creation failed:', error.message); + return ''; + } +} + +async function getScannerCapabilities(host, jobId) { + const url = `${host}/api/device/scanners/jobs/${jobId}/scanner/capabilities`; + + try { + const response = await request({ + url, + method: 'GET', + json: true + }); + + return response.status === 200 ? response.data : ''; + } + catch (error) { + console.error('Scan job creation failed:', error.message); + return ''; + } +} + // Get multiple image files async function getImageFiles(host, jobId, directory) { const images = []; @@ -249,13 +313,17 @@ async function getImageStreams(host, jobId) { } module.exports = { + ScannerType, + JobStatus, + getServerInfo, getDevices, - scanDocument, + createJob, deleteJob, + updateJob, + checkJob, getImageFile, getImageStream, getImageFiles, getImageStreams, - ScannerType, - getServerInfo + getScannerCapabilities }; \ No newline at end of file From ec5b6aaaca7f0f8744be37d17697871e5c47ab83 Mon Sep 17 00:00:00 2001 From: yushulx Date: Wed, 26 Mar 2025 10:52:10 +0800 Subject: [PATCH 3/8] Update --- index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index d9ae4fc..44e2531 100644 --- a/index.js +++ b/index.js @@ -205,8 +205,12 @@ async function createJob(host, parameters) { }, body: parameters }); + if (response.status !== 201) { + console.log('Job created:', response); - return response.status === 201 ? response.data : ''; + } + + return response.data; } catch (error) { console.error('Scan job creation failed:', error.message); return ''; From ee333b5a8858e61035ce9663565cf937ee48ac91 Mon Sep 17 00:00:00 2001 From: yushulx Date: Wed, 26 Mar 2025 12:27:23 +0800 Subject: [PATCH 4/8] Update --- examples/command-line/app.js | 35 +++++++++--- index.js | 103 ++++++++++++++++++++++++++++++++++- 2 files changed, 127 insertions(+), 11 deletions(-) diff --git a/examples/command-line/app.js b/examples/command-line/app.js index 2ef5f25..bb7b12b 100644 --- a/examples/command-line/app.js +++ b/examples/command-line/app.js @@ -54,7 +54,7 @@ function askQuestion() { let parameters = { license: "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==", device: devices[index].device, - autoRun: false + autoRun: true }; parameters.config = { @@ -73,21 +73,38 @@ function askQuestion() { let jobId = json.jobuid; (async () => { - let status = await docscan4nodejs.checkJob(host, jobId); - console.log('Job status:', status); + // let status = await docscan4nodejs.checkJob(host, jobId); + // console.log('Job status:', status); + + // let caps = await docscan4nodejs.getScannerCapabilities(host, jobId); + // console.log('Capabilities:', caps); + + // let updateStatus = await docscan4nodejs.updateJob(host, jobId, { + // status: docscan4nodejs.JobStatus.RUNNING + // }); + // console.log('Update status:', updateStatus); - let caps = await docscan4nodejs.getScannerCapabilities(host, jobId); - console.log('Capabilities:', caps); - let updateStatus = await docscan4nodejs.updateJob(host, jobId, { - status: docscan4nodejs.JobStatus.RUNNING - }); - console.log('Update status:', updateStatus); let images = await docscan4nodejs.getImageFiles(host, jobId, './'); for (let i = 0; i < images.length; i++) { console.log('Image ' + i + ': ' + images[i]); } + + let info = await docscan4nodejs.getImageInfo(host, jobId); + console.log('Image info:', info); + + let doc = await docscan4nodejs.createDocument(host, {}); + docObj = JSON.parse(doc); + console.log('Document:', doc); + + let docinfo = await docscan4nodejs.getDocumentInfo(host, docObj.uid); + console.log('Document info:', docinfo); + + + let deleteDoc = await docscan4nodejs.deleteDocument(host, docObj.uid); + console.log('Delete document:', deleteDoc); + await docscan4nodejs.deleteJob(host, jobId); askQuestion(); })(); diff --git a/index.js b/index.js index 44e2531..6a81fca 100644 --- a/index.js +++ b/index.js @@ -272,6 +272,7 @@ async function updateJob(host, jobId, parameters) { } } +// Get scanner capabilities async function getScannerCapabilities(host, jobId) { const url = `${host}/api/device/scanners/jobs/${jobId}/scanner/capabilities`; @@ -306,7 +307,6 @@ async function getImageFiles(host, jobId, directory) { // Get multiple image streams async function getImageStreams(host, jobId) { const streams = []; - console.log('Starting stream collection...'); while (true) { const stream = await getImageStream(host, jobId); @@ -316,6 +316,98 @@ async function getImageStreams(host, jobId) { return streams; } +// Get image information +async function getImageInfo(host, jobId) { + const url = `${host}/api/device/scanners/jobs/${jobId}/next-page-info`; + + try { + const response = await request({ + url, + method: 'GET', + json: true + }); + + return response.status === 200 ? response.data : ''; + } catch (error) { + console.error('Image info fetch failed:', error.message); + return ''; + } +} + +//////////////////////// +// Document functions + +// Create a new document +async function createDocument(host, parameters) { + const url = `${host}/api/storage/documents`; + + try { + const response = await request({ + url, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(JSON.stringify(parameters)) + }, + body: parameters + }); + console.log('Document created:', response); + return response.status === 201 ? response.data : ''; + } + catch (error) { + console.error('Document creation failed:', error.message); + return ''; + } +} + +// Get document information +async function getDocumentInfo(host, docId) { + const url = `${host}/api/storage/documents/${docId}`; + + try { + const response = await request({ + url, + method: 'GET', + json: true + }); + + return response.status === 200 ? response.data : ''; + } + catch (error) { + console.error('Document info fetch failed:', error.message); + return ''; + } +} + +// Delete existing document +async function deleteDocument(host, docId) { + const url = `${host}/api/storage/documents/${docId}`; + + try { + const response = await request({ + url, + method: 'DELETE' + }); + + return response.status === 204 ? 'Document deleted' : ''; + } + catch (error) { + console.error('Document deletion failed:', error.message); + } +} + +async function getDocument(host, docId) { + const url = `${host}/api/storage/documents/${docId}/content`; +} + +async function insertPage(host, docId, parameters) { + const url = `${host}/api/storage/documents/${docId}/pages`; +} + +async function deletePage(host, docId, pageId) { + const url = `${host}/api/storage/documents/${docId}/pages/${pageId}`; +} + module.exports = { ScannerType, JobStatus, @@ -329,5 +421,12 @@ module.exports = { getImageStream, getImageFiles, getImageStreams, - getScannerCapabilities + getScannerCapabilities, + getImageInfo, + createDocument, + getDocumentInfo, + deleteDocument, + getDocument, + insertPage, + deletePage }; \ No newline at end of file From 06bccfda2a4b4359b36cb345003a043854bfcd30 Mon Sep 17 00:00:00 2001 From: yushulx Date: Thu, 27 Mar 2025 13:44:58 +0800 Subject: [PATCH 5/8] Update --- examples/command-line/app.js | 29 +++++----- index.js | 108 +++++++++++++++++++++++++++++++++-- 2 files changed, 119 insertions(+), 18 deletions(-) diff --git a/examples/command-line/app.js b/examples/command-line/app.js index bb7b12b..65f716f 100644 --- a/examples/command-line/app.js +++ b/examples/command-line/app.js @@ -63,14 +63,13 @@ function askQuestion() { //XferCount: 1, //PageSize: 1, Resolution: 200, - IfFeederEnabled: false, + IfFeederEnabled: true, IfDuplexEnabled: false, }; docscan4nodejs.createJob(host, parameters).then((job) => { try { - let json = JSON.parse(job); - let jobId = json.jobuid; + let jobId = job.jobuid; (async () => { // let status = await docscan4nodejs.checkJob(host, jobId); @@ -83,26 +82,30 @@ function askQuestion() { // status: docscan4nodejs.JobStatus.RUNNING // }); // console.log('Update status:', updateStatus); - + let doc = await docscan4nodejs.createDocument(host, {}); + // console.log('Document:', doc); let images = await docscan4nodejs.getImageFiles(host, jobId, './'); for (let i = 0; i < images.length; i++) { console.log('Image ' + i + ': ' + images[i]); - } + let info = await docscan4nodejs.getImageInfo(host, jobId); + // console.log('Image info:', info); - let info = await docscan4nodejs.getImageInfo(host, jobId); - console.log('Image info:', info); + let insertPage = await docscan4nodejs.insertPage(host, doc.uid, { password: '', source: info.url }); + // console.log('Insert page:', insertPage); + } - let doc = await docscan4nodejs.createDocument(host, {}); - docObj = JSON.parse(doc); - console.log('Document:', doc); - let docinfo = await docscan4nodejs.getDocumentInfo(host, docObj.uid); - console.log('Document info:', docinfo); + let docinfo = await docscan4nodejs.getDocumentInfo(host, doc.uid); + // console.log('Document info:', docinfo); + // let deletePage = await docscan4nodejs.deletePage(host, doc.uid, insertPage.pages[0].uid); + // console.log('Delete page:', deletePage); + let docfile = await docscan4nodejs.getDocumentFile(host, doc.uid, './'); + console.log('Document file:', docfile); - let deleteDoc = await docscan4nodejs.deleteDocument(host, docObj.uid); + let deleteDoc = await docscan4nodejs.deleteDocument(host, doc.uid); console.log('Delete document:', deleteDoc); await docscan4nodejs.deleteJob(host, jobId); diff --git a/index.js b/index.js index 6a81fca..0c50d09 100644 --- a/index.js +++ b/index.js @@ -121,7 +121,6 @@ async function getImageFile(host, jobId, directory) { // Handle successful write writer.on('finish', () => { - console.log('Saved image to', imagePath); resolve(filename); }); @@ -181,7 +180,6 @@ async function getDevices(host, scannerType) { }); if (response.data.length > 0) { - console.log('Available scanners:', response.data.length); return response.data; } } catch (error) { @@ -203,11 +201,11 @@ async function createJob(host, parameters) { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(JSON.stringify(parameters)) }, + json: true, body: parameters }); if (response.status !== 201) { console.log('Job created:', response); - } return response.data; @@ -262,6 +260,7 @@ async function updateJob(host, jobId, parameters) { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(JSON.stringify(parameters)) }, + json: true, body: parameters }); @@ -349,6 +348,7 @@ async function createDocument(host, parameters) { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(JSON.stringify(parameters)) }, + json: true, body: parameters }); console.log('Document created:', response); @@ -396,16 +396,113 @@ async function deleteDocument(host, docId) { } } -async function getDocument(host, docId) { +// Fetch document file and save to directory +async function getDocumentFile(host, docId, directory) { + const url = `${host}/api/storage/documents/${docId}/content`; + + try { + const response = await request({ + url, + method: 'GET', + stream: true + }); + + if (response.status === 200) { + return new Promise((resolve, reject) => { + const filename = `document_${Date.now()}.pdf`; + const imagePath = path.join(directory, filename); + const writer = fs.createWriteStream(imagePath); + + // Pipe response stream to file + response.stream.pipe(writer); + + // Handle successful write + writer.on('finish', () => { + resolve(filename); + }); + + // Handle errors + const handleError = (err) => { + writer.destroy(); + reject(err); + }; + + writer.on('error', handleError); + response.stream.on('error', handleError); + + // Handle timeout (30 seconds) + const timeout = setTimeout(() => { + handleError(new Error('Download timeout')); + }, 30000); + + writer.on('close', () => clearTimeout(timeout)); + }); + } + } + catch (error) { + console.error('Document fetch failed:', error.message); + } +} + +// Fetch document stream +async function getDocumentStream(host, docId) { const url = `${host}/api/storage/documents/${docId}/content`; + + try { + const response = await request({ + url, + method: 'GET', + stream: true + }); + + if (response.status === 200) { + return response.stream; + } + } catch (error) { + console.error('Stream fetch failed:', error.message); + } + return null; } +// Insert a new page into an existing document async function insertPage(host, docId, parameters) { const url = `${host}/api/storage/documents/${docId}/pages`; + + try { + const response = await request({ + url, + method: 'POST', + headers: { + 'X-DICS-DOC-PASSWORD': parameters.password, + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(JSON.stringify(parameters)) + }, + json: true, + body: parameters + }); + return response.status === 200 ? response.data : ''; + } + catch (error) { + console.error('Page insertion failed:', error.message); + return ''; + } } +// Delete an existing page from a document async function deletePage(host, docId, pageId) { const url = `${host}/api/storage/documents/${docId}/pages/${pageId}`; + + try { + const response = await request({ + url, + method: 'DELETE' + }); + + return response.status === 204 ? 'Page deleted' : ''; + } + catch (error) { + console.error('Page deletion failed:', error.message); + } } module.exports = { @@ -426,7 +523,8 @@ module.exports = { createDocument, getDocumentInfo, deleteDocument, - getDocument, + getDocumentFile, + getDocumentStream, insertPage, deletePage }; \ No newline at end of file From bf9ea928cb883259fd70f551a1b3b8e59b1b7529 Mon Sep 17 00:00:00 2001 From: yushulx Date: Thu, 27 Mar 2025 14:05:53 +0800 Subject: [PATCH 6/8] Update README.md --- README.md | 209 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 146 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 49b31cf..8c8a559 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,171 @@ # Document Scanner SDK for Node.js -The package provides Node.js APIs for invoking **Dynamic Web TWAIN Service REST API**. It helps developers to create **desktop** or **server-side** document scanning applications with ease. - -## Key Features -- πŸ–¨οΈ ​Multi-driver Support - - TWAIN (32-bit & 64-bit) - - WIA (Windows Image Acquisition) - - SANE (Scanner Access Now Easy) - - ICA (Image Capture Architecture) - - eSCL (AirScan/Mopria) -- 🌐 ​Cross-platform Compatibility - - Windows 7+ - - Linux (x64/ARM64/MIPS64) - - macOS 10.15+ - -## Prerequisites -- Install Dynamic Web TWAIN Service. - - Windows: [Dynamsoft-Service-Setup.msi](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.msi) - - macOS: [Dynamsoft-Service-Setup.pkg](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.pkg) - - Linux: - - [Dynamsoft-Service-Setup.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.deb) - - [Dynamsoft-Service-Setup-arm64.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup-arm64.deb) - - [Dynamsoft-Service-Setup-mips64el.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup-mips64el.deb) - - [Dynamsoft-Service-Setup.rpm](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.rpm) - -- Request a [free trial license](https://www.dynamsoft.com/customer/license/trialLicense?product=dwt) for Dynamic Web TWAIN Service. - -## Dynamic Web TWAIN Service Configuration -After installing the Dynamic Web TWAIN Service, navigate to `http://127.0.0.1:18625/` in a web browser to configure the host and port settings. The default host IP address is set to `127.0.0.1`. If you wish to make the service accessible over the local network in your office or company, you can update the host setting to a LAN IP address, such as **192.168.8.72**. + +The package provides Node.js APIs for invoking the **Dynamic Web TWAIN Service REST API**. It helps developers create **desktop** or **server-side** document scanning and processing applications with ease. + +--- + +## πŸš€ Key Features + +- πŸ–¨οΈ **Multi-Driver Support** + - TWAIN (32-bit & 64-bit) + - WIA (Windows Image Acquisition) + - SANE (Scanner Access Now Easy) + - ICA (Image Capture Architecture) + - eSCL (AirScan/Mopria) + - Wi-Fi Direct + +- 🌐 **Cross-Platform Compatibility** + - Windows 7+ + - macOS 10.15+ + - Linux (x64 / ARM64 / MIPS64) + +--- + +## βš™οΈ Prerequisites + +### βœ… Install Dynamic Web TWAIN Service: + +- **Windows**: [Dynamsoft-Service-Setup.msi](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.msi) +- **macOS**: [Dynamsoft-Service-Setup.pkg](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.pkg) +- **Linux**: + - [Dynamsoft-Service-Setup.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.deb) + - [Dynamsoft-Service-Setup-arm64.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup-arm64.deb) + - [Dynamsoft-Service-Setup-mips64el.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup-mips64el.deb) + - [Dynamsoft-Service-Setup.rpm](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.rpm) + +### πŸ”‘ Get a License + +Request a [free trial license](https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform) for the Dynamic Web TWAIN Service. + +--- + +## 🧩 Configuration + +After installation, open `http://127.0.0.1:18625/` in your browser to configure the **host** and **port** settings. + +> By default, the service is bound to `127.0.0.1`. To access it across the LAN, change the host to your local IP (e.g., `192.168.8.72`). ![dynamsoft-service-config](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/e2b1292e-dfbd-4821-bf41-70e2847dd51e) -## REST API Reference -By default, the REST API's host address is set to `http://127.0.0.1:18622`. +--- + +## πŸ“‘ REST API Endpoints + +Default host: `http://127.0.0.1:18622` + +| Method | Endpoint | Description | Parameters | Response | +|--------|----------|-------------|------------|----------| +| GET | `/api/device/scanners` | List available scanners | `type` (optional) | `200 OK` | +| POST | `/api/device/scanners/jobs` | Create a scan job | `license`, `device`, `config` | `201 Created` | +| GET | `/api/device/scanners/jobs/:id/next-page` | Retrieve next image | `id`: Job ID | `200 OK` (image stream) | +| DELETE | `/api/device/scanners/jobs/:id` | Delete a scan job | `id`: Job ID | `204 No Content` | +| GET | `/api/storage/documents/:id/content` | Download document PDF | `id`: Document ID | `200 OK` (PDF stream) | +| POST | `/api/storage/documents` | Create document | `metadata` | `201 Created` | +| POST | `/api/storage/documents/:id/pages` | Insert page into document | `id`, `image`, `password` | `200 OK` | +| DELETE | `/api/storage/documents/:id/pages/:pageId` | Delete page | `id`, `pageId` | `204 No Content` | + +--- + +## πŸ“¦ Node.js APIs + +### πŸ” Scanner APIs + +- `getDevices(host, scannerType)` + Get available scanners. Returns an array of devices. -| Method | Endpoint | Description | Parameters | Response | -|--------|-----------------|-------------------------------|------------------------------------|-------------------------------| -| GET | `/DWTAPI/Scanners` | Get a list of scanners | None | `200 OK` with scanner list | -| POST | `/DWTAPI/ScanJobs` | Creates a scan job | `license`, `device`, `config` | `201 Created` with job ID | -| GET | `/DWTAPI/ScanJobs/:id/NextDocument`| Retrieves a document image | `id`: Job ID | `200 OK` with image stream | -| DELETE | `/DWTAPI/ScanJobs/:id`| Deletes a scan job | `id`: Job ID | `200 OK` | +- `createJob(host, parameters)` + Create a new scan job. Returns a job object. -## Node.js API -- `getDevices(host, scannerType)` - Get all available scanners. It returns an array of scanner objects. -- `createJob(host, parameters)` - Create a scanner job by feeding one or multiple physical documents. It returns the job id. -- `getImageFile(host, jobId, directory)` - Get one document image by job id. The directory specifies the physical location to save the images. It returns the image path. -- `getImageFiles(host, jobId, directory)` - Get document images by job id. The directory specifies the physical location to save the images. It returns an array of image paths. -- `deleteJob(host, jobId)` - Delete a scan job by job id. It can interrupt the scan process. -- `getImageStreams(host, jobId)` - Get document images by job id. It returns an array of image streams. +- `checkJob(host, jobId)` + Check job status (e.g., running, canceled, etc.) -## Parameter Configuration -The parameter configuration is based on [Dynamsoft Web TWAIN documentation](https://www.dynamsoft.com/web-twain/docs/info/api/Interfaces.html#DeviceConfiguration). It controls the behavior of the scanner. +- `deleteJob(host, jobId)` + Delete a scan job and terminate scanning. -For example, you can set the resolution to 200 DPI and the pixel type to color: +- `updateJob(host, jobId, parameters)` + Update job status (e.g., cancel a running job). + +- `getScannerCapabilities(host, jobId)` + Get scanner capabilities like resolution, color modes. + +### πŸ–ΌοΈ Image Retrieval + +- `getImageFile(host, jobId, directory)` + Fetch one image and save to local disk. + +- `getImageFiles(host, jobId, directory)` + Fetch all images for a job and save to local disk. + +- `getImageStream(host, jobId)` + Fetch one image as a readable stream. + +- `getImageStreams(host, jobId)` + Fetch all images as streams. + +- `getImageInfo(host, jobId)` + Retrieve metadata of the next page. + +### πŸ“„ Document APIs + +- `createDocument(host, parameters)` + Create a new empty document (PDF). + +- `getDocumentInfo(host, docId)` + Get document metadata and structure. + +- `deleteDocument(host, docId)` + Delete an existing document. + +- `getDocumentFile(host, docId, directory)` + Download the document and save as a PDF. + +- `getDocumentStream(host, docId)` + Download document as a stream. + +- `insertPage(host, docId, parameters)` + Insert a new page into an existing document. + +- `deletePage(host, docId, pageId)` + Remove a page from an existing document. + +--- + +## βš™οΈ Scan Job Parameters + +The configuration follows [Dynamsoft Web TWAIN DeviceConfiguration](https://www.dynamsoft.com/web-twain/docs/info/api/Interfaces.html#DeviceConfiguration). ```js let parameters = { - license: "LICENSE-KEY", - device: devices[index].device, -}; - -parameters.config = { + license: "LICENSE-KEY", + device: devices[0].device, + config: { IfShowUI: false, - PixelType: 2, // color + PixelType: 2, // Color Resolution: 200, IfFeederEnabled: false, - IfDuplexEnabled: false, + IfDuplexEnabled: false + } }; ``` -## Examples -Set the `LICENSE-KEY` before running the following examples. +--- + +## πŸ§ͺ Examples -- [Command-line](https://github.com/yushulx/dynamsoft-service-REST-API/tree/main/examples/command-line) +Set the `LICENSE-KEY` before running the examples. - Get all available scanners +### πŸ–₯️ Command-line App - ![image](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/24fcb45d-1bea-45ba-9569-b9a2ef377b63) +- [Command-line Example](https://github.com/yushulx/dynamsoft-service-REST-API/tree/main/examples/command-line) - Acquire a Document - - ![image](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/2688269d-4f05-4734-bf1c-7ba4e2638d66) + - Discover devices + ![image](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/24fcb45d-1bea-45ba-9569-b9a2ef377b63) -- [Web server](https://github.com/yushulx/dynamsoft-service-REST-API/tree/main/examples/web) + - Scan and save documents + ![image](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/2688269d-4f05-4734-bf1c-7ba4e2638d66) - ![server-side-document-scan](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/9a161dda-6f9d-473b-a2d4-168ebd5f6b0b) +### 🌐 Web Server App +- [Web Server Example](https://github.com/yushulx/dynamsoft-service-REST-API/tree/main/examples/web) + ![server-side-document-scan](https://github.com/yushulx/dynamsoft-service-REST-API/assets/2202306/9a161dda-6f9d-473b-a2d4-168ebd5f6b0b) From 00a1008503ab952ef39c9fb1983c094dc5b9a0df Mon Sep 17 00:00:00 2001 From: yushulx Date: Thu, 27 Mar 2025 14:08:30 +0800 Subject: [PATCH 7/8] Update --- examples/REST/app.js | 3 +-- examples/web/app.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/REST/app.js b/examples/REST/app.js index b4557f5..1e61da4 100644 --- a/examples/REST/app.js +++ b/examples/REST/app.js @@ -38,8 +38,7 @@ app.post('/createJob', async (req, res) => { IfDuplexEnabled: false, }; - let job = await docscan4nodejs.createJob(dynamsoftService, parameters); - let json = JSON.parse(job); + let json = await docscan4nodejs.createJob(dynamsoftService, parameters); let jobId = json.jobuid; let filename = await docscan4nodejs.getImageFile(dynamsoftService, jobId, './public'); diff --git a/examples/web/app.js b/examples/web/app.js index dae1dfc..934292c 100644 --- a/examples/web/app.js +++ b/examples/web/app.js @@ -45,8 +45,7 @@ io.on('connection', (socket) => { IfDuplexEnabled: false, }; - let job = await docscan4nodejs.createJob(host, parameters); - let json = JSON.parse(job); + let json = await docscan4nodejs.createJob(host, parameters); let jobId = json.jobuid; let status = await docscan4nodejs.checkJob(host, jobId); From 8042d4b5778dde5b40b930be80bf75d952daf10d Mon Sep 17 00:00:00 2001 From: yushulx Date: Thu, 27 Mar 2025 14:32:16 +0800 Subject: [PATCH 8/8] Update --- README.md | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/README.md b/README.md index 8c8a559..c44bc93 100644 --- a/README.md +++ b/README.md @@ -51,20 +51,7 @@ After installation, open `http://127.0.0.1:18625/` in your browser to configure ## πŸ“‘ REST API Endpoints -Default host: `http://127.0.0.1:18622` - -| Method | Endpoint | Description | Parameters | Response | -|--------|----------|-------------|------------|----------| -| GET | `/api/device/scanners` | List available scanners | `type` (optional) | `200 OK` | -| POST | `/api/device/scanners/jobs` | Create a scan job | `license`, `device`, `config` | `201 Created` | -| GET | `/api/device/scanners/jobs/:id/next-page` | Retrieve next image | `id`: Job ID | `200 OK` (image stream) | -| DELETE | `/api/device/scanners/jobs/:id` | Delete a scan job | `id`: Job ID | `204 No Content` | -| GET | `/api/storage/documents/:id/content` | Download document PDF | `id`: Document ID | `200 OK` (PDF stream) | -| POST | `/api/storage/documents` | Create document | `metadata` | `201 Created` | -| POST | `/api/storage/documents/:id/pages` | Insert page into document | `id`, `image`, `password` | `200 OK` | -| DELETE | `/api/storage/documents/:id/pages/:pageId` | Delete page | `id`, `pageId` | `204 No Content` | - ---- +[https://www.dynamsoft.com/web-twain/docs/info/api/restful.html](https://www.dynamsoft.com/web-twain/docs/info/api/restful.html) ## πŸ“¦ Node.js APIs