更新记录

1.0.0(2026-08-20) 下载此版本

  • 实现 ABI v1 的 Web 和 Android 通用 WASM 运行器。
  • 增加实例加载、字节调用、释放、大小限制和统一错误码。

平台兼容性

uni-app(5.21)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
- - - - - - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

uni-app x(5.21)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - - -

通用 WASM 运行器(jwt-wasm)

jwt-wasm 是面向 uni-app 和 uni-app x 的 UTS WebAssembly 运行插件。它使用统一的 ABI v1,在 Web 和 Android 端加载 .wasm 二进制、调用模块方法,并以 ArrayBuffer 交换数据。

插件适合承载加密、解密、签名、验签、编解码、压缩和规则计算等纯计算逻辑。 插件本身不提供固定加密算法,也不会在 WASM 内部发起网络请求;具体能力由接入的 WASM 模块决定。

主要能力

  • 从本地静态资源或 HTTPS 服务下载并加载 WASM。
  • 使用统一的 method + ArrayBuffer 协议调用不同业务方法。
  • Web 使用浏览器原生 WebAssembly
  • Android 使用内置 Chicory 1.7.5 运行时。
  • 提供实例释放、模块大小、输出大小和线性内存限制。
  • 提供统一错误码,加载或执行失败不会伪装成成功。

平台兼容性

平台 支持状态 说明
Web 支持 使用浏览器原生 WebAssembly
Android 9+ 支持 最低 API 28,使用 Chicory 1.7.5
iOS 暂不支持 调用时返回 9011001
HarmonyOS 暂不支持 调用时返回 9011001
各类小程序 暂不支持 未提供对应平台实现

Android 插件包含原生 JAR 依赖。使用 HBuilderX 调试 Android 时必须制作自定义基座, 标准基座无法加载插件内置的原生依赖。

安装

将插件导入项目后,确认目录存在:

uni_modules/jwt-wasm

业务代码统一从插件根目录导入,不要直接引用 utssdk 内部文件:

import {
  disposeAllWasm,
  disposeWasm,
  invokeWasm,
  isWasmSupported,
  loadWasm
} from '@/uni_modules/jwt-wasm'

快速开始

1. 下载 WASM

loadWasm 接收的是 ArrayBuffer,不是 URL。先使用 uni.request 下载模块:

function downloadWasm(url, success, fail) {
  uni.request({
    url,
    method: 'GET',
    responseType: 'arraybuffer',
    success: (response) => {
      if (response.statusCode < 200 || response.statusCode >= 300) {
        fail(new Error('WASM 服务返回状态码 ' + response.statusCode))
        return
      }

      if (!(response.data instanceof ArrayBuffer)) {
        fail(new Error('WASM 服务没有返回 ArrayBuffer'))
        return
      }

      success(response.data)
    },
    fail
  })
}

本地 H5 静态资源可以使用:

downloadWasm('/static/module.wasm', onSuccess, onFail)

从服务端动态加载可以使用:

downloadWasm('https://api.example.com/wasm/module.wasm', onSuccess, onFail)

服务端建议返回:

Content-Type: application/wasm
Cache-Control: public, max-age=3600

Web 端跨域下载时,服务端还需要正确配置 Access-Control-Allow-Origin。Android 正式环境应使用 HTTPS,不要通过明文 HTTP 下发可执行模块。

2. 加载并调用

let wasmHandle = ''

function runWasm(wasmBytes) {
  if (!isWasmSupported()) {
    console.error('当前平台不支持 WASM 运行时')
    return
  }

  loadWasm({
    bytes: wasmBytes,
    success: (loaded) => {
      wasmHandle = loaded.handle
      const input = new TextEncoder().encode('hello wasm').buffer

      invokeWasm({
        handle: loaded.handle,
        method: 'echo',
        input,
        success: (result) => {
          const output = new TextDecoder().decode(result.data)
          console.log(output)
        },
        fail: (error) => {
          console.error(error.errCode, error.errMsg)
        },
        complete: () => {
          disposeWasm(loaded.handle)
          wasmHandle = ''
        }
      })
    },
    fail: (error) => {
      console.error(error.errCode, error.errMsg)
    }
  })
}

页面退出时应释放仍在使用的实例:

onUnload() {
  if (this.wasmHandle.length > 0) {
    disposeWasm(this.wasmHandle)
    this.wasmHandle = ''
  }
}

Promise 封装

插件公开 API 使用 uni-app 标准回调形式。需要 async/await 时可以在业务层封装:

