npm package: @nppsdk/webview-sdk
Description: NPP 支付平台 WebView SDK - H5 与 App 通信的 JavaScript Bridge
Latest version: 1.0.4

# @nppsdk/webview-sdk

NPP 支付平台 WebView SDK，用于 H5 与 App（Flutter/原生 WebView）通信的 JavaScript Bridge。

源码为 **TypeScript**，构建产物支持 **ES Module**、**CommonJS**、**UMD（Script 标签）** 及 **按需加载**。

---

## 安装

```bash
npm install @nppsdk/webview-sdk
# 或
yarn add @nppsdk/webview-sdk
# 或
pnpm add @nppsdk/webview-sdk
```

---

## 返回值格式

所有方法均返回 `Promise<BridgeResponse<T>>`，**不会 reject**，始终通过 `success` 字段区分成功与失败（判别联合类型）：

```typescript
type BridgeResponse<T> =
  | { success: true; data: T } // 成功：data 类型精确为 T
  | { success: false; error: string }; // 失败：error 一定存在
```

推荐用法：

```typescript
const res = await bridge.login();
if (res.success) {
  console.log(res.data); // TypeScript 自动缩窄，data 类型为 string（会话令牌）
} else {
  console.error(res.error); // error 一定是 string
}
```

---

## 使用方式

### 1. ES Module（推荐，支持按需加载）

**默认导入（整包）：**

```javascript
import NppAppBridge from "@nppsdk/webview-sdk";

const res = await NppAppBridge.pay("ORDER_123");
if (res.success) {
  console.log("支付成功"); // data 为 null，无需读取
} else {
  console.error("支付失败", res.error);
}
```

**加载完成后再调用（ready）：**

```javascript
import NppAppBridge from "@nppsdk/webview-sdk";

NppAppBridge.ready()
  .then(() => {
    // Bridge 已就绪，可安全调用 pay、getLanguage 等
    NppAppBridge.pay("ORDER_123");
  })
  .catch((err) => console.error("Bridge 未就绪", err));

// 或 async/await
async function init() {
  await NppAppBridge.ready();
  const res = await NppAppBridge.getLanguage();
  if (res.success) console.log(res.data); // string，如 "zh-CN"
}
```

**按需导入（只打包用到的 API，体积更小）：**

```javascript
import {
  pay,
  getLanguage,
  loading,
  close,
  login,
  logout,
  clearCache,
} from "@nppsdk/webview-sdk";

const res = await pay("ORDER_123");
if (res.success) console.log("支付成功"); // data 为 null

const lang = await getLanguage();
if (lang.success) console.log(lang.data); // string，如 "zh-CN"

loading(true);
close();
```

**监听原生主动下发的事件：**

```javascript
import { on, off } from "@nppsdk/webview-sdk";

const handler = (data) => console.log("收到原生事件", data);
on("someEvent", handler);

// 移除监听
off("someEvent", handler);
```

**按需导入 + 默认实例：**

```javascript
import NppAppBridge, { call } from "@nppsdk/webview-sdk";

// 使用默认实例
NppAppBridge.pay("ORDER_123");
// 或使用导出的方法（同一单例）
call("customMethod", { foo: 1 });
```

---

### 2. CommonJS

```javascript
const NppAppBridge = require("@nppsdk/webview-sdk");

NppAppBridge.pay("ORDER_123").then((res) => {
  if (res.success) console.log("支付成功");
});
```

按需解构（需打包器支持 tree-shaking CJS）：

```javascript
const { pay, getLanguage, loading, close } = require("@nppsdk/webview-sdk");

pay("ORDER_123").then((res) => {
  if (res.success) console.log("支付成功");
});
```

---

### 3. Script 标签（UMD，全局变量）

适合传统页面、不打包直接引用。

**未压缩（开发）：**

```html
<script src="https://unpkg.com/@nppsdk/webview-sdk/dist/sdk.umd.js"></script>
<script>
  NppAppBridge.pay("ORDER_123").then(function (res) {
    if (res.success) console.log("支付成功");
  });
  NppAppBridge.loading(true);
  NppAppBridge.close();
</script>
```

**压缩版（生产）：**

```html
<script src="https://unpkg.com/@nppsdk/webview-sdk/dist/sdk.umd.min.js"></script>
<script>
  NppAppBridge.pay("ORDER_123");
</script>
```

也可使用 jsDelivr：

```html
<script src="https://cdn.jsdelivr.net/npm/@nppsdk/webview-sdk/dist/sdk.umd.min.js"></script>
```

---

## API 说明

