Replace upload file paths with single-use pick tokens

This commit is contained in:
Avi 2026-08-21 13:48:34 -05:00
commit 6e627a3341
6 changed files with 62 additions and 27 deletions

View file

@ -75,7 +75,7 @@ export const api = {
removeVaultPassword: (password: string) => call<AppState>('remove_vault_password', { password }),
revealSecretKey: (npub: string) => call<RevealedKey>('reveal_secret_key', { npub }),
pickImages: () => call<PickedImage[]>('pick_image'),
uploadImage: (path: string) => call<UploadedImage>('upload_image', { path }),
uploadImage: (token: string) => call<UploadedImage>('upload_image', { token }),
linkPreview: (url: string) => call<LinkPreview | null>('link_preview', { url }),
signerConnect: (uri: string) => call<SignerStatus>('signer_connect', { uri }),
signerDisconnect: () => call<SignerStatus>('signer_disconnect'),

View file

@ -105,10 +105,10 @@ export interface RevealedKey {
nsec: string;
}
/** An image file selected via the native file dialog. */
/** An image selected via the native file dialog, referenced by upload token. */
export interface PickedImage {
/** Absolute path on disk. */
path: string;
/** One-time token that authorises exactly one `upload_image` call. */
token: string;
/** File name for display. */
name: string;
/** Best-effort MIME type from the file extension. */

View file

@ -105,7 +105,7 @@ export function ComposeScreen() {
const picked = await pickImages();
const uploaded: Attachment[] = [];
for (const image of picked) {
const result = await uploadImage(image.path);
const result = await uploadImage(image.token);
uploaded.push({ url: result.url, mime: result.mime, name: image.name });
}
setAttachments((prev) => [...prev, ...uploaded]);

View file

@ -56,7 +56,7 @@ interface AppContextValue {
removeVaultPassword: (password: string) => Promise<AppState>;
revealSecretKey: (npub: string) => Promise<RevealedKey>;
pickImages: () => Promise<PickedImage[]>;
uploadImage: (path: string) => Promise<UploadedImage>;
uploadImage: (token: string) => Promise<UploadedImage>;
linkPreview: (url: string) => Promise<LinkPreview | null>;
signerConnect: (uri: string) => Promise<SignerStatus>;
signerDisconnect: () => Promise<SignerStatus>;
@ -177,7 +177,7 @@ export function AppProvider({ children }: { children: ReactNode }) {
);
const revealSecretKey = useCallback((npub: string) => api.revealSecretKey(npub), []);
const pickImages = useCallback(() => api.pickImages(), []);
const uploadImage = useCallback((path: string) => api.uploadImage(path), []);
const uploadImage = useCallback((token: string) => api.uploadImage(token), []);
const linkPreview = useCallback((url: string) => api.linkPreview(url), []);
const signerConnect = useCallback((uri: string) => api.signerConnect(uri), []);
const signerDisconnect = useCallback(() => api.signerDisconnect(), []);

View file

@ -32,8 +32,8 @@ export interface FakeBackend {
nextErrors: Record<string, { message: string; details?: string; code?: string }>;
/** Every request dispatched through `api.request`, in order. */
requests: { method: string; params: Record<string, unknown> }[];
/** Files returned by `pick_image`. */
pickedImages: { path: string; name: string; mime: string }[];
/** Files returned by `pick_image` (token, name, mime — never paths). */
pickedImages: { token: string; name: string; mime: string }[];
/** URLs returned by `upload_image`, one per call. */
uploadUrls: string[];
/** Current NIP-46 signer status. */
@ -92,7 +92,7 @@ export function createFakeBackend(initial?: AppState): FakeBackend {
relayErrors: new Set(),
nextErrors: {},
requests: [],
pickedImages: [{ path: '/tmp/picked.png', name: 'picked.png', mime: 'image/png' }],
pickedImages: [{ token: 'fake-pick-token', name: 'picked.png', mime: 'image/png' }],
uploadUrls: ['https://cdn.nostr.build/i/uploaded.png'],
signer: makeSignerStatus(),
setSigner(next) {
@ -196,6 +196,9 @@ export function createFakeBackend(initial?: AppState): FakeBackend {
return [...backend.pickedImages];
case 'upload_image': {
if (typeof params.token !== 'string' || params.token === '') {
throw Object.assign(new Error('No upload token given.'), { code: 'unknown_token' });
}
const index = backend.requests.filter((r) => r.method === 'upload_image').length - 1;
const url =
backend.uploadUrls[index] ?? backend.uploadUrls[backend.uploadUrls.length - 1] ?? '';