mcpField
D
descope-byos-builder

descope-byos-builder

github.com/descope开发1842 次安装1 天前更新
代码仓库

关于 descope-byos-builder

descope-byos-builder 帮助基于 Descope 导出的 flow JSON 构建 React BYOS 界面,同时保留 Descope 的流程引擎。它指导 Agent 解析屏幕与子流程、生成界面并核验认证流程,重点说明常见错误及其排查方法。

主要功能

  1. 01解析导出的 flow JSON
  2. 02映射屏幕与交互
  3. 03生成 React BYOS 界面
  4. 04追踪子流程及屏幕冲突
  5. 05核验认证流程与常见故障

适用场景

  1. 01为 Descope flow 构建自定义界面
  2. 02流程变更后更新现有 BYOS
  3. 03排查认证状态或屏幕处理异常
  4. 04检查自定义界面与托管屏幕的一致性

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/descope/skills --skill descope-byos-builder

安装后在 Claude Code 中输入/descope-byos-builder确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

parse-flow.mjs
references/
byos-component-patterns.md
gotchas.md

详细介绍

Descope BYOS 构建器

将 Descope 流程 JSON 导出文件转换为可运行的 React BYOS 界面,并调用 state.next(interactionId, form)。以下故障记录自真实 BYOS 会话——每次首次遇到都要花 15–60 分钟才能解决。

适用场景

  • 在保留 Descope 流程引擎的同时,为 Descope 流程构建自定义 UI(不解析客户端 JWT,完整保留流程逻辑)
  • Descope 控制台中的底层流程发生变化后,修改现有 BYOS 实现
  • 调试“流程已完成但会话状态不对”/“按钮不起作用”/“会话未认证”/“通行密钥仪式中止”等问题
  • 审查现有 BYOS 是否与托管屏幕保持一致
  • 添加认证后的升级子流程(例如 add-passkeys),在 logged-in 后运行

**不要用于:**完全可以通过托管的 <Descope flowId=... /> 组件运行的流程(BYOS 是一种取舍——流程修改不会在不改代码的情况下自动生效)。

铁律

**BYOS 的每个组件都必须以导出的流程 JSON 为依据。**不要猜测交互 ID、输出键名或屏幕名称。故障目录中的每个问题,起因都是有人编造了一个看似合理的值。

所需输入(先询问用户)

此技能无法从 Descope 控制台获取流程——Agent 无法访问控制台。开始任何工作前,先请用户提供:

  1. 流程 JSON 文件——从 Descope 控制台导出主流程以及它调用的所有子流程(LoadSubflow 的 arguments.flowId),包括认证后的升级子流程(例如 add-passkeys)。可提供文件路径或粘贴 JSON。
  2. 项目 ID + 基础 URL(如果尚未配置)——AuthProvider / SDK 初始化所需。
  3. React 应用中的挂载点——应渲染 BYOS 入口的文件/组件。
  4. 现有 BYOS 代码(如需修改)——当前屏幕组件和屏幕映射的路径。

如果用户只提供了主流程 JSON,请先停止并索要子流程,再生成组件——缺少子流程 JSON 是导致 [byos] no handler for screen "..." 运行时错误的首要原因。

工作流

  1. 从用户处收集流程 JSON。 Agent 无法访问 Descope 控制台,用户必须导出并粘贴 JSON,或提供其路径。需要提供主流程以及它调用的每个子流程(arguments.flowId 中指定的 LoadSubflow 操作)。这包括在 logged-in 操作之后运行的登录后升权子流程(例如 add-passkeys)——用户此时已经完成身份验证,很容易漏掉。如果用户只提供了主流程,请扫描其中的 LoadSubflow 操作,并在继续之前要求用户导出每个子流程。缺少子流程 JSON → 未发现相应界面 → 运行时出现 [byos] no handler for screen …。

  2. 使用 parse-flow.mjs 解析(位于此目录中)。运行 node parse-flow.mjs <path/to/flow.json>,它会打印每个界面任务的 screenName、allInputKeys、contextKeys、下一步规则(interactionId → taskId)、UI 节点摘要(输入框的 name 属性、按钮标签)以及子流程调用。

  3. 构建界面名称映射。 在所有流程中,每个唯一界面名称对应一个 React 组件。注意名称冲突——多个任务经常共用同一个名称(例如,"Welcome Screen" 同时用于邮箱输入和密码输入)。如果存在冲突,请编写一个路由器组件,根据 state.context.form.* 启发式判断并分发(参见“注意事项”→“界面名称冲突”)。

  4. 编写每个组件。 每个 BYOS 界面都执行以下四项操作:

    • 读取上下文以显示内容:state.context.sentTo.maskedEmail、state.context.form.email 等
    • 将输入收集到 form 中(通过 setForm({ ...form, key: value }))
    • 点击按钮时触发 state.next(interactionId, payload)
    • Descope 报告错误时渲染 state.error?.text
  5. 接入流程。 Descope React SDK 通过 @descope/react-sdk 提供 <Descope flowId="..." onScreenUpdate={handler} />,作为 BYOS 入口点;onScreenUpdate 接收 (screenName, state, next),你可以据此分发到匹配的界面组件。一种常见做法是构建轻量级的 FlowOrByos 包装组件,接收 byosScreens 映射并在内部进行分发;但 FlowOrByos 不是 SDK 导出项,需要自行构建。在入口点挂载一个实例。在 onSuccess 时,清除所有身份验证状态缓存(参见“注意事项”→“会话粘滞”)。

  6. 端到端验证。 走完流程支持的每种用户操作路径。任何在控制台中出现 [byos] no handler for screen "..." 的界面,都说明映射中缺少对应处理程序。

