nativeFastPrinter.ts 7.3 KB
import type { LabelTemplateData, SystemLabelTemplate } from './types/printer'

type NativePrinterResult = {
  code?: number
  msg?: string
  errMsg?: string
  connected?: boolean
  deviceId?: string
  deviceName?: string
  success?: boolean
  backend?: string
  pluginVersion?: string
  stage?: string
  lastError?: string
  buildMs?: number
  writeMs?: number
  commandBytes?: number
  lastPrintAt?: number
  nativeTextCount?: number
  rasterTextCount?: number
  qrCodeCount?: number
  barcodeCount?: number
  imagePatchCount?: number
  lineCount?: number
  elementCount?: number
  available?: boolean
  lastAction?: string
}

const nativeFastPrinterState: NativePrinterResult = {
  available: false,
  lastAction: 'idle',
}

function getUniApi (): any {
  return uni as any
}

function parsePluginResult (payload: any): NativePrinterResult {
  if (!payload) return {}
  if (typeof payload === 'string') {
    try {
      return JSON.parse(payload)
    } catch (_) {
      return { msg: payload }
    }
  }
  return payload as NativePrinterResult
}

function updateNativeState (patch: NativePrinterResult) {
  Object.assign(nativeFastPrinterState, patch, {
    available: isNativeFastPrinterAvailable(),
  })
}

function getNativePlugin (): any | null {
  // #ifdef APP-PLUS
  try {
    const api = getUniApi()
    if (typeof api.requireNativePlugin !== 'function') return null
    const plugin = api.requireNativePlugin('native-fast-printer')
    return plugin || null
  } catch (_) {
    return null
  }
  // #endif
  // #ifndef APP-PLUS
  return null
  // #endif
}

function ensureNativePlugin (): any {
  const plugin = getNativePlugin()
  if (!plugin) {
    updateNativeState({
      available: false,
      lastAction: 'plugin:missing',
      lastError: 'NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND',
    })
    throw new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND')
  }
  return plugin
}

export function isNativeFastPrinterAvailable (): boolean {
  const plugin = getNativePlugin()
  return !!plugin
    && typeof plugin.connect === 'function'
    && typeof plugin.printTemplate === 'function'
}

export function getNativeFastPrinterState (): NativePrinterResult | null {
  return {
    ...nativeFastPrinterState,
    available: isNativeFastPrinterAvailable(),
  }
}

function buildTimeoutError (action: string, timeoutMs: number): Error {
  const snapshot = getNativeFastPrinterState()
  const detail = [
    `action=${action}`,
    `timeout=${Math.round(timeoutMs / 1000)}s`,
    snapshot?.backend ? `backend=${snapshot.backend}` : '',
    snapshot?.stage ? `stage=${snapshot.stage}` : '',
    snapshot?.commandBytes ? `commandBytes=${snapshot.commandBytes}` : '',
    snapshot?.lastError ? `lastError=${snapshot.lastError}` : '',
  ].filter(Boolean).join('\n')
  return new Error(`Native printer timeout.\n${detail}`.trim())
}

function wrapCallback (
  action: string,
  timeoutMs: number,
  executor: (resolve: (value: NativePrinterResult) => void, reject: (reason?: any) => void) => void
) {
  return new Promise<NativePrinterResult>((resolve, reject) => {
    let settled = false
    const timer = setTimeout(() => {
      if (settled) return
      settled = true
      updateNativeState({
        lastAction: `${action}:timeout`,
      })
      reject(buildTimeoutError(action, timeoutMs))
    }, timeoutMs)

    const done = (handler: () => void) => {
      if (settled) return
      settled = true
      clearTimeout(timer)
      handler()
    }

    executor(
      (value) => done(() => resolve(value)),
      (reason) => done(() => reject(reason)),
    )
  })
}

