npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@andbridge/glowpod

v3.3.1

Published

A axios-based HTTP client library with cross-platform AES encrypt/decrypt support (Browser / Node.js / React Native)

Readme

@andbridge/glowpod

基于 axios 的跨平台 HTTP 客户端:统一拦截器、AES 加解密、结构化错误派发。Node.js / Browser / React Native 三端零配置开箱即用。

Cross-platform axios-based HTTP client: unified interceptors, AES encrypt/decrypt, structured error dispatching. Zero-config out-of-the-box for Node.js / Browser / React Native.

npm version license TypeScript axios


目录 / Table of Contents


简介 / Introduction

@andbridge/glowpod 在 axios 之上提供三个核心能力,让前端 / Node 业务侧只写成功路径:

  1. 拦截器开箱即用:一行 setupInterceptors() 自动注册请求加密、响应解密、业务码解包、错误分类派发
  2. 透明加解密:设置 encryptKey 即可启用 AES 加解密,无需关心底层算法
  3. 结构化错误:HTTP 失败 / 业务码失败 / 网络断开 / 主动取消 自动分类到不同回调

A drop-in enhancement over axios that provides three core capabilities, letting business code focus only on the success path:

  1. Out-of-the-box interceptors: One-line setupInterceptors() auto-registers request encryption, response decryption, business code unwrapping, and error categorization
  2. Transparent encryption: Set encryptKey to enable AES encryption/decryption, no need to know the underlying algorithm
  3. Structured errors: HTTP failures, business code failures, network errors, and cancellations are auto-dispatched to different callbacks
// 业务侧只需 3 行 / Business code needs only 3 lines
import { GlowPod, setupInterceptors } from "@andbridge/glowpod";

const pod = new GlowPod({ baseURL: "https://api.example.com" });
setupInterceptors({ instance: pod.getInstance() });

await pod.Get<User>({ url: "/users/1" });   // 直接拿到 User 类型 / Get User typed directly

安装 / Installation

按平台安装对应依赖:

Install platform-specific dependencies:

Node.js

pnpm add @andbridge/glowpod
# 或 / or npm install / yarn add

Node 端无需额外 peer 依赖(使用内置 node:crypto)。

No extra peer dependencies needed on Node.js (uses built-in node:crypto).

Browser

pnpm add @andbridge/glowpod @noble/ciphers

浏览器使用纯 JS 的 @noble/ciphers(Web Crypto API 不支持 ECB)。

