一个 Widget 接入个人、客服和管理员
统一权限与布局 · Widget 2.3.7 / Loader 1.4.4 · 2026-09-21
商户 → 分组 → 用户 → 个人咨询与订单客服会话。商户管理员仅管理本商户,客服和代理仅访问后台授权分组,用户仅访问本人获授权会话。用户分组是统一依据,旧订单分组不再覆盖它;改组使用 customer-groups,清空组不会变成公共组。Open4X 平台管理员不默认获得聊天权限。
新 SDK 支持 role: 'agent' 作为分组代理身份,权限等同受限客服而非管理员。客户统一使用 createOpen4XUnifiedSessionHandler,不在前端区分 Token 类型。旧单会话 Token 仍保持原范围;必须更新商户 Session 接口并取得新凭证,不能修改 JWT 字段升级。
服务端 context 创建独立个人/订单会话并建立 Widget 绑定,订单入口默认打开当前订单。已获授权但从未打开的普通客服会话也能列出;首次打开有唯一客服时自动绑定,没有或多个接收人时由商户明确配置。已撤销绑定、作者私聊及争议不会自动恢复或扩大权限。需要 Open4X 迁移 0056;商户业务数据库无需为本次布局变化新增表。
默认订单与列表显隐
同一个个人 Session 可以显示列表,也可以只展示已选订单;这只是展示方式,不缩小或扩大凭证权限:
Open4XWidget.init(session_token, {
showConversationList: true,
conversationId: 'conv_authorized_order'
});
Open4XWidget.configure({ showConversationList: false });
Open4XWidget.configure({ conversationId: 'conv_another_authorized_order' });脚本属性为 data-show-conversation-list 和 data-conversation-id;自定义元素为 show-conversation-list 和 conversation-id;React 使用同名 camelCase 属性。默认显示列表,不指定 conversationId 时使用 Session 的默认会话;都不指定则显示列表,隐藏列表且无默认会话时打开最新一条。空列表显示空状态。未经授权的默认会话返回错误,不回退到别人的聊天。
原生 API 可在 POST /v1/conversations/customer-session 中提供 conversation_id。它经过本人权限校验,仅作为默认打开提示。订单 ID 是商户业务编号,conversationId 是 Open4X 会话 ID,不可混用。若订单页必须严格只能访问一条会话,继续使用原单会话凭证,而不是靠隐藏列表限制权限。
Widget 界面 2.3.6 / Loader 1.4.3 · 2026-09-21
客服和商户管理员打开会话后,默认选中最近发送公开消息的可用接收人,排除自己、已移除的参与者、系统消息和内部备注。可选对象仍为当前会话的活动用户或作者;无历史消息但只有一个可选对象时直接选中。多人且无法确定最近发件人时仍需明确选择,不会默认群发。历史记录有后续分页时,不把当前页末尾误认为整个会话的最新消息。
自动选择后,接收人区域缓慢闪烁三次;启用系统“减少动态效果”时改为静态高亮。尚未输入或附加文件、且没有手动选择时,默认对象跟随最近发件人。新消息不覆盖手动选择、已有回复草稿或转述的目标。最新发件人变化后,会显示最新发件人及实际接收人,点击发送后需确认;确认期间对象再次变化则重新确认。取消确认不会发送。内部备注不外发,个人端仍不显示接收人选择。
无需修改商户数据库、API Key 或 Session 协议。刷新页面后加载新版界面;Loader 未改动,控制台日志仍显示 1.4.3,界面版本可通过 https://widget.open4x.com/version.json 查看。下面的版本记录保留各次发布时的行为。
控制台 Inbox 和 SocialOps 的消息时间统一解析 API 时间:D1 不带时区的时间戳按 UTC 读取,再按浏览器本地时区显示;已有 Z 或偏移量的时间戳保留其含义,秒级和毫秒级 Unix 时间均受支持。修复 UTC+8 下消息时间差八小时的问题,不修改数据库原始时间,不对时间固定加八小时。
正在排查接入?复制 个人端检查提示词、客服端检查提示词或商户管理员检查提示词。先核对身份与凭证合同,再定位 Session、路由或界面问题。
Loader 1.4.3 / Widget 界面 2.3.4 · 2026-09-18
修复本地预览或内嵌页面使用 srcdoc 且隐藏 Referrer 时,客服/管理员错误提示“无法验证宿主页面”的问题。Loader 使用浏览器实际的安全来源,而不是 about:srcdoc 的 URL 来源;个人聊天的主题和按钮控制也恢复正常。
可信宿主中的 srcdoc 保留 HTTP(S) 来源时受支持;真正不保留来源的 sandbox 仍拒绝客服授权,不放宽来源校验,不使用 * 传递凭证。不要为不可信内容随意取消 sandbox 限制。自定义 iframe 接入需传实际来源,不能传字符串 null。
使用托管 Loader 的网站请强制刷新一次;若仍加载旧版,可将脚本地址更新为 https://widget.open4x.com/widget.js?v=1.4.3。自托管需更新 Loader。不需要修改业务数据库、API Key 或 Session 协议。
Widget 界面 2.3.4 / Loader 1.4.2 · 2026-09-16
修复父页面来源为字符串 "null" 时 postMessage 抛错、导致初始化中断的问题。所有授权和界面控制消息均校验来源及发送窗口,不使用 * 传递凭证。旧 workbench.html 兼容入口也提供渲染失败提示和重新加载按钮,仍显示同一个 Widget。
隐藏 Referrer 的网站可继续使用:Loader 为个人、客服和管理员统一传入有效的 parent_origin,SDK 同样自动提供。自定义 iframe 宿主需传真实的 HTTP(S) 页面来源;file:、data: 和不保留来源的 sandbox 不能用于客服授权,会提示无法验证宿主页面。不要把 null 改成 *。
本版本包含 2.3.3 的紧凑会话列表、用户名与订单信息、未读角标和切换会话加载状态。控制台签发的管理员消息显示“管理员”,审计仍保留真实操作身份。刷新控制台即可加载托管 Widget 更新;自托管需同步更新 Loader 和页面资源。不需要更换 Key、Session 协议或数据库。
Widget 界面 2.3.2 · 2026-09-16
控制台与嵌入式 Widget 共用以下规则:唯一接收人自动选中;必须手动选择时,接收人列表直接展开并标记“必选”,不再隐藏在折叠菜单中。选择后可收起,再次清空会重新展开。没有可用接收人时明确提示,不能发送公开回复。
内部备注统一显示“内部备注(不对外发送)”,无需选择接收人;creator 的默认角色名称为“作者”,不再使用特定业务的“交易员”称呼。不会改变会话权限或默认群发。刷新控制台即可使用;Loader 保持 1.4.1,运行界面更新为 2.3.2。
Widget 1.4.1 修复 · 2026-09-16
客服或管理员回复时,只有一名可选接收人(活动用户或作者)会自动选中;多名接收人和转述仍需明确选择,不会默认群发。普通用户不显示接收人选择,接收范围由商户服务端签发的 Session 决定。内部备注始终与公开回复隔离。
修复浏览器禁用本地存储、隐藏顶部按钮时恢复最小化状态,以及历史消息正文为空导致的加载或显示问题。重复初始化会重新加载相应身份的 Widget。加载超时会显示“重试”,渲染异常会显示“重新加载”,不回退到其他角色。
使用托管 https://widget.open4x.com/widget.js 的网站请强制刷新一次,以清除旧入口脚本缓存;自托管需同步升级 Loader 和 Widget 页面资源至 1.4.1,不需要换 Key、Session 协议或数据库。若仍只看到外层标题栏,请提供页面地址和不含凭证的错误信息;不要发送 Token 或完整的 iframe URL。
Widget 1.4.0 界面统一(已发布 · 2026-09-16)
本次更新取消按角色跳转独立 Conversation Hub 工作台。个人、客服和管理员都使用同一个 Widget 入口,共用聊天气泡、输入框和会话列表布局。旧 workbench.html 地址仅作为兼容入口,显示同一个 Widget,不再维护独立工作台界面。
管理员操作位于聊天右上角“会话操作”菜单;未加入时可以先查看,再点击“加入会话”。接收人、转述和内部备注按需展开。主题、语言、时区和按钮显隐沿用 Open4XWidget.configure()。网页仍只需接入 widget.js,不需要额外安装 Conversation Hub 或 Workbench UI。
服务端接口、wbs_ 凭证和现有权限范围保持兼容,不需要修改数据库或重新创建 Key。已经接入管理员 SDK 2026-09-16-admin.1 的商户无需再次更换服务端 SDK;自托管 Loader 需升级到 1.4.0。
个人端不选择接收人
个人用户的日常咨询和每个订单会话都直接在当前聊天中发送,不展示“选择接收人”、内部备注或客服转述操作。即使服务端为会话配置了多个接收人,也不要求个人用户再次选择,更不会自动改成向所有参与者群发。
使用统一 Session 适配器时,由商户服务端根据真实登录身份返回 role: 'customer'。widget_customer_session 用于个人会话列表,打开某个会话后使用对应的 widget_session。接收人由服务端签发 Session 时确定;浏览器发送正文、附件或引用信息,不决定接收人、发送者角色或消息可见性。
如果个人页面出现客服的接收人列表,先检查商户 Session 接口是否误发了客服/管理员的 wbs_ 凭证,不要仅通过 CSS 隐藏操作,也不要为普通用户授予客服权限。URL 的显示参数不是身份授权依据。接收人缺失或失效时,应由商户服务端修复配置并重新签发会话,不把修复任务交给个人用户。
控制台会话入口
商户登录后打开 Conversations。该页面直接嵌入正式 Widget 主入口,不再使用控制台独立的 Conversation Hub 界面,也不跳转 workbench.html。页面沿用控制台主题,自动展示当前商户的会话列表。
控制台在服务端校验当前登录账户及团队的 conversation:read、conversation:write 和 conversation:supervise 后,签发 15 分钟的专用 Widget 凭证并自动续期。凭证不写入 URL 或浏览器存储,不能用于控制台、账单或 API Key 管理。无需手动创建 Key 或填写商户信息;平台运营身份不会因此获得其他商户的数据权限。
团队成员、权限或商户归属在每次 Widget 请求时重新校验。切换工作区、退出登录或授权失败会清除当前嵌入会话。查看默认不加入会话,发送前需点击“加入会话”;接收人行为见上方版本说明,转述需明确选择目标。内部备注、转述、分配和接管继续由同一 Widget 提供。
此入口仅用于 Open4X 商户控制台。第三方网站仍使用下文的服务端 Session 适配器,不应调用控制台专用签发接口。
管理员查看与介入
已发布,服务端 SDK 版本为 2026-09-16-admin.1。旧版冻结包保留不变,不会自动获得新增监督权限。
升级后的服务端适配器在可信 resolve 返回 role: 'admin' 时签发 scope: 'tenant', mode: 'agent', supervise: true。直接调用 POST /v1/conversations/workbench-sessions 也可传这三个字段。Bridge Key 必须具有 conversation:read、conversation:write、conversation:supervise;浏览器不得持有 Bridge Key,也不得自行决定角色。
- Widget 自动显示商户会话列表,覆盖当前商户的个人、订单及未分组会话,可按订单号、用户 ID、分组、未分组和状态筛选。
- 打开会话只查看,不自动创建客服参与者。点击“加入会话”后才能回复或写内部备注;已被撤销的参与者不能自行重新加入。
- 加入后可“接管会话”,完成后“释放接管”。接管使用服务端版本锁,冲突返回
409,必须刷新后重新操作;释放后仍是已加入成员。 manage: true另外开放状态和客服分配操作;它不替代supervise。当前 Widget 不提供将接管权转给另一外部账号的操作。- 管理范围始终是签发 Key 所属商户,不授予 Open4X 平台运营跨商户查看权限。
GET /v1/conversations/inbox 新增精确筛选参数 external_user_id、group_tag、ungrouped=true(后两者互斥);现有 context_id 为精确订单/业务对象筛选,q 搜索订单号或外部会话编号。筛选只能缩小授权范围。
监督状态和操作使用 GET/POST /v1/conversations/{id}/supervision,审计使用 GET /v1/conversations/{id}/audit。POST 支持 join、observe、takeover、release,后两者必须带最新 revision。只查看不会取得发送权限;加入行为按参与者持久保存。操作审计记录使用 external:<external_user_id>。
旧 Token 和旧版冻结 SDK 不会自动升级为 supervise: true。请更新服务端适配器、重新签发 Session;前端原 Loader 接入方式不变。不需要新增数据库迁移。
已发布 · 2026-09-16
Loader 自动路由已发布到正式 Widget。商户需更新本地服务端 SDK 并修改 Session 接口;自托管 Loader 也需升级。已有个人列表与客服工作台接口继续兼容,不需要数据库迁移。
服务端源码下载(两个文件放在同一目录,仅用于商户后端):Session 适配器、API 客户端、SHA-256 校验清单。这是源码交付,不是公共 npm 包;同一包提供可选浏览器适配器与 React/自定义元素源码,文件列表见清单。
商户前端保留原 Session 地址与 Open4XWidget.init(session_token) 调用。修改商户后端 Session 实现,根据已验证的登录身份返回个人列表 Token 或客服工作台 Token。不是由浏览器传一个 role=admin 获得权限,也不是修改现有 JWT 的内容。
| 后端身份 | 可见内容 |
|---|---|
customer | 自己的个人咨询和已创建的订单会话 |
support_agent / agent | group_tags 授权分组下用户的个人咨询与订单客服会话 |
admin | 当前 Open4X 商户租户内的全部会话 |
商户需要修改的代码
本地 SDK 的 createOpen4XUnifiedSessionHandler 是商户服务端适配器,不是新增的 Open4X HTTP 路由。它复用 /v1/conversations/customer-session、/workbench-sessions、创建会话和 Widget Session 接口。
import { createOpen4XUnifiedSessionHandler } from './open4x-session-handler';
export const POST = createOpen4XUnifiedSessionHandler({
client, // Open4XConversationClient;Key 仅存在于服务端 Secret
widgetOrigin: 'https://widget.open4x.com',
resolve: async request => {
// 以下函数由商户实现:校验登录、CSRF/来源、速率限制和业务权限。
const actor = await requireMerchantSession(request);
if (!actor) return null;
// 商户自己实现:验证本次入口及访问权限,不直接信任前端 role。
const entry = await resolveAuthorizedChatEntry(request, actor);
if (entry === 'admin' && actor.isMerchantAdmin) {
return { role: 'admin', external_user_id: actor.stableId, manage: true };
}
if (entry === 'support_agent' && actor.isSupportAgent) {
return {
role: 'support_agent', external_user_id: actor.stableId,
group_tags: await getAuthorizedSupportGroups(actor),
manage: false
};
}
if (entry !== 'customer') return null;
// 从商户业务上下文取得订单;有订单时必须验证归属。
const order = await resolveAuthorizedOrder(request, actor);
return {
role: 'customer', external_user_id: actor.stableId,
user_name: actor.displayName,
context: {
order_id: order?.id,
recipient_external_ids: await getAuthorizedSupportRecipients(actor, order),
group_tag: await getCustomerSupportGroup(actor)
}
};
}
});actor.stableId 必须使用稳定、带站点前缀的用户 ID,与原 Widget Session 一致。组名和收件人从商户服务端取得。widgetOrigin 是实际 iframe 的源站,不是商户网站域名;自托管时改为自托管 Widget 的 origin。
示例中的 resolveAuthorizedChatEntry 是商户需实现的鉴权辅助函数,不是 Open4X SDK 方法。它应区分已获授权的个人、客服和管理入口;同一管理员进入个人咨询页时仍返回 customer,不得以账号最高角色覆盖当前入口。
Bridge Key 需要 conversation:read、conversation:write;动态客服范围以及此适配器创建客服路由时还需要 conversation:supervise。不要把此 Key 交给浏览器。管理员模式默认也不开放管理写操作,须显式 manage: true。
个人咨询与订单会话
- 提供
context时,适配器确保一条个人咨询存在。 - 同时提供已授权的
order_id时,再确保该用户该订单的一条独立会话存在。 - 稳定 ID 由用户 ID、会话类型和订单 ID 的元组计算,并由 Open4X 按租户隔离。重复调用复用会话和 Chat,不会创建重复消息。
- 最终返回本人范围的列表凭证,并携带本次创建的默认会话;进入订单时直接打开该订单,列表仍可显示。订单之间消息记录独立。
- 省略整个
context时只签发列表 Token,不创建任何会话。 - 不会遍历商户全部历史订单。希望所有订单立即出现在列表,需要商户对每个已授权订单执行一次准备流程;否则按用户首次咨询创建。
- 已使用自定义外部会话 ID 的商户,可在
context.external_conversation_id提供现有映射,避免当前订单出现重复记录;个人咨询的旧映射需要商户迁移或沿用旧准备流程并省略context。 - 收件人必须是活动客服参与者。此适配器面向人工客服,不隐式绑定 AI、Telegram 或作者。
前端与生命周期
无需把用户角色、分组权限写入前端。新 Loader 识别 wbs_ 后自动加载客服工作台,通过受来源校验的消息传递令牌,不将客服 Token 放入 URL 或存储。 个人列表仍显示可选左侧栏,单会话 Token 仍按原方式打开单个会话。切换账号时调用 destroy() 后重新初始化,清理旧会话和计数。
使用旧版或自托管 Loader 的客户必须升级一次,不能只修改后端后仍加载旧脚本。初始挂载代码和 Session URL 可以保持不变。
客服令牌默认 900 秒,个人列表默认 1800 秒。商户沿用会话续期流程;新 Loader 在客服令牌被拒绝时发出 open4x-session-required 事件,不能保证原本没有续期机制的前端无需增加续期代码。无权限时应回退商户工单,不自动切换管理员权限。
联调验收
- 个人用户能看到个人咨询和两个订单,来回切换不串消息。
- 同一订单重复创建 Session,不增加会话数量。
- 修改浏览器请求中的角色、用户 ID、组名不能改变服务端授权结果。
- 客服只能看到授权分组;管理员不能跨商户查看。
- 从客服账号切换到个人账号后,旧工作台和未读计数清除。
- 收起客服窗口不会继续标记新消息已读。