关键规则

注意: 以下若干规则(嵌套 <form> 标签的行为、WebAuthn 仪式的归属、ctxKey 预填、componentsConditions 字段名、E.164 格式号码被静默拒绝)是根据真实 BYOS 会话的经验得出的,并未在 Descope 的官方文档中明确说明,但已在生产环境中验证,并与流程 JSON 结构核对过。

  • 表单键与输入节点的 name 属性匹配,而不是 allInputKeys 或 inputsMetadata.key。Task 40 的 Set Password 输入项中,name="newPassword",但 allInputKeys: ["newPassword_noPolicyOverrides"]。提交时使用 { newPassword: "..." }。
  • 在 state.next 载荷中只发送当前屏幕的输出。将累积的完整表单数据展开到子流程边界之外,可能会污染上下文并导致静默失败。
  • 切勿嵌套 <form> 标签。 Descope 的 Web 组件会将子元素包裹在 <form> 中。请使用 <div>、onClick 和 onKeyDown={makeEnterHandler(submit)}。
  • OAuth 按钮必须在载荷中设置 provider({ provider: 'google' }),不能只设置交互 ID。
  • 电话号码必须采用 E.164 格式。 非 E.164 格式的号码会被 SMS 连接器静默拒绝——流程会完成,但不会创建会话。
  • 在 onSuccess 时使身份验证缓存失效。 会话升级(匿名 → 已验证)可能会复用同一个 sub,因此以 userId 为键的缓存不会自动刷新。
  • 从屏幕 JSON 中读取 componentsConditions,以复现显示/隐藏规则(例如,当 unauthUser.verifiedPhone 为 false 时,隐藏“使用代码登录”)。
  • WebAuthn 流程:由 SDK 执行仪式。 当某个交互路由到 WebAuthn 操作任务(webauthn-update-user-start/finish、Sign Up or In / Passkeys)时,只需调用 state.next(interactionId)。切勿在 BYOS 中调用 navigator.credentials.create/get。切勿导入 @simplewebauthn/browser、startAuthentication、startRegistration 或任何 WebAuthn 辅助库——Descope SDK 已经会执行仪式的两个阶段。SDK 会在 onScreenUpdate 时观察该操作并自行执行仪式;取消或错误会通过 state.error.text 呈现。
  • 读取输入项的 ctxKey 以进行预填。 当输入节点具有 props.ctxKey="someKey" 时,首次渲染时通过以上下文值为依赖项的 useEffect,从 state.context[someKey] 初始化 form[name](仅在本地字段为空时设置)。
  • 复现 Device Not Supported 分支。 WebAuthn 流程通常会根据 deviceInfo.webAuthnSupport 进行分支。不支持的分支是一个真实的屏幕任务——请为其提供 BYOS 组件,否则托管小部件会在推广过程中间渲染。

共享屏幕名称的启发式判断

当两个任务共用一个屏幕名称时,不能凭猜测来选择区分信号。必须追踪每条路径,并构建一张“表单字段累积”表。

流程(必须遵循——跳过会导致不易察觉的错误):

  1. 对于每个使用冲突屏幕名称的任务,沿着 next.rules 反向追踪,直到父级流程的入口点。
  2. 对于每条路径,列出沿途的每个屏幕任务及其 allInputKeys——这些就是用户在该路径上填写的表单字段。
  3. 找出一个仅在一条路径上填充的表单字段。
  4. 使用 Boolean(state?.context?.form?.<that field>) 作为启发式判断条件。

常见错误:选择了在两条路径上都会填充的字段,因为没有完整追踪两条路径。

以下示例来自一组特定的流程——你的任务 ID、屏幕名称和子流程名称会有所不同。这些示例展示的是方法;请始终在自己的流程上运行 parse-flow.mjs。

