@bmsuisse/app-native

中文文档
0.5.0发布于 2026/10/12MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 2446

Web app side of the BMS app: typed calls to window.BMSNative (chrome, toast, share, scan, widgets, Live Activities, approvals, native forms and content) with browser fallbacks.

原始使用文档
文档目录6 节
  1. Installation
  2. Examples
  3. Behavior outside the app
  4. Responses and errors
  5. Overview
  6. License

@bmsuisse/app-native

Web app side of the BMS app: typed calls to window.BMSNative, which the app injects into every web app — with browser fallbacks, so the same page also runs outside the app. No dependencies, no React required.

Backend counterpart: bmsdna-app-native (Python). Detailed guides per topic: docs/, for native forms and content native-surfaces-guide.md.

Installation

npm install @bmsuisse/app-native
# or
bun add @bmsuisse/app-native

Examples

import {
  isNativeApp,
  can,
  on,
  setChrome,
  toast,
  haptic,
  share,
  biometricConfirm,
  scanBarcode,
  scanBarcodes,
  scanText,
  setWidgets,
  getAccessToken,
  navigate,
  addReminder,
  saveContact,
  callPhone,
  toIsoWithOffset,
  canShowNativeForms,
  isNativeSurfaceUnsupported,
  showForm,
  showContent,
} from '@bmsuisse/app-native'

// Is the page running in the app? Does it have a given capability?
if (isNativeApp() && can('customChrome')) {
  setChrome({
    scope: 'route',
    menu: [{ items: [{ id: 'new-order', label: 'New order', icon: 'plus' }] }],
    actionButtons: [{ id: 'search', icon: 'magnifyingglass' }],
  })
}

// Taps in the native menu — instead of setting window.onNativeMenuSelect yourself
const off = on('menuSelect', (id) => navigate(`/${id}`))

// Feedback
toast('Saved', 'success')
haptic('success')

// Share: app → native share sheet, browser → Web Share API / clipboard
await share({ title: 'Order 123', url: location.href })

// Face ID before a critical action (UX only, no proof for the backend)
const { ok } = await biometricConfirm('Approve order?')

// Scan a barcode; the result arrives as an event
on('barcodeScanned', (value) => lookup(value))
await scanBarcode()

// Or let the promise carry the result: several codes, or text from a label
// (docs/scan-guide.md). Keep a way to type the value in.
const codes = await scanBarcodes({ formats: ['ean13', 'qr'], max: 20 })
const serial = await scanText({ pattern: '[A-Z]{2}-\d{4,}' })

// Widgets on the native dashboard
await setWidgets([{ id: 'open-orders', kind: 'kpi', title: 'Open', data: { value: 12 } }])

// Entra token of the app's sign-in. Only needed where the browser cannot set a
// header (WebSocket, EventSource, …): fetch/XHR to your own backend get it automatically.
const session = await getAccessToken()
if (session.ok) socket.send(JSON.stringify({ token: session.token }))

// Other apps on the iPhone: route, reminder, contact, phone call
await navigate({ destination: { name: 'Müller AG', address: 'Industriestrasse 5, 4600 Olten' } })
await addReminder({ title: 'Call Mr. Meier back', due: toIsoWithOffset(new Date(2026, 9, 2, 9)) })
await saveContact({ givenName: 'Anna', organization: 'Müller AG', phones: ['+41 44 000 00 00'] })
await callPhone({ number: '+41 44 000 00 00' })

// Native form (an iOS sheet). The form stays open until your handler settles:
// `{ fieldErrors }` or a thrown error marks fields and lets the user correct.
if (canShowNativeForms()) {
  const res = await showForm(
    {
      id: 'new-customer',
      title: 'New customer',
      fields: [
        { id: 'name', type: 'text', label: 'Company name', required: true },
        { id: 'email', type: 'email', label: 'Email', required: true },
        { id: 'card', type: 'photo', label: 'Business card', max: 1 },
      ],
    },
    { onSubmit: async (values) => await api.createCustomer(values) }
  )
  if (!res.ok && isNativeSurfaceUnsupported(res)) openWebForm() // old app, no capability
} else {
  openWebForm()
}

