wanxiangbaiying
v0.1.1
Published
Framework-neutral third-party OAuth login with Electron host adapter
Readme
wanxiangbaiying:万象百应登录 SDK
2026-09-16,内部开发版 0.1.1。npm 包名为 wanxiangbaiying,API 名称 initWanxiangLogin / wanxiangLogin 与浏览器构建 wanxiangLogin.js 保持不变。SDK 专供三方应用接入万象授权;平台客户端的授权服务直接实现在平台项目内,不安装、不引用此 SDK。Mac/Windows 使用同一份 JS 实现,目前不验证发行签名,没有 C++、.node、OS 模块构建或额外资源复制步骤。
npm registry 当前已发布 0.1.0;本目录的 0.1.1 设备标识修复发布前,可执行 npm install ../wanxiang-login-sdk(替换为实际相对路径)做本地联调。发布后使用 npm install [email protected]。源码目录名和 npm 包名是独立的;改包名不改变平台本地协议 wanxiang-login 或应用的 clientId / scheme。
SDK 与应用各自负责什么
SDK 编排登录:探测已运行的平台 → 创建授权请求 → 原生确认或系统浏览器 → PKCE/回环校验 → 换票 → 返回结果;另提供取消、主动刷新和撤销方法。
SDK 不依赖 Vue、React、Pinia 或其他状态库,不保存登录结果,不创建业务用户,不调用三方自己的业务后端,不自动安排刷新。 应用自己决定如何使用/保存返回的 session、何时刷新以及如何建立自己的业务会话。万象开工使用 Pinia 是它自己的选择。
请求执行中必须临时持有 state/verifier/授权码等材料;执行完清理引用。成功后仅保留 openId/token 的不可逆摘要和本地代次,用于拦截串号刷新与过期异步结果,不保留完整 session、原始 token 或用户资料。clearSession() 只清这些校验信息、取消旧请求;不会替应用清自己的状态。
另有两个不含登录凭据的本地数据:SDK 在应用 userData 写随机 oauth-install-id,用于设备登记;平台运行时在当前用户目录发布短暂端口/握手记录。它们都不是接口登录结果,也不是 token 存储。SDK 不使用 Keychain、DPAPI、localStorage 或 Cookie 存 token。设备 ID 在标识文件确实不存在时才创建;文件损坏或暂时无法读取会明确失败,不会静默换一个 ID。并发首次启动也复用同一份标识。
设备登记默认上报经过清洗、截断且移除 .local 后缀的系统主机名,以及可读的操作系统名称和版本(例如 macOS 15.6.1);不读取硬盘序列号、网卡地址等硬件标识。接入方如有隐私或命名要求,可继续通过 getDeviceFields 覆盖这些展示字段。
Electron 接入:初始化一次,页面直接调用
创建主窗口后、首次 loadURL/loadFile 之前初始化:
import { initWanxiangLogin } from 'wanxiangbaiying/electron/main'
initWanxiangLogin({
platformOrigin: 'https://platform.example.com',
appId: 'your-app',
clientIds: { MAC: 'your_app_mac', WINDOWS: 'your_app_windows' },
})页面按钮调用:
import { wanxiangLogin } from 'wanxiangbaiying/renderer'
const result = await wanxiangLogin.signIn()
if (result.kind === 'success') {
// 应用自己处理 result.session:更新状态、同步业务后端等。
}没有第三段 preload 接入,也不再手写 attach/detach/closed/before-quit 清理。 SDK 自动注册自己的 sandbox 预加载脚本、绑定主窗口首次由应用发起的页面加载,并在关闭窗口/退出时清理。页面重载或跳到其他地址不会获得额外权限,其他窗口和 iframe 不获得登录 bridge。
window 可省略:初始化时只有一个尚未加载页面的应用窗口,就自动绑定它;存在多个窗口时传 window: mainWindow 明确指定,尚未创建窗口时会提示先创建。绑定后不跟随窗口焦点或后续新窗口改变,不需要额外填写 documentUrl。初始化后仍按应用原有逻辑调用 loadURL/loadFile。SDK 的 JS 文件随 npm 依赖正常打包,无需修改应用 preload、复制资源或安装构建插件;不要从最终应用中排除整个 SDK 依赖。
openBrowser 可省略,默认由 SDK 调用 Electron shell.openExternal(url) 打开系统默认浏览器。需要自定义时传 openBrowser: url => applicationOpenBrowser(url) 覆盖;支持返回 void 或 Promise。SDK 仍校验授权 URL 并等待回环。SDK 不决定登录后如何切页、聚焦或保存结果;需要登录后聚焦时可提供 focusApplication 回调。
本地开发可用目录 file: 安装,正式构建应固定 npm 版本。自动初始化已在 Electron43 验证,使用其 registerPreloadScript 能力;不支持该能力的旧版本会明确报错,不退回要求接入方手写桥接。开发 HTTP origin 需加 allowInsecureDevelopmentOrigin:true。
已自动处理与仍需配置的内容
| 项目 | 当前行为 |
| --- | --- |
| runtime | 不再提供此配置;页面依据宿主桥接能力自动选择 |
| Mac/Windows | Electron 主进程用真实 process.platform 选择对应 clientId |
| platformOrigin / appId / clientIds | 必填;来自平台登记,不能根据 OS、应用名称或 appId 猜 clientId |
| callbackPaths | 可省,默认 MAC /oauth/mac/callback、WINDOWS /oauth/windows/callback;平台必须已登记对应回环模板,若登记路径不同再覆盖 |
| scopes | 可省,默认仅 ['profile:read'];需 AI、任务、刷新等能力时显式申请对应 scope |
| requiredScopes | 可选;应用运行确实不可缺少的 scope,用于校验返回结果 |
| appScheme | 普通登录可省;平台市场唤起/浏览器页“打开应用”按钮需要填写并由应用注册自己的协议 |
| window | 可省,自动绑定初始化时唯一的应用窗口;多窗口时必须指定,不自动选择焦点窗口 |
| openBrowser | 可省,默认 Electron shell.openExternal;传入函数可覆盖 |
| productName / focusApplication | 名称默认取 Electron app;聚焦行为由应用按需提供,不自动决定 |
| 探测/设备/端口/PKCE/state | SDK 自动完成,无需填写平台安装路径、端口、deviceId、签名或通信代码 |
只发行一个系统的应用只填对应 clientIds 项即可。双端发行使用同一份配置,无需每端改代码。默认回调只是现有后端可接受的登记约定,不会替你修改登记;SDK 不能匿名查询 /app/market/{appId} 来补配置。
appId 标识整个应用,clientIds.MAC / clientIds.WINDOWS 是平台为该应用分别登记的 Mac / Windows 公开接入端编号,用来选择该端的回调白名单、权限和原生登录能力。它们不是用户 ID、密钥或唤起协议名;示例里的编号不能自行拼接代替真实登记值。开发者填写一次,SDK 按实际系统自动选择,用户不需要填写。
需要自动续期时显式加 scopes:['profile:read','offline_access']。授权结果里的 refreshToken 可能为空;只有获得刷新票时才能刷新。
页面 API 与数据所有权
const login = wanxiangLogin
const availability = await login.getAvailability() // ready 才适合显示快捷登录
const stop = login.onProgress(event => { /* 更新等待界面 */ })
await login.signIn() // 默认 auto,用户触发
await login.signIn({ mode: 'browser' }) // 用户明确选择浏览器
await login.cancel() // 取消正在进行的授权
const updated = await login.refresh(refreshToken) // 调用方提供刷新票,自己接收新 session
await login.revoke(refreshToken || accessToken) // 调用方提供需要撤销的票
stop() // 移除本订阅
// login.dispose() 会移除订阅并取消当前登录;不清业务状态、不代替退出。以上是独立调用示例,不是在一次登录里顺序执行。成功结果为 {kind:'success',channel:'native'|'browser',session};session 包含 appId/clientId/openId/accessToken/refreshToken/tokenType/scopes/expiresIn/expiresAt,可含 installId。取消和错误以带 code、可选 requestId 的 Error 拒绝 Promise。
应用负责唯一的刷新调度;用 login.refresh 返回的新结果替换自己保存的旧结果。退出先停止调度、清本地业务状态并取消旧请求,再尽力撤销此前持有的票。SDK 不清应用的 store、不自动把 token 注入业务 HTTP。业务后端必须可信自省,按 (platformId, appId, openId) 建用户,不能信页面自报的 openId。
授权与唤起
平台已运行、已登录且目标公开 client 开启 native 时,默认优先原生账号/权限确认。平台关闭、未登录、能力不支持或本地不可用时,打开平台后端 /consent/index.html?authRequestId=...。不自动启动平台;用户拒绝结束请求,不转浏览器再问一遍。
本地发现以平台 origin 隔离,通信使用 127.0.0.1 随机端口和双向 HMAC 握手。发现记录仅含端口、PID 和本地随机握手材料;CUSTOMER token 留在平台。当前不核验发行者,握手不抵御能读写当前用户目录的恶意程序;后端授权、用户确认与 PKCE/state 校验继续保留。
同产品 Mac/Windows 可共用 appScheme,clientId 分开。应用主进程接系统 open-url/second-instance,用 parseLaunchUrl(url, scheme) 校验。scheme://activate 只聚焦;scheme://launch?ticket=lt_... 交 初始化返回实例的 signInFromLaunch(ticket) 并把成功结果交给应用自己的业务登录处理。协议注册/冷启动排队属于应用生命周期,万象开工已有实现;市场 launch_ticket 不是普通反向登录请求的凭据。
普通 Web 网站可以不用 SDK
当前 Web 入口只跳转到网站自己的登录后端(BFF),不包含完整 Web OAuth 服务端流程,不做本机平台探测。普通链接已经足够:
<a href="/auth/wanxiang/start">使用万象账号登录</a>这个路径是网站需要自己实现的示例,不是 SDK 创建的接口。网站后端按已登记 WEB client 处理 state、PKCE、回调、换票和 HttpOnly 会话 Cookie,密钥留在网站后端。平台市场的 entryUrl 是网站首页,不是本机回环地址。
若多端共用按钮逻辑,可选以下轻量包装,仍不填 runtime:
import { WanxiangLogin } from 'wanxiangbaiying/web'
const login = new WanxiangLogin({ loginStartUrl: '/auth/wanxiang/start' })
// 用户点击:await login.signIn(),只进行同源导航,返回 {kind:'redirecting'}。普通网站无需 main/preload,不注册桌面 scheme;refresh/revoke 桌面 API 不用于网站,网站通过自己的后端管理会话。
非 Electron 客户端
| 应用技术 | 已实现能力 / 还需做什么 |
| --- | --- |
| Electron + 任意页面框架 | 初始化 + 页面调用,bridge/窗口绑定/清理由 SDK 内置,不要求 Vue/Pinia |
| 其他具备 Node 的桌面宿主 | 可复用 wanxiangbaiying/core、/protocol,宿主补系统浏览器、生命周期与界面桥接;当前未提供这些宿主的一键适配器 |
| Tauri、Qt WebEngine、WebView2 等 WebView 应用 | 可复用页面 JS API,宿主需实现 LoginBridge,以相同全局名暴露或传 new WanxiangLogin({bridge});当前未交付 Rust/C++/C# 原生适配器 |
| 纯 Swift/AppKit、C#/WPF、Qt 原生 UI | 不能直接执行依赖 Node 的 JS 核心;需对应语言 SDK/薄适配层,复用现有后端 OAuth 和本地协议 |
| 普通 Web 网站 | 直接用网站登录链接;此 JS 包是可选按钮包装 |
统一的是授权契约,不是让任何语言都能直接导入 .js。新增框架时由 SDK 维护方补相应适配器即可,不要求平台客户端重新实现授权,也不要求平台后端增加同义接口。当前支持的桌面操作系统为 Mac/Windows,移动端与 Linux 未交付。
维护者验证
npm run check
npm test
npm run test:platform
npm run test:native
npm run test:native:bundle
npm run test:auto-init
npm run test:asarnpm test 可独立跑 SDK 测试。test:platform 是跨项目协议测试,默认读取相邻 ../FangMao/fe-fangmao-platform,可用 WANXIANG_PLATFORM_PROJECT 指定平台源码路径;这只用于维护者测试,不是三方安装或运行依赖。Electron 烟测使用相邻万象开工的 Electron/Vite。
SDK 34 项测试通过,包含稳定设备标识、并发首次启动、损坏标识保护、自动选择唯一窗口、多窗口显式绑定、默认系统浏览器与自定义覆盖。此次参数简化还通过万象开工类型检查、23 项授权测试和真实 Mac Electron 打包主进程烟测(含窗口隔离、原生授权、PKCE/回环;云端为夹具)。此前跨项目互通 9 项、应用自有 preload 共存和 app.asar 内加载测试已通过。平台本地模块无需 SDK 就能加载运行。Windows 真机、真实短信/平台服务与安装包唤起仍需验收;没有为这些测试发布安装包。
