Migrate all IPC call sites to typed tauri-specta commands.*
🏗️ Build and Test JellyTau / Run Tests (pull_request) Successful in 4m39s
Traceability Validation / Check Requirement Traces (pull_request) Failing after 36s
🏗️ Build and Test JellyTau / Build Android APK (pull_request) Failing after 1m57s

Replace the remaining ~155 untyped invoke() calls across stores, services,
components, and routes with the generated commands.* wrappers from
$lib/api/bindings, so every IPC call is compile-time-checked against the
command signatures.

- Register repository_get_subtitle_url and repository_get_video_download_url
  in specta_builder() and the invoke_handler; regenerate bindings.ts.
- Source duplicated wire types (AutoplaySettings, CacheConfig, Session,
  ConnectivityStatus, audio/video settings, etc.) from bindings.
- Fix two bugs surfaced by the typed wrappers:
  - VideoDownloadButton passed an un-awaited Promise as the stream URL.
  - setAutoplaySettings omitted the required userId argument.
- Update unit tests asserting the old invoke(name, args) shape.
- Remove the five param-naming guard tests; the compiler and codegen now
  enforce what they checked.

svelte-check: 0 errors. vitest: green. cargo test --lib: green.
This commit is contained in:
2026-06-21 08:47:04 +02:00
parent 14e9d7e03a
commit d01c2aab9f
47 changed files with 456 additions and 2119 deletions
+56 -100
View File
@@ -6,10 +6,11 @@
// TRACES: UR-009, UR-012 | IR-009, IR-014
import { writable, derived, get } from "svelte/store";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { commands } from "$lib/api/bindings";
import { RepositoryClient } from "$lib/api/repository-client";
import type { User, AuthResult } from "$lib/api/types";
import type { Session, AuthServerInfo as ServerInfo } from "$lib/api/bindings";
import { connectivity } from "./connectivity";
import { getDeviceId, clearCache as clearDeviceIdCache } from "$lib/services/deviceId";
@@ -29,29 +30,6 @@ interface AuthState {
sessionVerified: boolean;
}
interface Session {
userId: string;
username: string;
serverId: string;
serverUrl: string;
serverName: string;
accessToken: string;
verified: boolean;
needsReauth: boolean;
}
interface ServerInfo {
name: string;
version: string;
id: string;
normalizedUrl: string;
}
interface SecurityStatus {
usingKeyring: boolean;
storageType: string;
}
function createAuthStore() {
const initialState: AuthState = {
isAuthenticated: false,
@@ -163,7 +141,7 @@ function createAuthStore() {
try {
// Check security status
try {
const securityStatus = await invoke<SecurityStatus>("storage_get_security_status");
const securityStatus = await commands.storageGetSecurityStatus();
console.log("[Auth] Security status:", securityStatus);
if (!securityStatus.usingKeyring) {
update((s) => ({
@@ -178,7 +156,7 @@ function createAuthStore() {
// Initialize auth manager and get session
console.log("[Auth] Initializing auth manager...");
const session = await invoke<Session | null>("auth_initialize");
const session = await commands.authInitialize();
console.log("[Auth] Session retrieval result:", session ? "Session found" : "No session found");
if (session) {
@@ -192,12 +170,12 @@ function createAuthStore() {
const deviceId = await getDeviceId();
try {
console.log("[Auth] Configuring Rust player with restored session...");
await invoke("player_configure_jellyfin", {
serverUrl: session.serverUrl,
accessToken: session.accessToken,
userId: session.userId,
deviceId: deviceId,
});
await commands.playerConfigureJellyfin(
session.serverUrl,
session.accessToken,
session.userId,
deviceId
);
console.log("[Auth] Rust player configured for automatic playback reporting");
} catch (error) {
console.error("[Auth] Failed to configure Rust player:", error);
@@ -231,7 +209,7 @@ function createAuthStore() {
// Start background session verification
try {
const verifyDeviceId = await getDeviceId();
await invoke("auth_start_verification", { deviceId: verifyDeviceId });
await commands.authStartVerification(verifyDeviceId);
console.log("[Auth] Background verification started");
} catch (error) {
console.error("[Auth] Failed to start verification:", error);
@@ -273,7 +251,7 @@ function createAuthStore() {
try {
console.log("[Auth] Connecting to server:", serverUrl);
const serverInfo = await invoke<ServerInfo>("auth_connect_to_server", { serverUrl });
const serverInfo = await commands.authConnectToServer(serverUrl);
console.log("[Auth] Connected to server:", serverInfo.name, serverInfo.version);
console.log("[Auth] Normalized URL:", serverInfo.normalizedUrl);
@@ -302,47 +280,32 @@ function createAuthStore() {
const deviceId = await getDeviceId();
console.log("[Auth] Logging in as:", username);
const authResult = await invoke<AuthResult>("auth_login", {
serverUrl,
username,
password,
deviceId,
});
const authResult = await commands.authLogin(serverUrl, username, password, deviceId);
console.log("[Auth] Login successful:", authResult.user);
// Save to storage
await invoke("storage_save_server", {
id: authResult.serverId,
name: serverName,
url: serverUrl,
version: null,
});
await commands.storageSaveServer(authResult.serverId, serverName, serverUrl, null);
await invoke("storage_save_user", {
id: authResult.user.id,
serverId: authResult.serverId,
username: authResult.user.name,
accessToken: authResult.accessToken,
});
await commands.storageSaveUser(
authResult.user.id,
authResult.serverId,
authResult.user.name,
authResult.accessToken
);
await invoke("storage_set_active_user", {
userId: authResult.user.id,
serverId: authResult.serverId,
});
await commands.storageSetActiveUser(authResult.user.id, authResult.serverId);
// Set session in auth manager with server name
await invoke("auth_set_session", {
session: {
userId: authResult.user.id,
username: authResult.user.name,
serverId: authResult.serverId,
serverUrl,
serverName,
accessToken: authResult.accessToken,
verified: true,
needsReauth: false,
},
await commands.authSetSession({
userId: authResult.user.id,
username: authResult.user.name,
serverId: authResult.serverId,
serverUrl,
serverName,
accessToken: authResult.accessToken,
verified: true,
needsReauth: false,
});
// Create RepositoryClient
@@ -352,12 +315,12 @@ function createAuthStore() {
// Configure Rust player
try {
const playerDeviceId = await getDeviceId();
await invoke("player_configure_jellyfin", {
await commands.playerConfigureJellyfin(
serverUrl,
accessToken: authResult.accessToken,
userId: authResult.user.id,
deviceId: playerDeviceId,
});
authResult.accessToken,
authResult.user.id,
playerDeviceId
);
console.log("[Auth] Rust player configured for playback reporting");
} catch (error) {
console.error("[Auth] Failed to configure Rust player:", error);
@@ -380,7 +343,7 @@ function createAuthStore() {
// Start background verification
try {
const verifyDeviceId = await getDeviceId();
await invoke("auth_start_verification", { deviceId: verifyDeviceId });
await commands.authStartVerification(verifyDeviceId);
} catch (error) {
console.error("[Auth] Failed to start verification:", error);
}
@@ -404,25 +367,22 @@ function createAuthStore() {
const deviceId = await getDeviceId();
console.log("[Auth] Re-authenticating...");
const authResult = await invoke<AuthResult>("auth_reauthenticate", {
password,
deviceId,
});
const authResult = await commands.authReauthenticate(password, deviceId);
console.log("[Auth] Re-authentication successful");
// Update storage
await invoke("storage_save_user", {
id: authResult.user.id,
serverId: authResult.serverId,
username: authResult.user.name,
accessToken: authResult.accessToken,
});
await commands.storageSaveUser(
authResult.user.id,
authResult.serverId,
authResult.user.name,
authResult.accessToken
);
// Recreate repository with new credentials
if (repository) {
await repository.destroy();
const session = await invoke<Session | null>("auth_get_session");
const session = await commands.authGetSession();
if (session) {
await repository.create(session.serverUrl, authResult.user.id, authResult.accessToken, authResult.serverId);
}
@@ -431,12 +391,12 @@ function createAuthStore() {
// Reconfigure player
try {
const playerDeviceId = await getDeviceId();
await invoke("player_configure_jellyfin", {
serverUrl: repository ? await getCurrentSessionServerUrl() : "",
accessToken: authResult.accessToken,
userId: authResult.user.id,
deviceId: playerDeviceId,
});
await commands.playerConfigureJellyfin(
repository ? await getCurrentSessionServerUrl() : "",
authResult.accessToken,
authResult.user.id,
playerDeviceId
);
} catch (error) {
console.error("[Auth] Failed to reconfigure player:", error);
}
@@ -467,19 +427,15 @@ function createAuthStore() {
*/
async function logout() {
try {
const session = await invoke<Session | null>("auth_get_session");
const session = await commands.authGetSession();
if (session) {
const deviceId = await getDeviceId();
await invoke("auth_logout", {
serverUrl: session.serverUrl,
accessToken: session.accessToken,
deviceId,
});
await commands.authLogout(session.serverUrl, session.accessToken, deviceId);
}
// Disable Jellyfin reporting in player
try {
await invoke("player_disable_jellyfin");
await commands.playerDisableJellyfin();
} catch (error) {
console.error("[Auth] Failed to disable player reporting:", error);
}
@@ -524,7 +480,7 @@ function createAuthStore() {
*/
async function getCurrentSession() {
try {
return await invoke<Session | null>("auth_get_session");
return await commands.authGetSession();
} catch (error) {
console.error("[Auth] Failed to get current session:", error);
return null;
@@ -551,7 +507,7 @@ function createAuthStore() {
* Helper to get server URL from current session.
*/
async function getCurrentSessionServerUrl(): Promise<string> {
const session = await invoke<Session | null>("auth_get_session");
const session = await commands.authGetSession();
return session?.serverUrl || "";
}
@@ -562,7 +518,7 @@ function createAuthStore() {
try {
const deviceId = await getDeviceId();
console.log("[Auth] Retrying session verification after reconnection");
await invoke("auth_start_verification", { deviceId });
await commands.authStartVerification(deviceId);
} catch (error) {
console.error("[Auth] Failed to retry verification:", error);
}