| 1 | import { |
| 2 | enqueueHtmlThumbnails, |
| 3 | getFreshHtmlThumbnailPaths, |
| 4 | type HtmlThumbnailRequest |
| 5 | } from '../io/thumbnails/html-thumbnail-service' |
| 6 | |
| 7 | const SESSION_THUMBNAIL_VARIANT = 'first-page' |
| 8 | |
| 9 | export type SessionThumbnailEntry = { |
| 10 | sessionId: string |
| 11 | pageId?: string | null |
| 12 | sourcePath?: string | null |
| 13 | width?: number |
| 14 | height?: number |
| 15 | } |
| 16 | |
| 17 | function createSessionThumbnailRequest( |
| 18 | sessionId: string, |
| 19 | sourcePath: string | null | undefined, |
| 20 | pageId: string | null | undefined, |
| 21 | width?: number, |
| 22 | height?: number |
| 23 | ): HtmlThumbnailRequest | null { |
| 24 | const normalizedSessionId = sessionId.trim() |
| 25 | const normalizedSourcePath = typeof sourcePath === 'string' ? sourcePath.trim() : '' |
| 26 | if (!normalizedSessionId || !normalizedSourcePath) { |
| 27 | return null |
| 28 | } |
| 29 | const request: HtmlThumbnailRequest = { |
| 30 | resourceType: 'session', |
| 31 | resourceId: normalizedSessionId, |
| 32 | variant: SESSION_THUMBNAIL_VARIANT, |
| 33 | sourcePath: normalizedSourcePath, |
| 34 | pageId: typeof pageId === 'string' ? pageId.trim() : '' |
| 35 | } |
| 36 | if (width && height) { |
| 37 | request.captureWidth = width |
| 38 | request.captureHeight = height |
| 39 | request.thumbnailWidth = 640 |
| 40 | request.thumbnailHeight = Math.max(64, Math.round((640 * height) / width)) |
| 41 | } |
| 42 | return request |
| 43 | } |
| 44 | |
| 45 | function entriesToRequests(entries: SessionThumbnailEntry[]): HtmlThumbnailRequest[] { |
| 46 | const requests: HtmlThumbnailRequest[] = [] |
| 47 | for (const entry of entries) { |
| 48 | const request = createSessionThumbnailRequest( |
| 49 | entry.sessionId, |
| 50 | entry.sourcePath, |
| 51 | entry.pageId, |
| 52 | entry.width, |
| 53 | entry.height |
| 54 | ) |
| 55 | if (request) requests.push(request) |
| 56 | } |
| 57 | return requests |
| 58 | } |
| 59 | |
| 60 | export async function getSessionFirstPageThumbnails( |
| 61 | entries: SessionThumbnailEntry[] |
| 62 | ): Promise<Map<string, string>> { |
| 63 | return getFreshHtmlThumbnailPaths(entriesToRequests(entries)) |
| 64 | } |
| 65 | |
| 66 | export async function warmSessionFirstPageThumbnails( |
| 67 | entries: SessionThumbnailEntry[] |
| 68 | ): Promise<Map<string, string>> { |
| 69 | const requests = entriesToRequests(entries) |
| 70 | let freshMap: Map<string, string> |
| 71 | try { |
| 72 | freshMap = await getFreshHtmlThumbnailPaths(requests) |
| 73 | } catch (error) { |
| 74 | console.warn('[session-thumbnail] fresh thumbnail lookup failed', error) |
| 75 | return new Map() |
| 76 | } |
| 77 | const missing = requests.filter((request) => !freshMap.has(request.resourceId)) |
| 78 | if (missing.length > 0) { |
| 79 | void enqueueHtmlThumbnails(missing).catch((error) => { |
| 80 | console.warn('[session-thumbnail] background warmup failed', error) |
| 81 | }) |
| 82 | } |
| 83 | return freshMap |
| 84 | } |
| 85 |