function loadWasmAsync(bytes) {
  return new Promise((resolve, reject) => {
    loadWasm({
      bytes,
      success: resolve,
      fail: reject
    })
  })
}

function invokeWasmAsync(handle, method, input) {
  return new Promise((resolve, reject) => {
    invokeWasm({
      handle,
      method,
      input,
      success: (result) => resolve(result.data),
      fail: reject
    })
  })
}

加密二进制请求示例

推荐让 WASM 负责加密和解密,使用 uni.request 负责网络传输:

业务明文 -> invokeWasm(encrypt) -> 二进制密文 -> uni.request
服务端响应 -> 二进制密文 -> invokeWasm(decrypt) -> 业务明文

直接发送二进制请求体:

invokeWasm({
  handle: wasmHandle,
  method: 'encrypt',
  input: new TextEncoder().encode(JSON.stringify(requestData)).buffer,
  success: (encrypted) => {
    uni.request({
      url: 'https://api.example.com/secure',
      method: 'POST',
      header: {
        'Content-Type': 'application/octet-stream'
      },
      data: encrypted.data,
      responseType: 'arraybuffer',
      success: (response) => {
        invokeWasm({
          handle: wasmHandle,
          method: 'decrypt',
          input: response.data,
          success: (decrypted) => {
            const text = new TextDecoder().decode(decrypted.data)
            console.log(JSON.parse(text))
          }
        })
      }
    })
  }
})

抓包显示为二进制只代表请求体是字节数据,不代表 WASM 自己拥有联网能力。真正的 HTTP 请求仍由 uni.request 发出。

API

isWasmSupported()

判断当前平台是否有可用的插件运行时。

function isWasmSupported(): boolean

建议在下载模块前调用。当前 iOS 和 HarmonyOS 返回 false

loadWasm(options)

编译、实例化并校验 ABI v1 模块。

参数 类型 必填 默认值 说明
bytes ArrayBuffer - 完整 WASM 二进制
expectedAbiVersion number 1 期望的 ABI 版本
maxModuleBytes number 8388608 最大模块大小,默认 8 MiB
maxMemoryPages number 256 最大线性内存页数,每页 64 KiB
success Function - 加载成功回调
fail Function - 加载失败回调
complete Function - 成功或失败后都会调用

成功结果:

type WasmLoadResult = {
  handle: string
  abiVersion: number
}

invokeWasm(options)

调用已加载实例的 ABI 入口。

参数 类型 必填 默认值 说明
handle string - loadWasm 返回的实例句柄
method string - 传给 WASM 的 UTF-8 方法名
input ArrayBuffer - 输入字节,可以为空缓冲区
maxOutputBytes number 8388608 最大输出大小,默认 8 MiB
success Function - 调用成功回调
fail Function - 调用失败回调
complete Function - 成功或失败后都会调用

成功结果:

type WasmInvokeResult = {
  data: ArrayBuffer
}

disposeWasm(handle)

释放指定实例。释放后继续使用该句柄会返回 9011006

function disposeWasm(handle: string): boolean

返回 true 表示找到并释放实例,返回 false 表示句柄不存在。

disposeAllWasm()

释放插件当前持有的全部实例:

function disposeAllWasm(): void

ABI v1 接入规范

插件不是任意 WASM 文件的通用函数反射器。接入模块必须实现 ABI v1,并且不得包含 宿主导入。

模块必须导出:

int32_t wasm_abi_version(void);
int32_t wasm_alloc(int32_t size);
void wasm_free(int32_t pointer, int32_t size);
int32_t wasm_call(
    int32_t method_pointer,
    int32_t method_length,
    int32_t input_pointer,
    int32_t input_length,
    int32_t output_meta_pointer
);

同时必须导出名为 memory 的线性内存。

wasm_call 参数

参数 含义
method_pointer UTF-8 方法名字节的起始地址
method_length 方法名字节长度
input_pointer 输入数据起始地址
input_length 输入数据字节长度
output_meta_pointer 宿主分配的 8 字节输出元数据地址

调用成功后,模块需要向 output_meta_pointer 写入两个小端 i32

offset 0: output_pointer
offset 4: output_length

wasm_call 返回 0 表示成功,返回非零值会映射为 9011007

内存所有权

  • 宿主通过 wasm_alloc 分别分配方法名、输入数据和输出元数据。
  • WASM 必须为输出单独分配内存,不得直接把输入缓冲区作为输出返回。
  • 调用结束后,宿主会对方法名、输入、元数据和输出分别调用 wasm_free
  • wasm_free 必须允许释放长度为 0 的逻辑数据所对应的实际分配。
  • 指针与长度必须位于导出的 memory 范围内。

