lingxia-logic 0.6.1

JavaScript runtime for LingXia lightweight application
use crate::i18n::{js_error_from_platform_error, js_internal_error, js_invalid_parameter_error};
use lingxia_service::media::{ScanCodeRequest, ScanType};
use lxapp::{LxApp, lx};
use rong::{FromJSObj, IntoJSObj, JSContext, JSFunc, JSResult, function::Optional};
use serde_json::Value;

#[derive(FromJSObj, Clone, Default)]
struct JSScanOptions {
    #[rename = "onlyFromCamera"]
    only_from_camera: Option<bool>,
    #[rename = "scanType"]
    scan_type: Option<Vec<String>>,
}

#[derive(Debug, Clone, IntoJSObj)]
struct ScanResultObj {
    #[rename = "scanResult"]
    scan_result: String,
    #[rename = "scanType"]
    scan_type: String,
}

pub fn init(ctx: &JSContext) -> JSResult<()> {
    let scan_func = JSFunc::new(ctx, scan)?;
    lx::register_js_api(ctx, "scanCode", scan_func)?;
    Ok(())
}

async fn scan(ctx: JSContext, options: Optional<JSScanOptions>) -> JSResult<ScanResultObj> {
    let lxapp = LxApp::from_ctx(&ctx)?;
    let opts = options.as_ref().cloned().unwrap_or_default();
    let scan_types = parse_scan_types(opts.scan_type)?;
    let only_from_camera = opts.only_from_camera.unwrap_or(true);

    let request = ScanCodeRequest {
        scan_types,
        only_from_camera,
    };

    let data = lingxia_service::media::scan_code(&*lxapp.runtime, request)
        .await
        .map_err(|e| js_error_from_platform_error(&e))?;

    let payload: Value = serde_json::from_str(&data)
        .map_err(|e| js_internal_error(format!("scanCode invalid payload: {}", e)))?;

    let scan_result = payload
        .get("scanResult")
        .and_then(Value::as_str)
        .ok_or_else(|| js_internal_error("scanCode payload missing string `scanResult`"))?
        .to_string();

    let scan_type = payload
        .get("scanType")
        .and_then(Value::as_str)
        .ok_or_else(|| js_internal_error("scanCode payload missing string `scanType`"))?
        .to_string();

    let _ = lingxia_service::applink::handle(&scan_result);

    Ok(ScanResultObj {
        scan_result,
        scan_type,
    })
}

fn parse_scan_types(value: Option<Vec<String>>) -> JSResult<Vec<ScanType>> {
    let mut out: Vec<ScanType> = Vec::new();
    if let Some(list) = value {
        for token in list {
            let t = parse_scan_type_token(token.as_str())
                .ok_or_else(|| js_invalid_parameter_error("invalid scanType token"))?;
            if !out.contains(&t) {
                out.push(t);
            }
        }
    }
    Ok(out)
}

fn parse_scan_type_token(value: &str) -> Option<ScanType> {
    match value {
        "barCode" => Some(ScanType::BarCode),
        "qrCode" => Some(ScanType::QrCode),
        "datamatrix" => Some(ScanType::DataMatrix),
        "pdf417" => Some(ScanType::Pdf417),
        _ => None,
    }
}