Browser uses pure-JS @noble/ciphers (Web Crypto API doesn't support ECB).

React Native

pnpm add @andbridge/glowpod react-native-quick-crypto
cd ios && pod install

RN 端使用原生 AES 实现的 react-native-quick-crypto(性能比纯 JS 快 3-5x)。

RN uses react-native-quick-crypto for native AES (3-5x faster than pure JS).

⚠️ 每个平台的 peer 是编译期硬绑定的。bundle 内仅引用本端 peer,未安装时直接抛 Cannot find module,无运行时 fallback。

Each platform's peer is compile-time hard-bound. The bundle only references its own peer; if not installed, it throws Cannot find module directly — no runtime fallback.


30 秒上手 / 30-Second Quick Start

// main.ts
import { GlowPod, setupInterceptors } from "@andbridge/glowpod";

// 1. 创建实例 / Create instance
const pod = new GlowPod({ baseURL: "https://api.example.com" });

// 2. 配置拦截器 / Setup interceptors
setupInterceptors({
  instance: pod.getInstance(),
  networkFailedCaller: (response) => {
    if (response?.status === 401) location.href = "/login";
    else console.error("请求失败", response);
  },
});

// 3. 发起请求 / Make request
const users = await pod.Get<{ list: User[] }>({
  url: "/api/v1/users",
  params: { page: 1 },
});
console.log(users.list);

完成。拦截器、错误处理、加解密都已就绪。

Done. Interceptors, error handling, and encryption are all ready.


平台支持 / Platform Support

| 平台 / Platform | 加密实现 / Crypto Backend | 最低版本 / Min Version | 必装 peer / Required Peer | | --------------- | ----------------------------- | ---------------------- | ------------------------------- | | Node.js | node:crypto (AES-ECB) | Node 16+ | 无 / None | | Browser | @noble/ciphers/aes.js | 现代浏览器 / Modern | @noble/ciphers | | React Native | react-native-quick-crypto | RN 0.74+ | react-native-quick-crypto |

axios 已内置打包(~42KB),消费方无需安装。

axios is bundled internally (~42KB); consumers don't need to install it.

统一密文格式:AES-ECB + PKCS7 + Base64(三端互通)。

Unified ciphertext format: AES-ECB + PKCS7 + Base64 (interoperable across platforms).


核心能力 / Core Capabilities

基本 CRUD / Basic CRUD

GlowPod 提供 Get / Post / Put / Delete 四个泛型方法,所有 axios AxiosRequestConfig 字段平铺在 Options 中。

GlowPod provides Get / Post / Put / Delete as four generic methods; all axios AxiosRequestConfig fields are flattened in Options.

// GET - params 作为 query
const res = await pod.Get<PageResult<User>>({
  url: "/api/v1/users",
  params: { page: 1, size: 20 },
});

// POST - params 作为 body
const created = await pod.Post<User>({
  url: "/api/v1/users",
  params: { name: "Alice", age: 18 },
});

// PUT
await pod.Put<User>({ url: "/api/v1/users/1", params: { name: "Bob" } });

// DELETE
await pod.Delete<null>({ url: "/api/v1/users/1" });

默认 params 行为:GET / DELETE → query string;POST / PUT → request body。

Default params behavior: GET / DELETE → query string; POST / PUT → request body.


统一错误处理 / Unified Error Handling

setupInterceptors 按场景自动派发到三个回调,无需业务侧写 try/catch

setupInterceptors auto-dispatches to three callbacks by scenario — no try/catch in business code.

| 触发条件 / Trigger Condition | 派发目标 / Dispatched To | | ---------------------------------------------------------------- | ------------------------------------------------- | | 网络层错误(断网 / 超时 / DNS 失败 / CORS 拒绝 / 后端无响应) | serverKillCaller(error) — 错误对象本身 | | HTTP 失败(response.status 不在 [200,299]) | networkFailedCaller(error.response) | | 业务码失败(data[codeName] !== businessSuccessCode) | networkFailedCaller(error.response) | | 拦截器内部抛错(加解密失败 / setHeaders / beforeRequest 等) | networkFailedCaller(error.response) | | 主动取消(AbortController.abort()) | requestCancelCaller(error)(默认静默) |

setupInterceptors({
  instance: pod.getInstance(),
  responseStruct: {
    codeName: "code",        // 业务码字段名 / Business code field name
    dataName: "data",        // 业务数据字段名 / Business data field name
    messageName: "message",  // 业务消息字段名(仅用于回调内自取)
  },
  networkFailedCaller: (response) => {
    // response 是 AxiosError.response(HTTP 非 2xx / 业务码失败 / 内部抛错)
    if (response?.status === 401 || response?.status === 403) {
      location.href = "/login";
      return;
    }
    const msg = (response?.data as any)?.message ?? "请求失败";
    toast.error(msg);
  },
  serverKillCaller: (error) => {
    // error 是 AxiosError(含 request 字段、无 response 字段)
    toast.error("网络异常,请检查连接");
  },
  requestCancelCaller: (error) => {
    console.log("请求已取消", error);
  },
});

响应解包规则(HTTP 2xx 时按 responseStruct 解包):

Response unwrapping rules (HTTP 2xx unwrapped by responseStruct):

| 场景 / Scenario | 返回 / Returns | | --------------------------------------------------------------------- | --------------------------------------- | | data[codeName] == businessSuccessCodedata[dataName] 存在 | response.data[dataName] | | data[codeName] == businessSuccessCodedata[dataName] 缺失 | response.data | | data[codeName] 字段不存在 | response.data | | responseStruct: null | response.data(不解包) | | data[codeName] 存在但 !== businessSuccessCode | 抛 BusinessErrornetworkFailedCaller |

短路默认处理:在 catch 中调用 hiddenDefaultErrorHandler() 可阻止默认回调:

Bypass default handling: Call hiddenDefaultErrorHandler() in .catch() to suppress default callbacks:

import type { GlowPodRequestError } from "@andbridge/glowpod";

pod.Get({ url: "/users/1" }).catch((err: GlowPodRequestError) => {
  err.hiddenDefaultErrorHandler();
  // 自定义处理 / Custom handling
  toast.warning(err.data?.message ?? "请求失败");
});

自动加解密 / Automatic Encryption

设置 encryptKey 即启用自动加解密:

Set encryptKey to enable automatic encryption/decryption:

pod.Post({
  url: "/api/v1/users",
  params: { name: "Alice" },
  encryptKey: "16-byte-key-here",   // 16/24/32 字节 / 16/24/32 bytes
});

执行顺序 / Execution Order:

请求拦截器:  setHeaders → beforeRequest → 加密 params/data → 发送
Response:   接收 → 解密响应 → afterResponse → 响应解包 → 返回 T

默认行为约定 / Default Behavior:

  • POST / PUT:params 加密后作为 request body
  • GET / DELETE:params 加密后包装为 { [encryptPayloadField]: encrypted } 作为 query
  • 响应:字符串响应体自动解密为对象,后续 code / data 解包在明文上执行
  • 跳过条件:responseType === "blob" 或响应体不是字符串

GET 加密字段名可通过 encryptPayloadField 自定义(默认 "data"):

GET query field name can be customized via encryptPayloadField (default "data"):

pod.Get({
  url: "/search",
  params: { q: "alice" },
  encryptKey: "my-key",
  encryptPayloadField: "payload",  // → ?payload=<encrypted>
});

跳过加解密:支持单独关闭请求或响应阶段。

Skip encryption: Support closing request or response phase separately.

pod.Get({
  url: "/raw",
  encryptKey: "shared-key",
  closeRequestEncrypt: true,   // 不加密请求(params 明文传输)
});

pod.Post({
  url: "/legacy",
  params: { raw: "data" },
  encryptKey: "shared-key",
  closeResponseEncrypt: true,  // 不解密响应(密文原样返回)
});

自定义加解密算法 / Custom Encryption Algorithm

实例级配置 / Instance-Level Config

import CryptoJS from "crypto-js";

const IV = CryptoJS.enc.Utf8.parse("0102030405060708");

setupInterceptors({
  instance: pod.getInstance(),
  encryptCaller: async (data, key) =>
    CryptoJS.AES.encrypt(JSON.stringify(data), key, {
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7,
      iv: IV,
    }).toString(),
  decryptCaller: async (encrypted, key) => {
    const bytes = CryptoJS.AES.decrypt(encrypted, key, {
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7,
      iv: IV,
    });
    return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
  },
});

单请求覆盖 / Single Request Override

import type { EncryptCaller, DecryptCaller } from "@andbridge/glowpod";

const sm4Encrypt: EncryptCaller = async (data, key) => "...";
const sm4Decrypt: DecryptCaller = async (encrypted, key) => "...";

pod.Post({
  url: "/special",
  params: { id: 1 },
  encryptKey: "sm4-key",
  encryptCaller: sm4Encrypt,
  decryptCaller: sm4Decrypt,
});

优先级链 / Priority Chain: 请求级 Options > 实例级 setupInterceptors > 内置默认实现。


动态 Token 注入 / Dynamic Token Injection

两种钩子,按场景选择:

Two hooks, choose by scenario:

| 钩子 / Hook | 时机 / Timing | 适用场景 / Use Case | | ------------------ | ---------------- | ------------------------------------------------- | | setHeaders | 加密之前 | Token 也会被加密传输(推荐用于鉴权 Token) | | beforeRequest | 加密之前 | 改 config.params / config.data(加密会覆盖) |

// 方式一:setHeaders(Token 会被加密传输)
setupInterceptors({
  instance: pod.getInstance(),
  setHeaders: async (headers) => {
    const token = await getToken();
    headers.set("Authorization", `Bearer ${token}`);
  },
});

// 方式二:beforeRequest(修改 params,但 GET 会被加密覆盖)
setupInterceptors({
  instance: pod.getInstance(),
  beforeRequest: async (config) => {
    // 添加非加密参数(如 _t 时间戳防缓存)
    config.params = { ...config.params, _t: Date.now() };
    return config;
  },
});

响应后处理 / Response Post-Processing

afterResponse 在解密后、解包前执行,可短路或自定义响应。

afterResponse runs after decryption but before unwrapping; can short-circuit or customize.

setupInterceptors({
  instance: pod.getInstance(),
  afterResponse: async (data, raw) => {
    // data: 已解密明文 / decrypted plain data
    // raw:  解密前原始 response(未启用解密时为 undefined)

    if ((data as any)?.code === 403) {
      router.push("/forbidden");
    }

    // 返回值语义 / Return value semantics:
    // - undefined: 继续默认解包 / continue default unwrapping
    // - 任意值:   短路,直接作为响应返回 / short-circuit, return as response
    return data;
  },
});

解密失败的降级处理:解密抛错时 dataundefinedraw 是密文。可在 afterResponse 内提前 try/catch 降级。

Graceful degradation on decryption failure: When decryption throws, data is undefined and raw holds the ciphertext. Try/catch inside afterResponse for fallback.

afterResponse: async (data, raw) => {
  if (!data && raw) {
    return { code: -1, data: null, message: "解密失败" };
  }
  return data;
},

文件下载 / File Download

设置 responseType: "blob" 跳过加解密和解包:

Set responseType: "blob" to skip encryption/decryption and unwrapping:

const response = await pod.Get({
  url: "/api/v1/download",
  params: { fileId: "123" },
  responseType: "blob",
});

// response 是原始 AxiosResponse
const blob = response.data as Blob;
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "file.zip";
a.click();
URL.revokeObjectURL(url);

请求取消 / Request Cancellation

使用 axios 原生 AbortController

Use axios native AbortController:

const controller = new AbortController();

pod.Get({
  url: "/api/v1/users",
  signal: controller.signal,
});

// 取消
controller.abort();

取消时的派发:取消会抛 AxiosErrorcode: "ERR_CANCELED"),默认静默。需要在取消时执行清理时,配置 requestCancelCaller

