feat: bind room tickets to member sessions and disconnect revoked sessions

This commit is contained in:
DiheChen committed 2026-11-08 22:41:00 +08:00
1 parent 54efdb6dd2
commit e69dc18581
43 files changed
+944 -140

No files matched your search

+20
View File
@@ -11,6 +11,14 @@ type Handler = (token: string | null) => Response | Promise<Response>
class FakeStorage {
readonly #items = new Map<string, string>()
get length(): number {
return this.#items.size
}
key(index: number): string | null {
return [...this.#items.keys()][index] ?? null
}
getItem(key: string): string | null {
return this.#items.get(key) ?? null
}
@@ -172,6 +180,18 @@ describe('token 失效时更换身份', () => {
expect(events).toEqual([{ kind: 'rejected' }, { kind: 'renewed', member: issued }])
})
it('票据属于被拒绝的身份,一并删除', async () => {
local.setItem(memberTokenKey, 'expired')
local.setItem('alisten.ticket.r1', 't1')
local.setItem('alisten.ticket.r2', 't2')
routes.set('PATCH /api/me', unauthorized)
const { api, IdentityRenewedError } = await load()
await expect(api.updateMe({ name: 'alice' })).rejects.toBeInstanceOf(IdentityRenewedError)
expect(local.getItem('alisten.ticket.r1')).toBeNull()
expect(local.getItem('alisten.ticket.r2')).toBeNull()
})
it('同时被拒绝的请求只更换一次身份', async () => {
local.setItem(memberTokenKey, 'expired')
routes.set('PATCH /api/me', unauthorized)
+5 -3
View File
@@ -1,7 +1,7 @@
import type { Auth, CreateTicketRequest, CreateTicketResponse, ErrorResponse, HttpRequest } from './http.gen'
import type { ErrorCode, Member } from '@/core/realtime'
import { Emitter } from '@/shared/lib/emitter'
import { deleteMemberToken, loadAdminToken, loadMemberToken, saveMemberToken } from './credentials'
import { deleteAllTickets, deleteMemberToken, loadAdminToken, loadMemberToken, saveMemberToken } from './credentials'
import { httpClient } from './http.gen'
export class ApiError extends Error {
@@ -159,9 +159,11 @@ function renewIdentity(rejected: string): Promise<void> {
// 更换过程中用 unrenewed 读取成员:新 token 也被拒绝时不再更换,避免循环。
async function replaceIdentity(rejected: string): Promise<void> {
// 其他标签页可能已经更换了身份,只删除被拒绝的那个 token。
if (loadMemberToken() === rejected)
// 其他标签页可能已经更换了身份,只删除被拒绝的那个 token。票据属于被拒绝的身份,一并删除。
if (loadMemberToken() === rejected) {
deleteMemberToken()
deleteAllTickets()
}
identityEvents.emit({ kind: 'rejected' })
let member: Member
try {