feat(media): implement caching for chat media and update components to use cached media
This commit is contained in:
@@ -5,4 +5,5 @@
|
||||
export * from "./chat_storage";
|
||||
export * from "./local_chat_db";
|
||||
export * from "./local_chat_storage";
|
||||
export * from "./local_chat_media_storage";
|
||||
export * from "./local_message";
|
||||
|
||||
@@ -32,13 +32,34 @@ export interface LocalMessageRow {
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
export type LocalChatMediaKind = "image" | "audio";
|
||||
|
||||
export interface LocalChatMediaRow {
|
||||
cacheKey: string;
|
||||
ownerKey: string;
|
||||
messageId: string;
|
||||
kind: LocalChatMediaKind;
|
||||
remoteUrl: string;
|
||||
blob: Blob;
|
||||
mimeType: string;
|
||||
byteSize: number;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
lastAccessedAt: number;
|
||||
}
|
||||
|
||||
export class LocalChatDB extends Dexie {
|
||||
messages!: Table<LocalMessageRow, number>;
|
||||
media!: Table<LocalChatMediaRow, string>;
|
||||
|
||||
constructor(dbName: string = "cozsweet-chat") {
|
||||
super(dbName);
|
||||
this.version(1).stores({
|
||||
messages: "++dbId",
|
||||
});
|
||||
this.version(2).stores({
|
||||
messages: "++dbId",
|
||||
media: "cacheKey, ownerKey, messageId, kind, remoteUrl, updatedAt, lastAccessedAt",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
"use client";
|
||||
|
||||
import { Result, type Result as ResultT } from "@/utils";
|
||||
|
||||
import {
|
||||
LocalChatDB,
|
||||
type LocalChatMediaKind,
|
||||
type LocalChatMediaRow,
|
||||
} from "./local_chat_db";
|
||||
|
||||
export type { LocalChatMediaKind, LocalChatMediaRow };
|
||||
|
||||
export interface SaveLocalChatMediaInput {
|
||||
cacheKey: string;
|
||||
ownerKey: string;
|
||||
messageId: string;
|
||||
kind: LocalChatMediaKind;
|
||||
remoteUrl: string;
|
||||
blob: Blob;
|
||||
mimeType: string;
|
||||
}
|
||||
|
||||
export class LocalChatMediaStorage {
|
||||
private readonly db: LocalChatDB;
|
||||
private static _instance: LocalChatMediaStorage | null = null;
|
||||
|
||||
constructor(db?: LocalChatDB) {
|
||||
this.db = db ?? new LocalChatDB();
|
||||
}
|
||||
|
||||
static getInstance(): LocalChatMediaStorage {
|
||||
if (!LocalChatMediaStorage._instance) {
|
||||
LocalChatMediaStorage._instance = new LocalChatMediaStorage();
|
||||
}
|
||||
return LocalChatMediaStorage._instance;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static _resetForTests(): void {
|
||||
LocalChatMediaStorage._instance = null;
|
||||
}
|
||||
|
||||
async getMedia(cacheKey: string): Promise<ResultT<LocalChatMediaRow | null>> {
|
||||
try {
|
||||
const row = (await this.db.media.get(cacheKey)) ?? null;
|
||||
if (row) {
|
||||
await this.db.media.update(cacheKey, {
|
||||
lastAccessedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
return Result.ok(row);
|
||||
} catch (e) {
|
||||
return Result.err(e);
|
||||
}
|
||||
}
|
||||
|
||||
async saveMedia(input: SaveLocalChatMediaInput): Promise<ResultT<void>> {
|
||||
try {
|
||||
const now = Date.now();
|
||||
const existing = await this.db.media.get(input.cacheKey);
|
||||
await this.db.media.put({
|
||||
cacheKey: input.cacheKey,
|
||||
ownerKey: input.ownerKey,
|
||||
messageId: input.messageId,
|
||||
kind: input.kind,
|
||||
remoteUrl: input.remoteUrl,
|
||||
blob: input.blob,
|
||||
mimeType: input.mimeType,
|
||||
byteSize: input.blob.size,
|
||||
createdAt: existing?.createdAt ?? now,
|
||||
updatedAt: now,
|
||||
lastAccessedAt: now,
|
||||
});
|
||||
return Result.ok(undefined);
|
||||
} catch (e) {
|
||||
return Result.err(e);
|
||||
}
|
||||
}
|
||||
|
||||
async clearAll(): Promise<ResultT<void>> {
|
||||
try {
|
||||
await this.db.media.clear();
|
||||
return Result.ok(undefined);
|
||||
} catch (e) {
|
||||
return Result.err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user