// Native content: info with blocks and buttons
const shown = await showContent({
  title: 'Miller Ltd',
  blocks: [
    { type: 'facts', items: [{ label: 'Phone', value: '+41 44 000 00 00', kind: 'phone' }] },
  ],
  actions: [{ id: 'new-order', label: 'New order', style: 'primary' }],
})

Behavior outside the app

FunctionBrowser
toast, haptic, setBadge, setChromedoes nothing
shareWeb Share API, otherwise clipboard ('copied')
openExternalnew tab
biometricConfirm{ ok: true } (web flow continues)
navigateroute in Google Maps in a new tab
callPhonetel: link
showForm, showContent, closeSurface{ ok: false, error: 'unavailable' } — keep your web UI (canShowNativeForms())
all others (setWidgets, startLiveActivity, scanDocument, …){ ok: false, error: 'unavailable' }

Responses and errors

Calls with a response return { ok: true, … } or { ok: false, error }. Common errors: capability (the app has not granted the capability to the web app), invalid, cancelled, unavailable, notConfigured; for getAccessToken also forbidden and notSignedIn; for the device actions also denied; for showForm/showContent also forbidden, busy and tooLarge. An app that predates a call answers unknownAction: treat it like unavailable. can(capability) saves unnecessary calls, but the app always checks on its own.

Overview

AreaFunctionsCapability
DetectionisNativeApp, nativeInfo, can–
Eventson('menuSelect' | 'actionButtonTap' | 'bottomBarTap' | 'barcodeScanned' | 'barcodesScanned' | 'textScanned', …)–
ChromesetChrome, centerProminentEntrycustomChrome, customBottomBar
Feedbacktoast, haptic, setBadge–
Systemshare, openExternal, biometricConfirm–
CamerascanDocument, scanBarcode, scanBarcodes, scanText, canScan (guide)camera
WidgetssetWidgets, updateWidget, removeWidgets, refreshWidgetsdashboardWidgets
Live ActivitiesstartLiveActivity, updateLiveActivity, endLiveActivity, getLiveActivitiesliveActivities
ApprovalsrefreshApprovalsapprovals
Sign-ingetAccessTokenidentity
Other appsnavigate, callPhone, addReminder, saveContact, toIsoWithOffsetdeviceApps (reminder, contact)
Native formsshowForm, canShowNativeForms, formLimitWarningsnativeForms (+ camera for camera fields)
Native contentshowContent, canShowNativeContent, contentLimitWarningsnativeContent (+ deviceApps for saveContact/addReminder steps)
SurfacescloseSurface, surfaceResult, isNativeSurfaceUnsupported, on('surfaceAction', …), onSurfaceActionnativeForms or nativeContent
TestingsendTestNotificationnotifications
Raw accesscall, post, send–

License

MIT

版本与发布记录

@bmsuisse/app-native 共发布 6 个版本,首次发布于 2026/10/05;过去 12 个月发布了 6 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.5.0latest2026/10/1226 天前
0.4.12026/10/1226 天前
0.4.02026/10/1226 天前
0.3.02026/10/1128 天前
0.2.02026/10/051 个月前
0.0.12026/10/051 个月前

关于 @bmsuisse/app-native 的常见问题

@bmsuisse/app-native 是什么?

Web app side of the BMS app: typed calls to window.BMSNative (chrome, toast, share, scan, widgets, Live Activities, approvals, native forms and content) with browser fallbacks.

如何安装 @bmsuisse/app-native?

在项目目录运行 npm install @bmsuisse/app-native,或使用 pnpm add / yarn add / bun add。

@bmsuisse/app-native 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bmsuisse/app-native 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@bmsuisse/app-native 可以商用吗?

@bmsuisse/app-native 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bmsuisse/app-native 的下载量有多少?

根据 npm 官方下载统计,@bmsuisse/app-native 在 2026/10/09 – 2026/11/07 这 30 天内被下载 2,446 次,日均约 82 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bmsuisse/app-native 还在维护吗?

最新版本 0.5.0 发布于 2026/10/12(26 天前);至今共发布 6 个版本,其中过去 12 个月发布了 6 个;首次发布于 2026/10/05。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。