diff --git a/web/src/core-bridge/worker/channelDataKeys.ts b/web/src/core-bridge/worker/channelDataKeys.ts index d6ff609..6bebab9 100644 --- a/web/src/core-bridge/worker/channelDataKeys.ts +++ b/web/src/core-bridge/worker/channelDataKeys.ts @@ -14,6 +14,10 @@ const states = new Map(); const domainId = (channelId: string, domain: string, epoch: number) => `${channelId}:${domain}:${epoch}`; const contextId = (spaceId: string, channelId: string, domain: string, epoch: number) => `${spaceId}:${channelId}:${domain}:${epoch}`; const loaded = new Map(); +/** 服务端登记的对象作者(账号)。收到别人的消息时据此先显示名字,不必等结构刷新 */ +export function objectAuthorAccount(resourceId: string, itemId: string): string | undefined { + return states.get(resourceId)?.objects.find(o => o.itemId === itemId)?.authorAccount ?? undefined; +} export function clearDataKeys(): void { objectKeys.clear(); states.clear(); loaded.clear(); } export function attachDataKeys(session: ChannelSession, resource: SavedResource): void { if (resource.kind !== 'channel') return; diff --git a/web/src/core-bridge/worker/channelEvents.ts b/web/src/core-bridge/worker/channelEvents.ts index 3062aa2..4afbe76 100644 --- a/web/src/core-bridge/worker/channelEvents.ts +++ b/web/src/core-bridge/worker/channelEvents.ts @@ -1,9 +1,10 @@ import { parseLogRecords, parseEnvelope, toBase64Url, type ChannelSession, type LogRecord, type ReceivedItem } from '@webdrop/core'; -import { canDecryptRecord, objectEventProjection, readDataKeys, verifyObjectDeletion, verifyObjectEvent } from './channelDataKeys'; +import { canDecryptRecord, objectAuthorAccount, objectEventProjection, readDataKeys, verifyObjectDeletion, verifyObjectEvent } from './channelDataKeys'; import { getResource, cacheResource, type SavedResource } from '../database'; import { channelCache } from './resourceMetadata'; -import { loadWorkspace } from './workspaceStructure'; -import type { FileItem, Item, Snapshot } from '../types'; +import { cachedPerson, loadWorkspace } from './workspaceStructure'; +import { avatarThumbnail } from './senderSnapshot'; +import type { FileItem, Item, SenderInfo, Snapshot } from '../types'; import { previewKindOf } from '@/lib/fileKind'; import { blobs, readers } from './sessions'; import { showRejected, publishWorkspace } from './presentation'; @@ -31,6 +32,15 @@ export function mapReceived(item: ReceivedItem): Item { text: new TextDecoder().decode(item.content) }; } +/** 别人发的消息:服务端登记了作者账号,先用它(和已加载的资料)显示名字,避免「未知成员」闪一下 */ +async function authorOf(resourceId: string, itemId: string): Promise { + const username = objectAuthorAccount(resourceId, itemId); + if (!username) return undefined; + const person = cachedPerson(resourceId, username); + const avatar = person?.avatar ? await avatarThumbnail(person.avatar) : undefined; + return { username, displayName: person?.displayName || username, ...(avatar ? { avatar } : {}) }; +} + export async function applyChannelRecord(resource: SavedResource, session: ChannelSession, input: Uint8Array | LogRecord): Promise { if (!(input instanceof Uint8Array)) { const itemId = input.kind === 'frame' ? toBase64Url(parseEnvelope(input.envelope).itemId) : input.itemId; @@ -80,7 +90,8 @@ export async function applyChannelRecord(resource: SavedResource, session: Chann } else { const blob = item.kind === 'file' && event.item.complete ? new Blob([event.item.content], { type: item.mime }) : undefined; if (blob) blobs.set(`${resource.id}:${item.id}`, blob); - publishWorkspace(resource.id, { t: 'item', item }); + const author = await authorOf(resource.id, item.id); + publishWorkspace(resource.id, { t: 'item', item: author ? { ...item, author } : item }); } break; } diff --git a/web/src/core-bridge/worker/workspaceStructure.ts b/web/src/core-bridge/worker/workspaceStructure.ts index 6d95836..a546c80 100644 --- a/web/src/core-bridge/worker/workspaceStructure.ts +++ b/web/src/core-bridge/worker/workspaceStructure.ts @@ -28,6 +28,10 @@ async function publish(id: string, body: WorkspaceBody): Promise export async function cachedWorkspace(id: string): Promise { return current.get(id) ?? loadWorkspace(id); } +/** 已加载的成员资料(没有时返回 undefined) */ +export function cachedPerson(id: string, username: string): WorkspaceBody['people'][number] | undefined { + return current.get(id)?.people.find(person => person.username === username); +} export async function loadWorkspace(id: string): Promise { const { session } = await getSession(id); const body = await apiFetch(`/spaces/${id}/structure`, { headers: session.authHeaders('read') }).then(r => r.json() as Promise);