Cancellation dispatching: Cancellation throws AxiosError (code: "ERR_CANCELED"), silently by default. Configure requestCancelCaller for cleanup:

// 全局配置 / Global
setupInterceptors({
  instance: pod.getInstance(),
  requestCancelCaller: (error) => console.log("已取消", error),
});

// 或单请求覆盖 / Or per-request override
pod.Get({
  url: "/users",
  signal: controller.signal,
  requestCancelCaller: () => {/* 覆盖全局 */},
});

未配置 requestCancelCaller 时,错误仍向上抛,可在 .catch() 中通过 err.code === "ERR_CANCELED" 自判:

Without requestCancelCaller, error still propagates; check in .catch():

try {
  await pod.Get({ url: "/users", signal: controller.signal });
} catch (err: any) {
  if (err?.code === "ERR_CANCELED") return;
  throw err;
}

自定义业务错误 / Custom Business Errors

业务侧在 setHeaders / beforeRequest 主动抛错会被分类到 networkFailedCaller。如需在 catch 处单独处理:

Business-side errors thrown from setHeaders / beforeRequest are dispatched to networkFailedCaller. To handle separately in .catch():

beforeRequest: async (config) => {
  if (!config.params?.userId) {
    const err: any = new Error("缺少 userId");
    err.business = true;   // 打标记
    throw err;
  }
  return config;
},

