feat(web)!: sign in before joining a space and drop the stranger option
Opening a space link while signed out shows the sign-in page first and keeps the link in memory. The channel settings no longer offer the stranger switch, and the client supports API contract major 8.
This commit is contained in:
1 parent
c858bd2a09
commit
8233369cd4
16 files changed
+27
-30
No files matched your search
@@ -134,7 +134,7 @@ const { complete, nextIndex, meta } = await result;
|
||||
|
||||
## 必须注意的几点
|
||||
|
||||
**1. 访客终端密钥必须持久化。** `session.endpointSecret`(32 字节)标识「这个访客终端在这个频道里」,服务端按它登记访客以便认证和移除;登录账号以账号会话鉴权,不需要它,同一账号的多台设备也不重复占名额。访客不持久化的话,每次打开链接都会登记一个新访客终端。用 IndexedDB 保存,下次 `open(link, { endpointSecret })` 传回来。
|
||||
**1. 终端密钥只用于便捷分享。** `session.endpointSecret`(32 字节)标识「这个终端」,便捷分享的创建者与读者靠它登记和续传;频道与空间只凭账号会话鉴权,不使用它。分享读者不持久化的话,每次打开链接都会登记一个新访客终端,用 IndexedDB 保存。
|
||||
|
||||
**2. Worker。** `ChannelSession` 实例不能跨线程传递(它持有 `CryptoKey` 和闭包)。在 Worker 里用链接和 `endpointSecret` 重建它,主线程只传链接、帧字节和事件。
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ export type AuthRole = 'read' | 'write' | 'owner';
|
||||
/** 频道设置,字段名与 API 一致(snake_case)。 */
|
||||
export interface ChannelSettings {
|
||||
join_policy?: 'open' | 'invite';
|
||||
allow_guests?: boolean;
|
||||
name?: string;
|
||||
max_members?: number;
|
||||
members_can_upload?: boolean;
|
||||
|
||||
@@ -45,7 +45,8 @@ let dataDir = '';
|
||||
let base = '';
|
||||
let accountToken = '';
|
||||
function headersFor(session: ChannelSession, role: AuthRole): Record<string, string> {
|
||||
return { ...session.authHeaders(role), ...(session.role === 'owner' ? { 'X-WD-Account': accountToken } : {}) };
|
||||
// 频道只接受登录账号:测试里所有角色共用同一个账号,权限差异由角色令牌体现
|
||||
return { ...session.authHeaders(role), 'X-WD-Account': accountToken };
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -145,7 +146,7 @@ function subscribe(session: ChannelSession, role: AuthRole, until: (events: Item
|
||||
const h = headersFor(session, role);
|
||||
const protocols = ['wd.v1', `wd.role.${role}`, `wd.ep.${h['X-WD-Endpoint']!}`];
|
||||
if (h.Authorization) protocols.push(`wd.auth.${h.Authorization.replace('Bearer ', '')}`);
|
||||
if (session.role === 'owner') protocols.push(`wd.account.${accountToken.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')}`);
|
||||
protocols.push(`wd.account.${accountToken.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')}`);
|
||||
|
||||
const ws = new WebSocket(`${base.replace('http', 'ws')}/api/v1/channels/${session.id}/ws?from=${from}`, protocols);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
@@ -416,7 +417,7 @@ suite('端到端(真实服务端)', () => {
|
||||
const member = await ChannelSession.open(owner.link(ORIGIN));
|
||||
await joinChannel(member, 'read');
|
||||
|
||||
const res = await fetch(api(`/channels/${owner.id}/guests`), { headers: { ...headersFor(member, 'read') } });
|
||||
const res = await fetch(api(`/channels/${owner.id}/settings`), { method: 'PATCH', headers: { ...headersFor(member, 'read'), 'Content-Type': 'application/json' }, body: '{}' });
|
||||
expect(res.status).toBe(403);
|
||||
expect(() => headersFor(member, 'owner')).toThrow();
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user