Relintio/relintio-vue-agent

GitHub: Relintio/relintio-vue-agent

Relintio 的官方 Vue 3 插件,通过拦截 fetch 和组合式 API 为前端应用提供透明的人机验证挑战处理能力。

Stars: 0 | Forks: 0

Relintio

@relintio/vue-agent

npm quickstart license

用于 Vue 3 的 Relintio agent。

这是一个位于你的组件和 `window.fetch` 之间的 Vue 插件。通过 `app.use(relintio, …)` 安装它,它会包装 `fetch`,因此当你的自有 API 以 `403` 和 `X-Relintio-Challenge` header 拒绝请求时,访问者会看到托管的 challenge,并且在通过后,被拒绝的请求会重试一次。`useRelintio()` 向组件提供响应式状态、`verify()` 和 `challenge()`;`useRelintioChallenge()` 向其提供 iframe 的连接逻辑。入口点是从 `@relintio/vue-agent` 导出的 `relintio` 插件对象,它应被放置在 `main.ts` 中。 ``` // main.ts import { createApp } from 'vue'; import { relintio } from '@relintio/vue-agent'; import App from './App.vue'; createApp(App) .use(relintio, { publishableKey: 'pk_live_...' }) .mount('#app'); ``` ## 安装说明 ``` npm install @relintio/vue-agent ``` Vue 3.3 或更高版本,作为 peer dependency 声明 — 该插件使用了 `onScopeDispose` 和 `shallowRef`,并且没有 Vue 2 构建版本。唯一的 runtime dependency 是 `@relintio/browser-core`,npm 会为你安装它。 ## 注册 `app.use(relintio, { … })` 应放置在你准备挂载的 app 实例的 `main.ts` 中,且必须在 `.mount()` 之前执行。插件的 `install` 方法负责调用 `app.provide`,而 `inject` 只能访问在组件的 `setup` 运行之前就已提供的依赖。因此,如果在挂载之后安装插件,会导致所有已创建的组件都缺少 agent。如果你开启了 `verifyOnMount`,它同样会在 `install` 中触发 — 在第一个组件渲染之前执行,而非之后。 注册是针对每个 app 实例的。在一个页面上调用两次 `createApp` 会生成两个独立的 agent,除非你在两者上都安装了该插件;第二个 app 下的组件无法通过第一个 app 进行 inject。 该插件永远不会抛出异常。如果 key 被拒绝,`install` 会在提供任何内容之前返回,应用会在不受保护的情况下挂载,并在 console 中输出错误 — 因为我们的配置问题而导致客户的应用崩溃是更糟糕的结果。这种选择的代价可见于 [边界情况](#edge-cases)。 ## 配置 `app.use` 的第二个参数是 `RelintioPluginOptions`:即 core 的 `RelintioConfig` 加上 `interceptFetch`。 | 选项 | 默认值 | 含义 | | --- | --- | --- | | `publishableKey` | — | 必填。必须以 `pk_` 开头。任何其他格式都会在数据离开浏览器之前被拒绝。 | | `apiUrl` | `https://api.relintio.com/v1` | 用于 staging 或自托管的覆盖地址。构造时会自动去除一次末尾的斜杠。 | | `challengeTimeoutMs` | `120000` | challenge 可以保持开启的时长。无论你设置得多低,最低都会被限制为 10000。 | | `verifyOnMount` | `false` | 是否在安装时请求判定。除非前端是你唯一可控的层面,否则请保持关闭。 | | `fallbackUrl` | — | 在 `RelintioConfig` 上声明,用于无法向访问者展示 challenge 的情况。当前的 core 中没有任何逻辑会读取它;目前设置它不会改变任何行为。 | | `interceptFetch` | `true` | 包装 `globalThis.fetch`。只有字面量值 `false` 才会关闭此功能。 | ### 应该使用哪个 key 这段代码运行在访问者的浏览器中,因此无论它持有什么 key,加载该页面的所有人都能读取到。该 key 必须是 **publishable key**(`pk_live_…`)。publishable key 在设计上就是公开的,并且只具备一种能力:它可以向 Relintio 请求判定结果。它无法读取你的规则、写入 telemetry,或导致 challenge 被通过。 **licence key**(`UP_LIVE_…`)是用于 challenge passport 和请求签名的 HMAC key。任何持有它的人都可以自行伪造通过你 WAF 的通行凭证。它属于你的服务器、edge 或适配器包,绝对不能被粘贴到 `main.ts`、被 Vite 内联到 bundle 中的 `.env` 文件,或任何其他会被发送到浏览器的内容中。 **如果传入了 licence key,agent 将拒绝启动。** `isUsable()` 会检查该 key 是否是以 `pk_` 开头的字符串;而 licence key 并不符合,因此它会输出一个指明该错误的错误日志并返回 `false`,随后 `install` 会就此停止。此时不会发起任何判定请求,key 也永远不会被传输 — `verify()` 会直接返回 `null` 而完全不调用 `fetch`,core 的测试套件正是通过一个记录自身是否被调用过的 stubbed `fetch` 来断言这一点的。对于空字符串、`undefined`、单纯的 `pk`,或是以 `sk_` 开头的 secret key,处理逻辑也是一样的。 ## 组件中的 challenge `useRelintioChallenge()` 返回状态、一个 template ref 以及 iframe 必须携带的 attributes。它特意返回数据而不是组件:由依赖注入的固定定位 iframe 在别人的设计系统中就是一个布局 bug。markup 和样式应由你自己决定。 ```