// catch 处
pod.Get({ url, params }).catch((err: any) => {
  if (err.business) {
    err.hiddenDefaultErrorHandler?.();
    toast("参数错误");
  }
});

进阶 / Advanced

TypeScript 类型 / TypeScript Types

完整类型导出:

Complete type exports:

import type {
  GlowPod,              // class
  Options,              // 单请求配置 / Per-request config
  InterceptorsOptions,  // 全局拦截器配置 / Global interceptor config
  ResponseStruct,       // { codeName?, dataName?, messageName? }
  EncryptCaller,        // (data: unknown, key: string) => Promise<string>
  DecryptCaller,        // (encrypted: string, key: string) => Promise<unknown>
  RequestCancelCaller,  // (error: AxiosError) => void
  GlowPodRequestError,  // AxiosError & { hiddenDefaultErrorHandler(): void }
} from "@andbridge/glowpod";

泛型推断 / Generic inference:

const user = await pod.Get<User>({ url: "/users/1" });
//    ^? User

interface PageResult<T> { list: T[]; total: number; }
const page = await pod.Get<PageResult<User>>({ url: "/users", params: { page: 1 } });
//    ^? PageResult<User>

多实例 / Multiple Instances

不要对同一 instance 多次调用 setupInterceptors(错误派发状态会污染)。多套拦截器需求下创建多个实例:

