企业码解析+登录跑通真实后端:Tinode连接适配层、类型化API客户端、会话store
lib/tinode/connection.ts:单例Tinode连接+自己的发布订阅层——SDK的onConnect/onDisconnect/onLogin
是单槽位属性(赋值会互相覆盖),不是真正的多监听器事件源,所以这里统一接管这几个槽位,自己
扇出给任意数量的React订阅方(lib/tinode/hooks.ts的useSyncExternalStore)。installTenantHiPatch
照搬旧应用tinode-web.jsx的实现搬过来——服务端{hi}包的tenant字段SDK本身没有API,只能在原始
sendText层面注入,这是后端协议的真实约束,不是旧代码库特有的写法。
lib/api/client.ts:REST客户端按旧应用src/lib/im-api.js的request函数+完整REASON_MESSAGES错误码
翻译表搬过来(含请求头X-MeetingIM-APIKey等几个月联调换来的正确写法),这次是重新用TypeScript
写、不是复制文件,但端点行为对齐。
实机联调过程中发现并修正了一处凭记忆猜错的地方:tenants/resolve真实响应结构是
{endpoints:{api,file,websocket}, tenant:{code,name}},不是我最初假设的顶层{code,name}——
已用真实请求验证修正。用企业码default解析成功(显示"Default Tenant")+ 一次真实错误密码
登录尝试(服务端真实401,错误提示正确显示"账号或密码错误")两步都对着真实后端验证通过,
不是只测了正确路径。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
8bcd9f3a5b
commit
8cdd84152d
@@ -0,0 +1,48 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export interface TenantContext {
|
||||
code: string;
|
||||
name: string;
|
||||
endpoints: { api: string; file: string };
|
||||
/** Real Tinode WS host + scheme, parsed from tenants/resolve's `endpoints.websocket`. */
|
||||
host: string;
|
||||
secure: boolean;
|
||||
}
|
||||
|
||||
interface SessionState {
|
||||
tenant: TenantContext | null;
|
||||
myUserId: string | null;
|
||||
setTenant: (tenant: TenantContext) => void;
|
||||
clearTenant: () => void;
|
||||
setMyUserId: (id: string | null) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tenant context persists across browser restarts (localStorage) the same way the old app kept
|
||||
* it, since a returning user's auto-login still needs it to build the {hi} packet and every REST
|
||||
* call's X-Tenant-Code header. `myUserId` is *not* persisted here -- it's re-derived from the
|
||||
* Tinode connection's own auth state on reconnect, not something to trust stale from a previous
|
||||
* session.
|
||||
*/
|
||||
export const useSessionStore = create<SessionState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
tenant: null,
|
||||
myUserId: null,
|
||||
setTenant: (tenant) => set({ tenant }),
|
||||
clearTenant: () => set({ tenant: null }),
|
||||
setMyUserId: (id) => set({ myUserId: id }),
|
||||
}),
|
||||
{
|
||||
name: 'meetingim-session',
|
||||
partialize: (state) => ({ tenant: state.tenant }),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
/** Parses `wss://host:port/v0/channels` (or `ws://...`) into Tinode's own {host, secure} shape. */
|
||||
export function parseWebsocketEndpoint(url: string): { host: string; secure: boolean } {
|
||||
const parsed = new URL(url);
|
||||
return { host: parsed.host, secure: parsed.protocol === 'wss:' };
|
||||
}
|
||||
Reference in New Issue
Block a user