编译要求

  • 不要导入 WASI、JavaScript env 函数或浏览器 API。
  • 不要依赖 fetch、DOM、Node.js API 或宿主文件系统。
  • C/C++、Rust、AssemblyScript 等语言都可以生成模块,但需要自行提供 ABI 适配层。
  • 发布前应同时在 Web 和 Android 真机验证生成的 WASM。

资源限制

  • WASM 文件默认最大 8 MiB。
  • 输出数据默认最大 8 MiB。
  • 线性内存默认最大 256 页,即 16 MiB。
  • Android 在实例构建时强制内存上限。
  • Web 会在实例化后和每次调用后检查内存大小;超限时返回 9011010 并释放实例。

不要加载来源不可信的 WASM。建议服务端为模块提供固定版本号和 SHA-256,并在客户端 加载前校验摘要,避免模块被缓存污染或替换。

错误处理

失败回调接收兼容 IUniError 的错误对象:

fail: (error) => {
  console.error(error.errSubject) // jwt-wasm
  console.error(error.errCode)
  console.error(error.errMsg)
}
错误码 含义 常见原因
9011001 当前平台没有 WASM 运行时 iOS、HarmonyOS 或未实现平台
9011002 模块为空、过大或格式无效 传入非 WASM、下载到 HTML 错误页、超过大小限制
9011003 编译或实例化失败 模块损坏、指令不兼容、包含未提供的导入
9011004 缺少 ABI 必需导出 缺少 memory 或 ABI v1 函数
9011005 ABI 版本不匹配 wasm_abi_version() 返回值不符合预期
9011006 实例句柄无效或已释放 句柄拼写错误、重复释放后继续调用
9011007 模块执行失败 WASM 陷阱、模块返回非零状态码
9011008 内存地址或长度无效 模块返回越界指针或非法长度
9011009 输出超过限制 输出大于 maxOutputBytes
9011010 资源使用超过限制 初始内存或运行后内存超过上限

常见问题

Android 提示原生依赖不能在标准基座生效

插件包含 Chicory JAR,必须重新制作并使用自定义基座。仅重新运行标准基座不能加载 新增的原生依赖。

下载 WASM 时提示只允许 HTTP 或 HTTPS

uni.request 不能读取 App 的 file:// 路径。需要将 WASM 部署到 HTTPS 服务端, 或者在业务层使用 App 文件 API 读取后再把 ArrayBuffer 传给 loadWasm

下载成功但返回 9011002

检查响应状态码和响应内容。常见情况是 URL 实际返回了登录页面、404 HTML 或网关 JSON,而不是以 00 61 73 6d 开头的 WASM 二进制。

WASM 能不能直接调用 uni.request

不能。ABI v1 禁止宿主导入,invokeWasm 也是同步计算调用。应先让 WASM 生成密文或 签名,再由 JS/UTS 调用 uni.request,响应回来后再次调用 WASM 解密或验签。

二进制请求是否等于加密?

不等于。application/octet-stream 只表示请求体是字节数据。是否安全取决于 WASM 模块实际采用的算法和密钥协议。生产环境建议使用 HTTPS,并采用 AES-GCM 或 ChaCha20-Poly1305 等带认证的加密方案,同时加入时间戳、随机 nonce 和防重放校验。

可以把固定密钥写进 WASM 吗?

技术上可以,但不能防止高级逆向。生产环境建议通过登录态协商会话密钥,或使用 服务端公钥和临时密钥派生方案,不要将长期固定密钥永久硬编码在客户端。

发布与生产建议

  1. 仅通过 HTTPS 下载 WASM 和发送业务请求。
  2. 为远程 WASM 使用版本化 URL,更新时同步更新摘要。
  3. 对 WASM 文件进行 SHA-256 或签名校验。
  4. 对加密消息使用认证加密,不要只使用裸流加密。
  5. 服务端校验时间戳、nonce 和请求唯一标识,防止重放。
  6. 使用完实例及时调用 disposeWasm,页面退出时处理兜底释放。
  7. 根据实际业务数据收紧模块、内存和输出大小限制。
  8. Android 发布前使用自定义基座和正式云打包分别进行真机验证。

第三方组件

Android 运行时使用 Chicory 1.7.5:

  • com.dylibso.chicory:runtime:1.7.5
  • com.dylibso.chicory:wasm:1.7.5

Chicory 使用 Apache License 2.0。详细声明见 THIRD_PARTY_NOTICES.md

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。