Don't call setupInterceptors multiple times on the same instance (error dispatch state will be polluted). Use multiple instances for multi-tenant / multi-backend:

const mainPod = new GlowPod({ baseURL: BASE_URL_A });
const adminPod = new GlowPod({ baseURL: BASE_URL_B });

setupInterceptors({ instance: mainPod.getInstance(), /* callbacks A */ });
setupInterceptors({ instance: adminPod.getInstance(), /* callbacks B */ });

手动加解密 / Manual Encryption

不通过拦截器,直接调用底层加解密工具:

Without interceptors, directly call the underlying encryption utility:

import { encrypt, decrypt } from "@andbridge/glowpod";

const ciphertext = await encrypt("16-byte-key-here", { name: "Alice" });
const plaintext = await decrypt("16-byte-key-here", ciphertext);

默认实现:AES-128/192/256-ECB + PKCS7 + Base64。key 必须是 16/24/32 字节(UTF-8 编码后)。

Default implementation: AES-128/192/256-ECB + PKCS7 + Base64. key must be 16/24/32 bytes (UTF-8 encoded).

明文格式:默认实现先把对象用 Jackson 风格 JSON.stringify(obj, null, 2) 格式化(键与冒号间带空格:{"a" : 1}),再走 AES-ECB 加密。如需与 crypto-js 或其它后端互通,请确保明文格式一致,或整体替换为自定义 encryptCaller / decryptCaller

Plaintext format: The default implementation first formats objects in Jackson-style JSON.stringify(obj, null, 2) (space between key and colon: {"a" : 1}), then encrypts with AES-ECB. For interop with crypto-js or other backends, ensure plaintext format consistency, or replace with custom encryptCaller / decryptCaller.


__PLATFORM__ 公共常量 / Public Constant

每个 bundle 都导出 __PLATFORM__: "nodejs" | "browser" | "reactnative",值为编译期确定的字符串字面量。

Each bundle exports __PLATFORM__: "nodejs" | "browser" | "reactnative", the value of which is a compile-time determined string literal.

import { __PLATFORM__ } from "@andbridge/glowpod";

if (__PLATFORM__ === "browser") {
  // 浏览器专属逻辑 / Browser-only logic
}

console.log("Running on:", __PLATFORM__);  // 用于埋点 / 调试 / Used for analytics / debugging

API 参考 / API Reference

GlowPod 类 / Class

import { GlowPod } from "@andbridge/glowpod";

class GlowPod {
  constructor(options: CreateAxiosDefaults);  // 与 axios.create() 同参
  getInstance(): AxiosInstance;
  Get<T>(options: Options): Promise<T>;
  Post<T>(options: Options): Promise<T>;
  Put<T>(options: Options): Promise<T>;
  Delete<T>(options: Options): Promise<T>;
}

Options 字段 / Options fields:

