256 lines
6.4 KiB
TypeScript
256 lines
6.4 KiB
TypeScript
import { Device } from '../abstract/device.abstract.js';
|
|
import { IppProtocol } from './printer.classes.ippprotocol.js';
|
|
import type {
|
|
IPrinterInfo,
|
|
IPrinterCapabilities,
|
|
IPrintOptions,
|
|
IPrintJob,
|
|
IRetryOptions,
|
|
} from '../interfaces/index.js';
|
|
|
|
/**
|
|
* Printer class for IPP network printers
|
|
*/
|
|
export class Printer extends Device {
|
|
public readonly uri: string;
|
|
public supportsColor: boolean = false;
|
|
public supportsDuplex: boolean = false;
|
|
public supportedMediaTypes: string[] = [];
|
|
public supportedMediaSizes: string[] = [];
|
|
public maxCopies: number = 99;
|
|
|
|
private ippClient: IppProtocol | null = null;
|
|
private ippPath: string;
|
|
|
|
constructor(
|
|
info: IPrinterInfo,
|
|
options?: {
|
|
ippPath?: string;
|
|
retryOptions?: IRetryOptions;
|
|
}
|
|
) {
|
|
super(info, options?.retryOptions);
|
|
this.uri = info.uri;
|
|
this.supportsColor = info.supportsColor;
|
|
this.supportsDuplex = info.supportsDuplex;
|
|
this.supportedMediaTypes = info.supportedMediaTypes;
|
|
this.supportedMediaSizes = info.supportedMediaSizes;
|
|
this.maxCopies = info.maxCopies;
|
|
this.ippPath = options?.ippPath ?? '/ipp/print';
|
|
}
|
|
|
|
/**
|
|
* Create a Printer from discovery info
|
|
*/
|
|
public static fromDiscovery(
|
|
discoveredDevice: {
|
|
id: string;
|
|
name: string;
|
|
address: string;
|
|
port: number;
|
|
txtRecords: Record<string, string>;
|
|
},
|
|
retryOptions?: IRetryOptions
|
|
): Printer {
|
|
// Parse capabilities from TXT records
|
|
const txtRecords = discoveredDevice.txtRecords;
|
|
|
|
// Get IPP path from TXT records
|
|
const rp = txtRecords['rp'] || 'ipp/print';
|
|
const ippPath = rp.startsWith('/') ? rp : `/${rp}`;
|
|
|
|
// Parse color support
|
|
const colorSupported =
|
|
txtRecords['Color'] === 'T' ||
|
|
txtRecords['color'] === 'true' ||
|
|
txtRecords['URF']?.includes('W8') ||
|
|
false;
|
|
|
|
// Parse duplex support
|
|
const duplexSupported =
|
|
txtRecords['Duplex'] === 'T' ||
|
|
txtRecords['duplex'] === 'true' ||
|
|
txtRecords['URF']?.includes('DM') ||
|
|
false;
|
|
|
|
// Build printer URI
|
|
const isSecure = txtRecords['TLS'] === '1' || discoveredDevice.port === 443;
|
|
const protocol = isSecure ? 'ipps' : 'ipp';
|
|
const uri = `${protocol}://${discoveredDevice.address}:${discoveredDevice.port}${ippPath}`;
|
|
|
|
const info: IPrinterInfo = {
|
|
id: discoveredDevice.id,
|
|
name: discoveredDevice.name,
|
|
type: 'printer',
|
|
address: discoveredDevice.address,
|
|
port: discoveredDevice.port,
|
|
status: 'online',
|
|
uri: uri,
|
|
supportsColor: colorSupported,
|
|
supportsDuplex: duplexSupported,
|
|
supportedMediaTypes: [],
|
|
supportedMediaSizes: [],
|
|
maxCopies: 99,
|
|
manufacturer: txtRecords['usb_MFG'] || txtRecords['mfg'],
|
|
model: txtRecords['usb_MDL'] || txtRecords['mdl'] || txtRecords['ty'],
|
|
};
|
|
|
|
return new Printer(info, { ippPath, retryOptions });
|
|
}
|
|
|
|
/**
|
|
* Get printer info
|
|
*/
|
|
public getPrinterInfo(): IPrinterInfo {
|
|
return {
|
|
...this.getInfo(),
|
|
type: 'printer',
|
|
uri: this.uri,
|
|
supportsColor: this.supportsColor,
|
|
supportsDuplex: this.supportsDuplex,
|
|
supportedMediaTypes: this.supportedMediaTypes,
|
|
supportedMediaSizes: this.supportedMediaSizes,
|
|
maxCopies: this.maxCopies,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get printer capabilities
|
|
*/
|
|
public async getCapabilities(): Promise<IPrinterCapabilities> {
|
|
if (!this.isConnected) {
|
|
await this.connect();
|
|
}
|
|
|
|
if (!this.ippClient) {
|
|
throw new Error('IPP client not initialized');
|
|
}
|
|
|
|
const caps = await this.withRetry(() => this.ippClient!.getAttributes());
|
|
|
|
// Update local properties
|
|
this.supportsColor = caps.colorSupported;
|
|
this.supportsDuplex = caps.duplexSupported;
|
|
this.supportedMediaSizes = caps.mediaSizes;
|
|
this.supportedMediaTypes = caps.mediaTypes;
|
|
this.maxCopies = caps.maxCopies;
|
|
|
|
return caps;
|
|
}
|
|
|
|
/**
|
|
* Print a document
|
|
*/
|
|
public async print(data: Buffer, options?: IPrintOptions): Promise<IPrintJob> {
|
|
if (!this.isConnected) {
|
|
await this.connect();
|
|
}
|
|
|
|
if (!this.ippClient) {
|
|
throw new Error('IPP client not initialized');
|
|
}
|
|
|
|
this.setStatus('busy');
|
|
this.emit('print:started', options);
|
|
|
|
try {
|
|
const job = await this.withRetry(() => this.ippClient!.print(data, options));
|
|
this.setStatus('online');
|
|
this.emit('print:submitted', job);
|
|
return job;
|
|
} catch (error) {
|
|
this.setStatus('online');
|
|
this.emit('print:error', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all print jobs
|
|
*/
|
|
public async getJobs(): Promise<IPrintJob[]> {
|
|
if (!this.isConnected) {
|
|
await this.connect();
|
|
}
|
|
|
|
if (!this.ippClient) {
|
|
throw new Error('IPP client not initialized');
|
|
}
|
|
|
|
return this.withRetry(() => this.ippClient!.getJobs());
|
|
}
|
|
|
|
/**
|
|
* Get specific job info
|
|
*/
|
|
public async getJobInfo(jobId: number): Promise<IPrintJob> {
|
|
if (!this.isConnected) {
|
|
await this.connect();
|
|
}
|
|
|
|
if (!this.ippClient) {
|
|
throw new Error('IPP client not initialized');
|
|
}
|
|
|
|
return this.withRetry(() => this.ippClient!.getJobInfo(jobId));
|
|
}
|
|
|
|
/**
|
|
* Cancel a print job
|
|
*/
|
|
public async cancelJob(jobId: number): Promise<void> {
|
|
if (!this.isConnected) {
|
|
await this.connect();
|
|
}
|
|
|
|
if (!this.ippClient) {
|
|
throw new Error('IPP client not initialized');
|
|
}
|
|
|
|
await this.withRetry(() => this.ippClient!.cancelJob(jobId));
|
|
this.emit('print:canceled', jobId);
|
|
}
|
|
|
|
/**
|
|
* Connect to the printer
|
|
*/
|
|
protected async doConnect(): Promise<void> {
|
|
this.ippClient = new IppProtocol(this.address, this.port, this.ippPath);
|
|
|
|
// Test connection by checking availability
|
|
const available = await this.ippClient.checkAvailability();
|
|
if (!available) {
|
|
throw new Error('Printer not available');
|
|
}
|
|
|
|
// Fetch capabilities to populate local properties
|
|
await this.getCapabilities();
|
|
}
|
|
|
|
/**
|
|
* Disconnect from the printer
|
|
*/
|
|
protected async doDisconnect(): Promise<void> {
|
|
this.ippClient = null;
|
|
}
|
|
|
|
/**
|
|
* Refresh printer status
|
|
*/
|
|
public async refreshStatus(): Promise<void> {
|
|
try {
|
|
if (this.ippClient) {
|
|
const available = await this.ippClient.checkAvailability();
|
|
this.setStatus(available ? 'online' : 'offline');
|
|
} else {
|
|
this.setStatus('offline');
|
|
}
|
|
} catch (error) {
|
|
this.setStatus('error');
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
export { IppProtocol };
|