feat(frontend): use a content token for the open-original link
Mint a content token on file load (POST /files/:id/content-token) and put it in the original-content URL instead of the access token, so opening an original — especially a long video — in a new tab keeps working past the 15-minute access token expiry. Falls back to the access token until the content token arrives, and re-mints when paging to another file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,10 @@
|
|||||||
let file = $state<File | null>(null);
|
let file = $state<File | null>(null);
|
||||||
let fileTags = $state<Tag[]>([]);
|
let fileTags = $state<Tag[]>([]);
|
||||||
let previewSrc = $state<string | null>(null);
|
let previewSrc = $state<string | null>(null);
|
||||||
|
// Capability token for the original-content URL, minted per file (see
|
||||||
|
// fetchContentToken). Outlives the 15-minute access token so a long video
|
||||||
|
// opened in a new tab keeps streaming.
|
||||||
|
let contentToken = $state<string | null>(null);
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
let saving = $state(false);
|
let saving = $state(false);
|
||||||
let error = $state('');
|
let error = $state('');
|
||||||
@@ -68,6 +72,8 @@
|
|||||||
error = '';
|
error = '';
|
||||||
// Drop the previous file's tags; they reload lazily when scrolled to.
|
// Drop the previous file's tags; they reload lazily when scrolled to.
|
||||||
fileTags = [];
|
fileTags = [];
|
||||||
|
// Invalidate the previous file's content token before re-minting.
|
||||||
|
contentToken = null;
|
||||||
try {
|
try {
|
||||||
const fileData = await api.get<File>(`/files/${id}`);
|
const fileData = await api.get<File>(`/files/${id}`);
|
||||||
if (fileId !== id) return; // paged on; ignore
|
if (fileId !== id) return; // paged on; ignore
|
||||||
@@ -79,6 +85,7 @@
|
|||||||
isPublic = fileData.is_public ?? false;
|
isPublic = fileData.is_public ?? false;
|
||||||
dirty = false;
|
dirty = false;
|
||||||
void fetchPreview(id);
|
void fetchPreview(id);
|
||||||
|
void fetchContentToken(id);
|
||||||
// Log the view (activity.file_views). Fire-and-forget — never block or
|
// Log the view (activity.file_views). Fire-and-forget — never block or
|
||||||
// fail the viewer over view tracking.
|
// fail the viewer over view tracking.
|
||||||
void api.post(`/files/${id}/views`).catch(() => {});
|
void api.post(`/files/${id}/views`).catch(() => {});
|
||||||
@@ -103,13 +110,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mint a content token for this file so the "open original" link survives the
|
||||||
|
// 15-minute access-token expiry — a long video opened in a new tab keeps
|
||||||
|
// streaming, since the token is file-scoped and outlives session rotation.
|
||||||
|
// Fire-and-forget; the link falls back to the access token until it arrives.
|
||||||
|
async function fetchContentToken(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await api.post<{ token: string; expires_in: number }>(
|
||||||
|
`/files/${id}/content-token`
|
||||||
|
);
|
||||||
|
if (fileId === id) contentToken = res.token;
|
||||||
|
} catch {
|
||||||
|
// non-critical — originalUrl falls back to the access token below
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Direct link to the full-resolution original, opened in a new tab. A
|
// Direct link to the full-resolution original, opened in a new tab. A
|
||||||
// navigation can't send the auth header, so the token rides in the query —
|
// navigation can't send the auth header, so the token rides in the query —
|
||||||
// the server accepts ?access_token= for GET media. Reactive on the token so a
|
// the server accepts ?access_token= for GET media. Prefer the long-lived
|
||||||
// silent refresh keeps the link valid.
|
// content token; fall back to the access token until it's minted.
|
||||||
let originalUrl = $derived(
|
let originalUrl = $derived(
|
||||||
fileId
|
fileId
|
||||||
? `/api/v1/files/${fileId}/content?inline=1&access_token=${encodeURIComponent($authStore.accessToken ?? '')}`
|
? `/api/v1/files/${fileId}/content?inline=1&access_token=${encodeURIComponent(contentToken ?? $authStore.accessToken ?? '')}`
|
||||||
: '#'
|
: '#'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user