scanwith-web-sdk
v3.0.0
Published
Web SDK for ScanWithWeb - Connect web applications to local TWAIN scanners via WebSocket
Maintainers
Readme
ScanWith Web SDK
一个用于通过 WebSocket 连接 Web 应用与本地 TWAIN 扫描仪的 TypeScript SDK。支持 React、Vue 和原生 JavaScript/TypeScript。
特性
- TypeScript 优先: 包含完整的类型定义
- 多框架支持: 核心客户端、React Hooks 和 Vue 3 Composables
- 双协议支持: 根据页面协议自动选择 WS (HTTP) 或 WSS (HTTPS)
- 事件驱动: 订阅连接、扫描进度和图像事件
- 自动重连: 可配置的断线自动重连
- 完整扫描控制: DPI、色彩模式、纸张大小、双面、ADF 支持
- 令牌认证: 安全的会话认证机制
环境要求
- ScanWithWeb 服务: 必须在本地机器上运行 (Windows)
- Node.js: >= 16.0.0
- 浏览器: 支持 WebSocket 的现代浏览器
安装
npm install scanwith-web-sdk
# 或
yarn add scanwith-web-sdk
# 或
pnpm add scanwith-web-sdk快速开始
原生 TypeScript/JavaScript
import { ScanClient } from 'scanwith-web-sdk';
const client = new ScanClient({
host: 'localhost',
secure: window.location.protocol === 'https:',
autoReconnect: true,
});
// 监听扫描图像
client.on('image', (image) => {
const url = URL.createObjectURL(image.blob);
console.log('扫描页面:', image.pageNumber, url);
});
client.on('scanComplete', (totalPages) => {
console.log(`扫描完成: ${totalPages} 页`);
});
// 连接并扫描
async function startScan() {
await client.connect();
await client.authenticate();
const scanners = await client.getScanners();
console.log('可用扫描仪:', scanners);
if (scanners.length > 0) {
await client.selectScanner(scanners[0].name);
await client.scan({
dpi: 300,
pixelType: 'RGB',
paperSize: 'A4',
});
}
}
startScan();React
import { useScanClient, useScanners, useScan } from 'scanwith-web-sdk/react';
function ScannerApp() {
const { client, isConnected, isAuthenticated, error } = useScanClient({
secure: true,
autoConnect: true,
});
const { scanners, selectedScanner, selectScanner, loading } = useScanners(client);
const { scan, stop, images, scanning, currentPage } = useScan(client);
if (error) return <div>错误: {error.message}</div>;
if (!isConnected) return <div>正在连接...</div>;
if (!isAuthenticated) return <div>正在认证...</div>;
return (
<div>
<select
onChange={(e) => selectScanner(e.target.value)}
disabled={loading}
>
<option value="">选择扫描仪</option>
{scanners.map((s) => (
<option key={s.id} value={s.name}>{s.name}</option>
))}
</select>
<button onClick={() => scan({ dpi: 300 })} disabled={scanning || !selectedScanner}>
{scanning ? `正在扫描第 ${currentPage} 页...` : '扫描'}
</button>
<button onClick={stop} disabled={!scanning}>停止</button>
<div className="gallery">
{images.map((img, i) => (
<img key={i} src={URL.createObjectURL(img.blob)} alt={`第 ${i + 1} 页`} />
))}
</div>
</div>
);
}Vue 3
<script setup lang="ts">
import { useFullScanner } from 'scanwith-web-sdk/vue';
const {
isConnected,
isAuthenticated,
error,
scanners,
scanning,
} = useFullScanner({ secure: true });
const handleScan = async () => {
await scanning.scan({ dpi: 300, pixelType: 'RGB' });
};
</script>
<template>
<div v-if="error">错误: {{ error.message }}</div>
<div v-else-if="!isConnected">正在连接...</div>
<div v-else-if="!isAuthenticated">正在认证...</div>
<div v-else>
<select @change="scanners.selectScanner(($event.target as HTMLSelectElement).value)">
<option value="">选择扫描仪</option>
<option
v-for="s in scanners.scanners"
:key="s.id"
:value="s.name"
>
{{ s.name }}
</option>
</select>
<button @click="handleScan" :disabled="scanning.scanning">
{{ scanning.scanning ? `正在扫描第 ${scanning.currentPage} 页...` : '扫描' }}
</button>
<button @click="scanning.stop" :disabled="!scanning.scanning">
停止
</button>
<div class="gallery">
<img
v-for="(url, i) in scanning.imageUrls"
:key="i"
:src="url"
:alt="`第 ${i + 1} 页`"
/>
</div>
</div>
</template>API 参考
ScanClient
WebSocket 通信的核心客户端类。
构造函数选项
interface ScanClientOptions {
host?: string; // 默认: 'localhost'
port?: number; // 默认: 8180 (WS) 或 8181 (WSS)
secure?: boolean; // 默认: 根据页面协议自动检测
autoReconnect?: boolean; // 默认: true
reconnectInterval?: number; // 默认: 3000 (毫秒)
maxReconnectAttempts?: number; // 默认: 5
requestTimeout?: number; // 默认: 30000 (毫秒)
}方法
| 方法 | 描述 | 返回值 |
|------|------|--------|
| connect() | 连接到 WebSocket 服务器 | Promise<void> |
| disconnect() | 断开连接 | void |
| authenticate(clientId?) | 进行身份认证 | Promise<AuthResponse> |
| getScanners() | 获取可用扫描仪列表 | Promise<ScannerInfo[]> |
| selectScanner(name) | 按名称选择扫描仪 | Promise<ScanResponse> |
| getCapabilities() | 获取扫描仪能力 | Promise<ScannerCapabilities> |
| scan(settings?) | 开始扫描 | Promise<ScanResult> |
| stopScan() | 停止当前扫描 | Promise<ScanResponse> |
| ping() | 检查连接健康状态 | Promise<ScanResponse> |
| on(event, callback) | 订阅事件 | () => void (取消订阅函数) |
| off(event, callback) | 取消订阅事件 | void |
| once(event, callback) | 订阅一次性事件 | void |
属性
| 属性 | 类型 | 描述 |
|------|------|------|
| connectionState | ConnectionState | 当前连接状态 |
| isConnected | boolean | 是否已连接到服务 |
| isAuthenticated | boolean | 是否已认证 |
| scanState | ScanState | 当前扫描状态 |
| isScanning | boolean | 是否正在扫描 |
| authToken | string \| null | 当前认证令牌 |
事件
| 事件 | 回调 | 描述 |
|------|------|------|
| connect | () => void | 连接已建立 |
| disconnect | (event: CloseEvent) => void | 连接已关闭 |
| error | (error: Error) => void | 连接错误 |
| authChange | (authenticated: boolean) => void | 认证状态变化 |
| image | (image: ScannedImage) => void | 收到扫描图像 |
| progress | (page: number, total?: number) => void | 扫描进度 |
| scanComplete | (totalPages: number) => void | 扫描完成 |
| scanCancel | () => void | 扫描已取消 |
| scannersUpdate | (scanners: ScannerInfo[]) => void | 扫描仪列表已更新 |
扫描设置
interface ScanSettings {
dpi?: number; // 分辨率 (默认: 200)
pixelType?: PixelType; // 色彩模式 (默认: 'RGB')
paperSize?: PaperSize; // 纸张大小 (默认: 'A4')
duplex?: boolean; // 双面扫描 (默认: false)
showUI?: boolean; // 显示扫描仪原生界面 (默认: false)
source?: string; // 扫描仪源名称
useAdf?: boolean; // 使用自动进纸器 (默认: true)
maxPages?: number; // 最大页数, -1 = 无限制 (默认: -1)
continuousScan?: boolean; // 平板批量扫描模式 (默认: false)
}
type PixelType = 'RGB' | 'Gray8' | 'BlackWhite';
type PaperSize = 'A4' | 'A3' | 'A5' | 'Letter' | 'Legal' | 'B4' | 'B5' | 'USExecutive' | 'Custom';React Hooks
| Hook | 描述 |
|------|------|
| useScanClient(options) | 管理连接和认证 |
| useScanners(client) | 管理扫描仪列表和选择 |
| useScan(client) | 管理扫描操作 |
| useScannersAndScan(client) | 扫描仪和扫描的组合 Hook |
Vue Composables
| Composable | 描述 |
|------------|------|
| useScanClient(options) | 管理连接和认证 |
| useScanners(client, isAuthenticated?) | 管理扫描仪列表和选择 |
| useScan(client) | 管理扫描操作 |
| useFullScanner(options) | 一体化 Composable |
连接模式
SDK 会自动选择适当的 WebSocket 协议:
| 页面协议 | WebSocket | 端口 | 说明 |
|----------|-----------|------|------|
| HTTP (http://) | WS | 8180 | 无加密 |
| HTTPS (https://) | WSS | 8181 | 需要可信证书 |
WSS 证书设置
对于 HTTPS 页面,浏览器必须信任本地证书。有以下几种方式:
- 自动安装 (推荐): ScanWithWeb 服务首次运行时自动将证书安装到 Windows 受信任存储
- 手动接受: 访问
https://localhost:8181并接受证书警告 - 导入证书: 将
.pfx文件导入到系统的受信任根证书存储
错误处理
import { ScanError } from 'scanwith-web-sdk';
try {
await client.scan();
} catch (error) {
if (error instanceof ScanError) {
switch (error.code) {
case 'SCANNER_BUSY':
console.error('扫描仪正忙');
break;
case 'SCANNER_NOT_FOUND':
console.error('未选择扫描仪');
break;
case 'CONNECTION_FAILED':
console.error('连接失败');
break;
case 'REQUEST_TIMEOUT':
console.error('请求超时');
break;
default:
console.error(`错误 [${error.code}]: ${error.message}`);
}
}
}错误代码
| 代码 | 描述 |
|------|------|
| UNAUTHORIZED | 未认证 |
| INVALID_TOKEN | 无效的认证令牌 |
| TOKEN_EXPIRED | 认证令牌已过期 |
| INVALID_REQUEST | 无效的请求格式 |
| SCANNER_NOT_FOUND | 未找到扫描仪 |
| SCANNER_BUSY | 扫描仪正忙 |
| SCAN_FAILED | 扫描操作失败 |
| NO_SCANNERS_AVAILABLE | 未检测到扫描仪 |
| INTERNAL_ERROR | 内部服务器错误 |
| CONNECTION_FAILED | WebSocket 连接失败 |
| REQUEST_TIMEOUT | 请求超时 |
示例
多页文档扫描
const client = new ScanClient({ secure: true });
const images: ScannedImage[] = [];
client.on('image', (image) => {
images.push(image);
console.log(`第 ${image.pageNumber} 页已扫描`);
});
client.on('scanComplete', (totalPages) => {
console.log(`完成: ${totalPages} 页`);
uploadDocument(images);
});
await client.connect();
await client.authenticate();
await client.selectScanner('我的扫描仪');
await client.scan({
dpi: 300,
useAdf: true,
maxPages: -1,
});根据文档类型自定义 DPI
const scanDocument = async (type: 'photo' | 'document' | 'text') => {
const settings: ScanSettings = { paperSize: 'A4' };
switch (type) {
case 'photo':
settings.dpi = 600;
settings.pixelType = 'RGB';
break;
case 'document':
settings.dpi = 300;
settings.pixelType = 'RGB';
break;
case 'text':
settings.dpi = 200;
settings.pixelType = 'BlackWhite';
break;
}
await client.scan(settings);
};浏览器支持
| 浏览器 | 版本 | |--------|------| | Chrome | 60+ | | Firefox | 55+ | | Safari | 11+ | | Edge | 79+ |
许可证
MIT 许可证 - 详见 LICENSE 文件。
相关项目
- ScanWithWeb 服务 - 用于访问扫描仪的 Windows 服务
