fix(web): show the author of incoming messages right away [CI]
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/dev-image unknown status

Messages from other members or devices used to render as an unknown member
until the space structure refreshed. Use the author account the server
records for the object, together with the loaded profiles, when the item
arrives.
This commit is contained in:
justme committed 2026-10-16 07:04:31 +08:00
1 parent 12980ba6fe
commit fb5800f81d
3 files changed
+23 -4

No files matched your search

@@ -14,6 +14,10 @@ const states = new Map<string, CryptoState>();
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<string, DataKeys>();
/** 服务端登记的对象作者(账号)。收到别人的消息时据此先显示名字,不必等结构刷新 */
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;
+15 -4
View File
@@ -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<SenderInfo | undefined> {
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<void> {
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;
}
@@ -28,6 +28,10 @@ async function publish(id: string, body: WorkspaceBody): Promise<WorkspaceBody>
export async function cachedWorkspace(id: string): Promise<WorkspaceBody> {
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<WorkspaceBody> {
const { session } = await getSession(id);
const body = await apiFetch(`/spaces/${id}/structure`, { headers: session.authHeaders('read') }).then(r => r.json() as Promise<WorkspaceBody>);