示例:对于 sign-in-sms-otp 和 progressive-profile-sms 共用的“Verify OTP”,form.phone 看起来很合适,但这两个流程在到达 Verify OTP 之前,都会先通过 Phone-input 屏幕收集电话号码。应改用 form.password:sign-in-sms-otp 路径从父级流程的密码屏幕进入,该屏幕会写入 form.password;而 progressive-profile-sms 路径是在 magic link 之后进入的,用户从未输入过密码。

参考启发式判断(示例来自本技能所依据的流程集,并非通用规则):

冲突路径 A 在此屏幕前收集的字段路径 B 在此屏幕前收集的字段信号
“Welcome Screen”(email-entry 与 password-entry)—emailstate.context.form.email
“Verify OTP”(sign-in-sms-otp 与 progressive-profile-sms)email、password、phoneemail、phonestate.context.form.password(不要用 phone——两条路径都会收集该字段!)

三路冲突示例(主流程和两个子流程变体中的 Magic Link Sent):

路径此屏幕前收集的字段
主流程email无密码
子流程 new-useremail、password、fullName有密码和 fullName
子流程 existing-useremail、password有密码,没有 fullName

判断条件:supportsChooseOther = !hasPassword || hasFullName。

在路由器组件顶部记录所选的启发式判断,并附上完整追踪过程。如果能在 Descope 控制台中重命名其中一个屏幕,请提出申请——唯一的名称永远胜过启发式判断。

**成功案例:**两个 User Information 任务(verify-email-magic-link 的 existing-user 与 new-user 分支)在控制台中被重命名为 User Information - Unverified - Email Only 和 User Information - Unverified - Email and Name。虽然可以使用启发式判断(检查是否存在 fullName 输出),但重命名更省事、更清晰,也能经受流程编辑的变更。默认优先考虑重命名。

常见问题参考

完整故障目录(问题 → 症状 → 修复方法):参见 references/gotchas.md。在将静默失败归咎于 BYOS 代码之前,先查阅该文件——大多数“无法运行”的症状都对应已知问题。

验证

对流程支持的每条用户旅程进行端到端测试:

  • 流程 JSON 是否显示主流程从 task N 开始,并以 action=logged-in 的 task M 结束?
  • 流程中的每个屏幕任务是否都有 BYOS 组件,或已明确决定“回退到托管流程”?
  • onSuccess 是否触发,且应用是否到达预期的已认证状态(没有卡在匿名状态)?
  • onError 是否触发,并显示 Descope 错误消息(没有静默失败)?
  • 是否已端到端走完认证后提升子流程(passkey 提升等),包括 Device Not Supported 分支?
  • WebAuthn 屏幕是否只调用 state.next(interaction)——BYOS 代码中没有调用 navigator.credentials.*?

完整走完一条用户旅程期间,控制台应当没有 [byos] no handler for screen "..." 警告。

解析器

parse-flow.mjs(本目录)——Node 脚本。输入:一个或多个 flow JSON 路径。输出:每个 flow 一张摘要表,显示屏幕任务、输入、退出交互、UI 节点名称和子流程加载器。运行脚本,将输出粘贴到屏幕映射注释头部,即可获得 BYOS 组件所需所有常量的真实来源。

警示

如果你发现自己在:

  • 硬编码一个未在 flow JSON 中看到的交互 ID——停下,先解析
  • 在子流程边界处将 { ...form } 展开到 state.next 中——只发送输出
  • 未检查每个 flow 的规则,就给每个“Verify OTP”变体添加相同按钮——根据上下文设置门控
  • 在 BYOS 组件中编写 <form> 标签——使用 <div>
  • 在 onSuccess 中调用 navigate(),却未使身份验证缓存失效——先调用 invalidate
  • 在 BYOS 点击处理程序中调用 navigator.credentials.create/get——SDK 会处理;只需触发交互
  • 导入 @simplewebauthn/browser 或任何 WebAuthn 辅助工具——同一个陷阱,只是换成了第三方库;SDK 会处理
  • 因为“用户到那时已经登录”而跳过子流程导出——登录后的升级子流程会渲染真实屏幕

这些情况都意味着:暂停,重新阅读 references/gotchas.md,并根据 flow JSON 进行核实。

参考资料

  • references/byos-component-patterns.md——正向代码模式:核心连接方式(onScreenUpdate → ByosState)、屏幕路由器、屏幕骨架,以及电子邮件、OTP、电话、OAuth、冲突路由器和 ctxKey 预填的完整示例。开始搭建时从这里入手。
  • references/gotchas.md——故障目录:19 种真实的 BYOS 故障模式,每种都包含症状 → 根本原因 → 修复方法。还包含发版前检查清单。
  • parse-flow.mjs——Node 解析器:node parse-flow.mjs <flow.json> [more.json ...]。输出屏幕任务、真实表单键的 name 属性、交互 ID、子流程加载器和冲突警告。编写任何组件前都要运行。

评论

支持 Markdown