| 字段 / Field | 类型 / Type | 默认 / Default | 说明 / Description | | ------------------------- | ------------------------------------------------- | ----------------------- | ------------------------------------------------------------------ | | url | string | 必填 / required | 请求路径 / Request path | | params | object \| null | undefined | GET/DELETE → query; POST/PUT → body | | headers | RawAxiosRequestHeaders \| AxiosHeaders | undefined | 顶层平铺,不做合并 / Flat, no merge | | encryptKey | string | undefined | 启用自动加解密 / Enable automatic encryption | | encryptCaller | EncryptCaller | undefined | 单请求覆盖加密 / Per-request encryption override | | decryptCaller | DecryptCaller | undefined | 单请求覆盖解密 / Per-request decryption override | | encryptPayloadField | string | "data" | GET 加密后 query 字段名 / GET query field after encryption | | requestCancelCaller | RequestCancelCaller | undefined | 覆盖全局取消回调 / Override global cancellation callback | | closeRequestEncrypt | boolean | false | 跳过请求加密 / Skip request encryption | | closeResponseEncrypt | boolean | false | 跳过响应解密 / Skip response decryption | | 其他 / Others | - | - | 顶层平铺 AxiosRequestConfigresponseType / timeout / signal / baseURL 等 |


setupInterceptors 函数 / Function

import { setupInterceptors } from "@andbridge/glowpod";

function setupInterceptors(options: InterceptorsOptions & { instance: AxiosInstance }): void;

InterceptorsOptions 字段 / InterceptorsOptions fields:

| 字段 / Field | 类型 / Type | 默认 / Default | 说明 / Description | | --------------------------- | -------------------------------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------- | | instance | AxiosInstance | 必填 / required | pod.getInstance() | | serverKillCaller | (error: AxiosError) => void | noop | 网络层错误 / Network-layer errors | | networkFailedCaller | (response: AxiosResponse \| undefined) => void | noop | HTTP 非 2xx / 业务码失败 / 内部抛错 | | requestCancelCaller | (error: AxiosError) => void | undefined | 主动取消 / Manual cancellation | | responseStruct | ResponseStruct \| null | { codeName: "code", dataName: "data" } | 传 null 跳过解包 / Pass null to skip unwrapping | | businessSuccessCode | string | "0" | 业务码成功判定值 / Business success code value | | encryptCaller | EncryptCaller | 内置 AES-ECB / Built-in AES-ECB | 实例级默认加密 / Instance-level default encryption | | decryptCaller | DecryptCaller | 内置 AES-ECB / Built-in AES-ECB | 实例级默认解密 / Instance-level default decryption | | encryptPayloadField | string | "data" | 实例级 GET query 字段名 / Instance-level GET query field | | setHeaders | (headers: AxiosRequestHeaders) => Promise<void> | noop | 加密之前执行 / Runs before encryption | | beforeRequest | (config: InternalAxiosRequestConfig) => Promise<config> | noop | 加密之前执行 / Runs before encryption | | afterResponse | (data, raw?) => Promise<any> | noop | data 已解密;raw 是原始密文 response / data decrypted; raw is original ciphertext response |


encrypt / decrypt 函数 / Functions

import { encrypt, decrypt } from "@andbridge/glowpod";

function encrypt<T>(key: string, data: T): Promise<string>;
function decrypt(key: string, encrypted: string): Promise<unknown>;

默认实现:AES-ECB + PKCS7 + Base64。

Default implementation: AES-ECB + PKCS7 + Base64.


__PLATFORM__ 常量 / Constant

import { __PLATFORM__ } from "@andbridge/glowpod";

const platform: "nodejs" | "browser" | "reactnative" = __PLATFORM__;

值在编译期固定,由 rollup replace 插件注入。

Value is fixed at compile time, injected by rollup's replace plugin.


GlowPodRequestError 类型 / Type

import type { GlowPodRequestError } from "@andbridge/glowpod";

type GlowPodRequestError = AxiosError & {
  hiddenDefaultErrorHandler(): void;  // 调用以阻止默认错误回调
};

常见问题 / FAQ

各平台必装什么?/ What peer dependencies per platform?

| 平台 / Platform | 必装 / Required | 何时 / When | | --------------- | ------------------------ | ---------------------------------------- | | Node.js | 无 / None | 默认使用 node:crypto | | Browser | @noble/ciphers | 浏览器 bundle 静态引用 noble | | React Native | react-native-quick-crypto | RN bundle 静态引用 quick-crypto(推荐,原生 AES 性能) |

