fix(call): thumbnail fetch falls back to base64 if binary path returns nothing
Binary IPC (tauri::ipc::Response) came back as an unrecognised shape on the user's runtime — the frontend couldn't extract an ArrayBuffer and every thumbnail resolved to null, so every card rendered the placeholder icon. Added: - Widened the invoke typing to ArrayBuffer | Uint8Array | number[] so all three known Tauri/WebView2 deserialisation shapes parse. - A one-time console.warn when the Response body lands as an unknown object shape, so the real wire format can be diagnosed if this ever trips again. - An automatic tier-2 fallback: if the binary path produced 0 usable bytes, re-invoke the legacy base64 command and decode client-side. Slower on the JS thread than binary IPC but known to work across all Tauri 2.x runtimes. Net behaviour: thumbnails render again. If the binary path works on a given build, we get the fast path; otherwise the base64 fallback keeps the picker usable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -55,48 +55,72 @@ export async function captureScreenSourceThumbnail(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Binary-IPC variant. The Rust command uses `tauri::ipc::Response` to ship
|
// Thumbnail fetch with a two-tier fallback:
|
||||||
// raw JPEG bytes without JSON encoding; we wrap the resulting ArrayBuffer
|
// 1. binary-IPC path (`..._bytes`) — ArrayBuffer over Tauri's raw channel
|
||||||
// in a Blob so callers can hand it straight to `URL.createObjectURL` —
|
// 2. base64 path (legacy) — same command minus the ArrayBuffer wrapper
|
||||||
// never touches base64 on either side. Returns null when the Rust side
|
|
||||||
// produced zero bytes (capture failed, source vanished).
|
|
||||||
//
|
//
|
||||||
// Tauri's invoke may resolve with different shapes depending on runtime
|
// The binary path can come through in several shapes depending on the
|
||||||
// version — accept ArrayBuffer, Uint8Array, or a number[] fallback so a
|
// Tauri / WebView2 version combo: a real ArrayBuffer, a Uint8Array, or
|
||||||
// WebView2 variant that doesn't speak the binary channel still works.
|
// occasionally a plain number[] when the response got re-serialised.
|
||||||
|
// We normalise all three into an ArrayBuffer before handing it to Blob.
|
||||||
|
// If the binary path returns nothing usable we retry once on the base64
|
||||||
|
// command — keeps thumbnails visible while the binary contract settles.
|
||||||
|
let warnedBinaryShape = false;
|
||||||
export async function captureScreenSourceThumbnailBytes(
|
export async function captureScreenSourceThumbnailBytes(
|
||||||
sourceId: string,
|
sourceId: string,
|
||||||
): Promise<Blob | null> {
|
): Promise<Blob | null> {
|
||||||
if (!isTauriRuntime()) return null;
|
if (!isTauriRuntime()) return null;
|
||||||
try {
|
|
||||||
const { invoke } = await import('@tauri-apps/api/core');
|
const { invoke } = await import('@tauri-apps/api/core');
|
||||||
const result = await invoke<ArrayBuffer | Uint8Array | number[]>(
|
|
||||||
|
// ---- Tier 1: binary IPC ---------------------------------------------
|
||||||
|
try {
|
||||||
|
const result = await invoke<ArrayBuffer | Uint8Array | number[] | null>(
|
||||||
'capture_screen_source_thumbnail_bytes',
|
'capture_screen_source_thumbnail_bytes',
|
||||||
{ sourceId },
|
{ sourceId },
|
||||||
);
|
);
|
||||||
let bytes: ArrayBuffer | Uint8Array | null = null;
|
let bytes: Uint8Array | null = null;
|
||||||
if (result instanceof ArrayBuffer) {
|
if (result instanceof ArrayBuffer) {
|
||||||
bytes = result;
|
bytes = new Uint8Array(result);
|
||||||
} else if (result instanceof Uint8Array) {
|
} else if (result instanceof Uint8Array) {
|
||||||
bytes = result;
|
bytes = result;
|
||||||
} else if (Array.isArray(result) && result.length > 0) {
|
} else if (Array.isArray(result) && result.length > 0) {
|
||||||
bytes = new Uint8Array(result);
|
bytes = new Uint8Array(result);
|
||||||
|
} else if (result && typeof result === 'object') {
|
||||||
|
// One-time diagnostic so we can see the unexpected shape in the
|
||||||
|
// console if WebView2 de-serialises the Response body into a bag
|
||||||
|
// of properties instead of a transferable binary buffer.
|
||||||
|
if (!warnedBinaryShape) {
|
||||||
|
warnedBinaryShape = true;
|
||||||
|
console.warn(
|
||||||
|
'capture_screen_source_thumbnail_bytes: unexpected shape, falling back',
|
||||||
|
result,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!bytes || bytes.byteLength === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
// Normalise to ArrayBuffer so Blob() accepts the part without TS
|
if (bytes && bytes.byteLength > 0) {
|
||||||
// complaining about SharedArrayBuffer-backed Uint8Array flavours.
|
const copy = new Uint8Array(bytes.byteLength);
|
||||||
const buffer =
|
copy.set(bytes);
|
||||||
bytes instanceof ArrayBuffer
|
return new Blob([copy.buffer], { type: 'image/jpeg' });
|
||||||
? bytes
|
}
|
||||||
: (bytes.buffer.slice(
|
|
||||||
bytes.byteOffset,
|
|
||||||
bytes.byteOffset + bytes.byteLength,
|
|
||||||
) as ArrayBuffer);
|
|
||||||
return new Blob([buffer], { type: 'image/jpeg' });
|
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
console.warn('capture_screen_source_thumbnail_bytes failed', { sourceId, err });
|
console.warn('binary thumbnail path threw, trying base64 fallback', {
|
||||||
|
sourceId,
|
||||||
|
err,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Tier 2: base64 fallback ----------------------------------------
|
||||||
|
try {
|
||||||
|
const b64 = await invoke<string | null>('capture_screen_source_thumbnail', {
|
||||||
|
sourceId,
|
||||||
|
});
|
||||||
|
if (!b64) return null;
|
||||||
|
const bin = atob(b64);
|
||||||
|
const fallbackBytes = new Uint8Array(bin.length);
|
||||||
|
for (let i = 0; i < bin.length; i++) fallbackBytes[i] = bin.charCodeAt(i);
|
||||||
|
return new Blob([fallbackBytes.buffer], { type: 'image/jpeg' });
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.warn('base64 thumbnail fallback failed', { sourceId, err });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user