export function getNativeFastPrinterDebugInfo () {
  return wrapCallback('getDebugInfo', 5000, (resolve, reject) => {
    try {
      const nativePlugin = ensureNativePlugin()
      if (typeof nativePlugin.getDebugInfo !== 'function') {
        const snapshot = getNativeFastPrinterState()
        resolve(snapshot || {})
        return
      }
      nativePlugin.getDebugInfo((payload: any) => {
        const res = parsePluginResult(payload)
        updateNativeState({
          ...res,
          lastAction: 'getDebugInfo',
        })
        resolve(res)
      })
    } catch (error: any) {
      reject(error instanceof Error ? error : new Error(String(error || 'NATIVE_FAST_PRINTER_DEBUG_FAILED')))
    }
  })
}

export function connectNativeFastPrinter (options: {
  deviceId: string
  deviceName?: string
}) {
  return wrapCallback('connect', 12000, (resolve, reject) => {
    try {
      const nativePlugin = ensureNativePlugin()
      if (typeof nativePlugin.connect !== 'function') {
        reject(new Error('NATIVE_FAST_PRINTER_CONNECT_METHOD_NOT_FOUND'))
        return
      }
      nativePlugin.connect({
        deviceId: options.deviceId,
        deviceName: options.deviceName || '',
      }, (payload: any) => {
        const res = parsePluginResult(payload)
        updateNativeState({
          ...res,
          lastAction: 'connect',
        })
        if (res.code === 1 || res.success === true) {
          resolve(res)
          return
        }
        reject(new Error(res.msg || res.errMsg || 'NATIVE_FAST_PRINTER_CONNECT_FAILED'))
      })
    } catch (error: any) {
      reject(error instanceof Error ? error : new Error(String(error || 'NATIVE_FAST_PRINTER_CONNECT_FAILED')))
    }
  })
}

export function disconnectNativeFastPrinter () {
  return wrapCallback('disconnect', 8000, (resolve, reject) => {
    try {
      const nativePlugin = ensureNativePlugin()
      if (typeof nativePlugin.disconnect !== 'function') {
        reject(new Error('NATIVE_FAST_PRINTER_DISCONNECT_METHOD_NOT_FOUND'))
        return
      }
      nativePlugin.disconnect((payload: any) => {
        const res = parsePluginResult(payload)
        updateNativeState({
          ...res,
          lastAction: 'disconnect',
        })
        if (res.code === 1 || res.success === true) {
          resolve(res)
          return
        }
        reject(new Error(res.msg || res.errMsg || 'NATIVE_FAST_PRINTER_DISCONNECT_FAILED'))
      })
    } catch (error: any) {
      reject(error instanceof Error ? error : new Error(String(error || 'NATIVE_FAST_PRINTER_DISCONNECT_FAILED')))
    }
  })
}

export function printNativeFastTemplate (options: {
  deviceId: string
  deviceName?: string
  template: SystemLabelTemplate
  data?: LabelTemplateData
  dpi?: number
  printQty?: number
}) {
  return wrapCallback('printTemplate', 20000, (resolve, reject) => {
    try {
      const nativePlugin = ensureNativePlugin()
      if (typeof nativePlugin.printTemplate !== 'function') {
        reject(new Error('NATIVE_FAST_PRINTER_PRINT_METHOD_NOT_FOUND'))
        return
      }
      nativePlugin.printTemplate({
        deviceId: options.deviceId,
        deviceName: options.deviceName || '',
        templateJson: JSON.stringify(options.template),
        dataJson: JSON.stringify(options.data || {}),
        dpi: options.dpi || 203,
        printQty: options.printQty || 1,
      }, (raw: any) => {
        const res = parsePluginResult(raw)
        updateNativeState({
          ...res,
          lastAction: 'printTemplate',
        })
        if (res.code === 1 || res.success === true) {
          resolve(res)
          return
        }
        reject(new Error(res.msg || res.errMsg || 'NATIVE_FAST_PRINTER_PRINT_FAILED'))
      })
    } catch (error: any) {
      reject(error instanceof Error ? error : new Error(String(error || 'NATIVE_FAST_PRINTER_PRINT_FAILED')))
    }
  })
}