| 方法                   | 参数                                    | 说明                  | 成功时 `data` 类型                                         |
| ---------------------- | --------------------------------------- | --------------------- | ---------------------------------------------------------- |
| `ready()`              | -                                       | Bridge 就绪后 resolve | 不适用（仅等待 bridge 注入完成）                           |
| `call(method, params)` | `method: string, params?: object`       | 调用原生任意方法      | 由原生自定义返回（任意 JSON）                              |
| `pay(orderNo)`         | `orderNo: string`                       | 调起支付              | `null`（无需 data）                                        |
| `getLanguage()`        | -                                       | 获取当前语言          | `string`，如 `"zh-CN"` / `"en-US"` / `"km-KH"` / `"vi-VN"` |
| `loading(show)`        | `show: boolean`                         | 显示/隐藏加载中       | `null`（无需 data）                                        |
| `close()`              | -                                       | 关闭 Web 页           | `null`（无需 data）                                        |
| `login()`              | -                                       | 调起登录              | `string`（会话令牌）                                       |
| `logout()`             | -                                       | 退出登录              | `null`（无需 data）                                        |
| `clearCache()`         | -                                       | 清除缓存              | `null`（无需 data）                                        |
| `on(event, listener)`  | `event: string, listener: (data)=>void` | 监听原生下发事件      | 事件推送的 `data`（任意 JSON）                             |
| `off(event, listener)` | `event: string, listener: (data)=>void` | 移除监听              | -                                                          |

所有与原生交互的方法均返回 `Promise<BridgeResponse<T>>`，**始终 resolve，不会 reject**。
当原生侧处理失败时，H5 侧会得到 `{ success: false, error: string }`。

### 独立工具：H5 拉起支付 / App Deeplink（不经过 Bridge）

用于在页面内打开支付页或自定义 scheme，**不调用原生**；在部分内置 WebView / Mini App 容器中对 http(s) 若存在 `openLink` 则优先使用，避免直接改 `location.href` 导致路径或 query 丢失。

| 函数                       | 参数          | 说明                                                                                         |
| -------------------------- | ------------- | -------------------------------------------------------------------------------------------- |
| `openPaymentDeepLink(url)` | `url: string` | 规范化后跳转：http(s) 在支持 `openLink` 时优先使用；自定义 scheme 走 `window.open` / `location` 兜底 |

```javascript
import { openPaymentDeepLink } from "@nppsdk/webview-sdk";

openPaymentDeepLink("https://pay.example.com/...?order=1");
// 或 alipay://、myapp:// 等，由内部按环境选择合适打开方式
```

---

## 原生 WebView 接入约定

### H5 → 原生（请求格式）

H5 调用任意方法时，会通过 `window.NppWebViewBridge.postMessage(message)` 向原生发送如下 JSON 字符串：

```json
{
  "method": "login",
  "messageId": "abc123xyz",
  "params": {}
}
```

- `method`：方法名，见上方 API 列表
- `messageId`：本次请求的唯一 ID，**原生回传时必须原样携带**，用于匹配回调
- `params`：调用参数，无参数时为 `{}`

### 原生 → H5（响应格式）

原生处理完请求后，调用 `window.NppWebViewBridge.onResponse(response)` 回传结果：

**成功：**

```json
{ "messageId": "abc123xyz", "success": true, "data": <约定的返回值> }
```

**失败：**

```json
{ "messageId": "abc123xyz", "success": false, "error": "错误描述" }
```

### 原生主动推送事件

原生无需等待 H5 请求，可主动下发事件（**不携带 messageId**）：

```json
{ "method": "tokenExpired", "success": true, "data": {} }
```

H5 通过 `bridge.on("tokenExpired", handler)` 监听接收。

---

## 构建与发布 npm

```bash
# 安装依赖
npm install

# 类型检查（源码为 TypeScript）
npm run typecheck

# 打包（生成 dist/）
npm run build
```

打包产物：

- `dist/sdk.esm.js`、`dist/sdk.esm.min.js` — ESM
- `dist/sdk.cjs`、`dist/sdk.cjs.min.js` — CommonJS
- `dist/sdk.umd.js`、`dist/sdk.umd.min.js` — UMD（全局 `NppAppBridge`）

发布到 npm（使用 `--access public` 发布为公开包，任何人可安装）：

```bash
npm login
npm publish --access public
npm publish --access public --registry https://registry.npmjs.org/
```

---

## 环境说明

- 依赖运行环境中的 `window.NppWebViewBridge` 或 `window.flutter_inappwebview`，仅在 **浏览器 / WebView** 内使用有效。
- Node 环境下可正常 `import`/`require`，但调用 `pay`、`getLanguage` 等会返回 `{ success: false, error: '...' }`，不会抛出异常。

---

## License

MIT