diff --git a/src/settings.tsx b/src/settings.tsx index 0458b60..d0d6593 100644 --- a/src/settings.tsx +++ b/src/settings.tsx @@ -1,19 +1,22 @@ import React, { useEffect, useState } from "react"; + +import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api/quick-connect-api"; import { getUserApi } from "@jellyfin/sdk/lib/utils/api/user-api"; -import { jellyfin, setJellyfinApi, setJellyfinUser } from "./app"; + +import { jellyfin, jellyfinApi, setJellyfinApi, setJellyfinUser } from "./app"; import styles from "./styles.module.css"; +type View = "url" | "password" | "quick-connect"; + export default function SettingsModal() { const [isLoggedIn, setIsLoggedIn] = useState(false); const [url, setUrl] = useState(Spicetify.LocalStorage.get("jellyfin-url") || ""); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); - const [isUsingQuickConnect, setIsUsingQuickConnect] = useState(false); + const [view, setView] = useState("url"); const [quickConnectCode, setQuickConnectCode] = useState(""); - const [isFocused, setIsFocused] = useState(false); - - const login = async () => { + const createApi = async () => { const servers = await jellyfin.discovery.getRecommendedServerCandidates(url); const best = jellyfin.discovery.findBestServer(servers); if (!best) { @@ -21,45 +24,91 @@ export default function SettingsModal() { return; } const api = jellyfin.createApi(best.address); - const userApi = getUserApi(api); Spicetify.LocalStorage.set("jellyfin-url", url); - const savedToken = Spicetify.LocalStorage.get("jellyfin-token"); - if (savedToken) { - api.accessToken = savedToken; - } else { - if (isUsingQuickConnect && quickConnectCode.length === 6) { - Spicetify.showNotification("Please enter the full quick connect code!", true); - return; - } + setJellyfinApi(api); + setView("password"); + }; - const auth = isUsingQuickConnect - ? await userApi.authenticateWithQuickConnect({ quickConnectDto: { Secret: quickConnectCode } }) - : await userApi.authenticateUserByName({ authenticateUserByName: { Username: username, Pw: password } }); + const login = async () => { + if (!jellyfinApi) return; + const userApi = getUserApi(jellyfinApi); - if (!auth.data.AccessToken) { - Spicetify.showNotification("Failed to login!", true); - return; - } + const auth = await userApi.authenticateUserByName({ authenticateUserByName: { Username: username, Pw: password } }); - api.accessToken = auth.data.AccessToken; - Spicetify.LocalStorage.set("jellyfin-token", auth.data.AccessToken); + if (!auth.data.AccessToken) { + Spicetify.showNotification("Failed to login!", true); + return; } - const user = await getUserApi(api).getCurrentUser(); + jellyfinApi!.accessToken = auth.data.AccessToken; + Spicetify.LocalStorage.set("jellyfin-token", auth.data.AccessToken); + + const user = await getUserApi(jellyfinApi!).getCurrentUser(); if (user.data.Id) { setJellyfinUser(user.data.Id!); Spicetify.LocalStorage.set("jellyfin-user", user.data.Id!); } - setJellyfinApi(api); setIsLoggedIn(true); }; useEffect(() => { - if (Spicetify.LocalStorage.get("jellyfin-token")) login(); - }, []); + if (view !== "quick-connect") return; + if (!jellyfinApi) return; + + const quickConnectApi = getQuickConnectApi(jellyfinApi); + let interval: NodeJS.Timeout; + + (async () => { + const enabled = await quickConnectApi.getQuickConnectEnabled(); + if (!enabled.data) { + Spicetify.showNotification("Quick Connect is not enabled on this server!", true); + setView("password"); + return; + } + + const init = await quickConnectApi.initiateQuickConnect(); + const secret = init.data.Secret!; + setQuickConnectCode(init.data.Code!); + + interval = setInterval(async () => { + try { + const state = await quickConnectApi.getQuickConnectState({ secret }); + if (!state.data.Authenticated) return; + + clearInterval(interval); + + const auth = await getUserApi(jellyfinApi!).authenticateWithQuickConnect({ + quickConnectDto: { Secret: secret }, + }); + + if (!auth.data.AccessToken) { + Spicetify.showNotification("Failed to login with Quick Connect!", true); + return; + } + + jellyfinApi!.accessToken = auth.data.AccessToken; + Spicetify.LocalStorage.set("jellyfin-token", auth.data.AccessToken); + + const user = await getUserApi(jellyfinApi!).getCurrentUser(); + if (user.data.Id) { + setJellyfinUser(user.data.Id!); + Spicetify.LocalStorage.set("jellyfin-user", user.data.Id!); + } + + setIsLoggedIn(true); + } catch { + clearInterval(interval); + Spicetify.showNotification("Quick Connect polling failed!", true); + setView("password"); + } + }, 2000); + })(); + + return () => clearInterval(interval); + }, [view]); if (isLoggedIn) return ( @@ -101,50 +150,43 @@ export default function SettingsModal() { -
- ); + if (view === "url") + return ( +
+
+ + setUrl(e.target.value)} /> +
+ +
+ +
+ ); + return (
-
- - setUrl(e.target.value)} /> -
- - {isUsingQuickConnect ? ( + {view === "quick-connect" ? (
- setQuickConnectCode(e.target.value.replace(/\D/g, ""))} - onFocus={() => setIsFocused(true)} - onBlur={() => setIsFocused(false)} - // Force caret to always be at the end - onKeyDown={(e) => { - if (["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) { - e.preventDefault(); - } - }} - // Same here - onSelect={(e) => { - const element = e.target as HTMLInputElement; - element.setSelectionRange(element.value.length, element.value.length); - }} - className={styles.quickConnectInput} - /> - {Array.from({ length: 6 }).map((_, i) => ( -
+
{quickConnectCode[i]}
))} @@ -161,31 +203,19 @@ export default function SettingsModal() { setPassword(e.target.value)} />
+ + )} -
-
- or -
-
- - -
); diff --git a/src/styles.module.css b/src/styles.module.css index 30a64ce..51f5bc4 100644 --- a/src/styles.module.css +++ b/src/styles.module.css @@ -9,9 +9,10 @@ } .hr { - flex-grow: 1; border: none; border-top: 1px solid var(--spice-button-disabled); + width: 100%; + margin: 0.5rem 0; } .modal { @@ -47,8 +48,7 @@ transition: 200ms border-color; } -.inputContainer input:focus, -.quickConnectBoxActive { +.inputContainer input:focus { border-color: var(--spice-button); }