每个平台的 peer 是编译期硬绑定的——未安装直接抛 Cannot find module,无运行时 fallback。

Each platform's peer is compile-time hard-bound — uninstalled peers throw Cannot find module directly; no runtime fallback.


Metro 需要配置吗?/ Does Metro need any configuration?

不需要。3.3.0+ 使用 双轨入口声明,同时覆盖所有 Metro 版本:

No configuration needed. 3.3.0+ uses dual-rail entry declarations covering all Metro versions:

  1. 顶层 "react-native" 字段:Metro 所有版本(0.60+ 至 0.83+)直接读取,最可靠的兜底入口 Top-level "react-native" field: Read directly by all Metro versions (0.60+ to 0.83+); the most reliable fallback entry
  2. exports["."].react-native 条件:新版 Metro(0.79+)与 Node.js 解析器使用 exports["."].react-native condition: Used by newer Metro (0.79+) and Node.js resolver

bundle 内静态 import react-native-quick-crypto,CJS bundle 零 import( 调用。

Bundles statically import react-native-quick-crypto; CJS bundles have zero import( calls.

历史说明:早期版本曾在 react-native.config.cjs 里提供 blockList: [/^node:.*/],实际无效(Metro 的 blockList 匹配的是已解析的文件绝对路径,node: protocol 在解析阶段就失败了)。如果你的 metro.config.js 里手写了这段配置,可以删除。

Historical note: Earlier versions provided blockList: [/^node:.*/] in react-native.config.cjs, which was actually ineffective (Metro's blockList matches resolved absolute file paths; node: protocol fails at the resolve stage). If your metro.config.js has this config, it can be removed.

Metro 报错 Unable to resolve module node:crypto 怎么办?/ Metro error Unable to resolve module node:crypto?

确认你使用的是 @andbridge/glowpod >= 3.3.0(含双轨入口声明)。如果升级后仍报错:

Verify you're using @andbridge/glowpod >= 3.3.0 (with dual-rail entry declarations). If the error persists after upgrading:

# 1. 清除 Metro 缓存
rm -rf $TMPDIR/metro-cache  # macOS
# 或 / or
rm -rf node_modules/.cache/metro

# 2. 重新安装 pods
cd ios && pod install && cd ..

# 3. 重启 Metro
npx react-native start --reset-cache

如果仍无法解决,可在 metro.config.js 显式配置 resolver:

If it still fails, configure the resolver explicitly in metro.config.js:

// metro.config.js
const { getDefaultConfig } = require("@react-native/metro-config");
const config = getDefaultConfig(__dirname);

config.resolver.resolveRequest = (context, moduleName, platform) => {
  if (moduleName === "@andbridge/glowpod") {
    return context.resolveRequest(
      context,
      "@andbridge/glowpod/build/glowpod.react-native.cjs",
      platform,
    );
  }
  return context.resolveRequest(context, moduleName, platform);
};

module.exports = config;

与原生 axios 的核心差异?/ Key differences from vanilla axios?

| 维度 / Dimension | 原生 axios / Vanilla axios | @andbridge/glowpod | | ----------------------- | --------------------------- | -------------------------------------------------------- | | 拦截器 / Interceptors | 每个 instance 手动 use | 一行 setupInterceptors 搞定 / One-line setup | | 业务码解包 / Code unwrap | 不支持 / Not supported | 内置 codeName / dataName 联动解包 | | 错误分类 / Error type | 需手写 try/catch | 三回调自动派发 + 取消可选 / Three-callback auto-dispatch | | 加解密 / Encryption | 完全不涉及 / Not involved | 一键启用 + 可替换算法 / One-key enable + replaceable | | 鉴权跳转 / Auth redirect | 每个 instance 写一遍 | 在 networkFailedCaller 内自判 response.status | | 业务代码 / Business code | 经常写 try/catch | 成功路径只写业务,失败由回调兜底 |


升级迁移 / Migration

跨版本迁移指南见 ARCHITECTURE.md → 升级迁移

For cross-version migration guide, see ARCHITECTURE.md → Migration.


License

ISC