From 15118707667358d81dc4b6c6bc5c14ebf3c566b4 Mon Sep 17 00:00:00 2001 From: Fishandchips321 Date: Sun, 22 Mar 2026 13:24:45 +0000 Subject: [PATCH 1/2] feat(jellyfin-auth): changed frontend server add flow to use the Quick Connect feature to get the api token --- backend/src/Controllers/ServersController.cs | 2 + backend/src/Repositories/JellyfinApiClient.cs | 2 +- frontend/src/Lib/QuickConnect.ts | 37 +++++++++++++++++++ .../ServerManagement/ServerManagement.tsx | 34 ++++++++++++----- 4 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 frontend/src/Lib/QuickConnect.ts diff --git a/backend/src/Controllers/ServersController.cs b/backend/src/Controllers/ServersController.cs index 9abb395..d4725ed 100644 --- a/backend/src/Controllers/ServersController.cs +++ b/backend/src/Controllers/ServersController.cs @@ -31,6 +31,8 @@ public class ServersController : ControllerBase var servers = await _serverService.GetServers(); + _logger.LogDebug("Servers found: {}", servers.Length); + return Ok(servers); } diff --git a/backend/src/Repositories/JellyfinApiClient.cs b/backend/src/Repositories/JellyfinApiClient.cs index acd6d85..338e7dd 100644 --- a/backend/src/Repositories/JellyfinApiClient.cs +++ b/backend/src/Repositories/JellyfinApiClient.cs @@ -176,7 +176,7 @@ public class JellyfinApiClient private string GetAuthHeader() { - var header = "Client=Test, Device=Test, DeviceId=Test, Version=1"; + var header = "Client=JellyGlass, Device=JellyGlass, DeviceId=JellyGlass, Version=1"; if (_apiKey != String.Empty) { diff --git a/frontend/src/Lib/QuickConnect.ts b/frontend/src/Lib/QuickConnect.ts new file mode 100644 index 0000000..c50771b --- /dev/null +++ b/frontend/src/Lib/QuickConnect.ts @@ -0,0 +1,37 @@ +import axios from "axios"; + +const DeviceInfoString = "MediaBrowser Client=JellyGlass, Device=JellyGlass, DeviceId=JellyGlass, Version=1"; + +interface QuickConnectInfo { + Code: string; + Secret: string; + Authenticated: boolean +} + +export interface QuickConnectAuth { + AccessToken: string; +} + +export const BeginQuickConnect = async (url: string, callback: (apiToken: string) => void, isCancelled: () => boolean): Promise => { + const response = await axios.post(`${url}/QuickConnect/Initiate`, {}, { headers: { Authorization: DeviceInfoString } }); + + setTimeout(() => { PollQuickConnect(url, response.data, callback, isCancelled) }); + + return response.data.Code; +} + +const PollQuickConnect = async (url: string, info: QuickConnectInfo, callback: (apiToken: string) => void, isCancelled: () => boolean) => { + if (isCancelled()) { + return; + } + + const response = await axios.get(`${url}/QuickConnect/Connect?secret=${info.Secret}`, { headers: { Authorization: DeviceInfoString } }); + + if (response.data.Authenticated) { + const authResponse = await axios.post(`${url}/Users/AuthenticateWithQuickConnect`, { Secret: response.data.Secret }) + callback(authResponse.data.AccessToken); + } + else { + setTimeout(() => { PollQuickConnect(url, response.data, callback, isCancelled) }, 5000) + } +} \ No newline at end of file diff --git a/frontend/src/Pages/Admin/ServerManagement/ServerManagement.tsx b/frontend/src/Pages/Admin/ServerManagement/ServerManagement.tsx index 17cbc48..0707bd0 100644 --- a/frontend/src/Pages/Admin/ServerManagement/ServerManagement.tsx +++ b/frontend/src/Pages/Admin/ServerManagement/ServerManagement.tsx @@ -1,17 +1,18 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { Button, Form, Spinner, Table } from "react-bootstrap"; import { AddServer, getServerList, RemoveServer, type Server } from "../../../Lib/Servers"; import { useImmer } from "use-immer"; import styles from "../Management.module.scss"; +import { BeginQuickConnect } from "../../../Lib/QuickConnect"; const ServerManagement = () => { const [servers, setServers] = useImmer | undefined>(undefined); + const [addCancelled, setAddCancelled] = useState(false); const [addServerInfo, setAddServerInfo] = useImmer({ url: "", owner: "", - apiToken: "" }); useEffect(() => { @@ -37,8 +38,26 @@ const ServerManagement = () => { }) } - const onServerAdd = () => { - AddServer(addServerInfo.owner, addServerInfo.url, addServerInfo.apiToken).then(result => { + const onQuickConnect = () => { + //start quick connect request + //show quick connect code to user (modal) + //poll quick connect state until it's authenticated + //fetch quick connect credentials + //onServerAdd(token); + BeginQuickConnect(addServerInfo.url, onServerAdd, () => addCancelled).then(quickConnectCode => { + //show modal with code + alert(`Your quick connect code is ${quickConnectCode}`); + }).catch(err => { + //alert user to error + //possibly clear fields? + console.log(err); + alert(err); + }); + } + + const onServerAdd = (apiToken: string) => { + //hide quick connect modal + AddServer(addServerInfo.owner, addServerInfo.url, apiToken).then(result => { if (result.errored) { alert("Server was added, but is not working. Check the logs for details"); } @@ -52,7 +71,6 @@ const ServerManagement = () => { } setAddServerInfo(draft => { - draft.apiToken = ""; draft.owner = ""; draft.url = ""; }); @@ -74,11 +92,7 @@ const ServerManagement = () => { Url setAddServerInfo(draft => { draft.url = e.target.value })} value={addServerInfo.url} /> - - ApiToken - setAddServerInfo(draft => { draft.apiToken = e.target.value })} value={addServerInfo.apiToken} /> - - + From e396d9844ceba3465340317a3b243ed58d12f19e Mon Sep 17 00:00:00 2001 From: Fishandchips321 Date: Sun, 22 Mar 2026 18:43:53 +0000 Subject: [PATCH 2/2] feat(jellyfn-auth): added crude error handling --- frontend/src/Lib/QuickConnect.ts | 26 ++++++++----- .../ServerManagement/ServerManagement.tsx | 38 ++++++++++++------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/frontend/src/Lib/QuickConnect.ts b/frontend/src/Lib/QuickConnect.ts index c50771b..89e5251 100644 --- a/frontend/src/Lib/QuickConnect.ts +++ b/frontend/src/Lib/QuickConnect.ts @@ -1,4 +1,4 @@ -import axios from "axios"; +import axios, { AxiosError } from "axios"; const DeviceInfoString = "MediaBrowser Client=JellyGlass, Device=JellyGlass, DeviceId=JellyGlass, Version=1"; @@ -12,26 +12,32 @@ export interface QuickConnectAuth { AccessToken: string; } -export const BeginQuickConnect = async (url: string, callback: (apiToken: string) => void, isCancelled: () => boolean): Promise => { +export const BeginQuickConnect = async (url: string, callback: (apiToken: string) => void, isCancelled: () => boolean, onError: (e: AxiosError) => void): Promise => { const response = await axios.post(`${url}/QuickConnect/Initiate`, {}, { headers: { Authorization: DeviceInfoString } }); - setTimeout(() => { PollQuickConnect(url, response.data, callback, isCancelled) }); + setTimeout(() => { PollQuickConnect(url, response.data, callback, isCancelled, onError) }); return response.data.Code; } -const PollQuickConnect = async (url: string, info: QuickConnectInfo, callback: (apiToken: string) => void, isCancelled: () => boolean) => { +const PollQuickConnect = async (url: string, info: QuickConnectInfo, callback: (apiToken: string) => void, isCancelled: () => boolean, onError: (e: AxiosError) => void) => { if (isCancelled()) { return; } - const response = await axios.get(`${url}/QuickConnect/Connect?secret=${info.Secret}`, { headers: { Authorization: DeviceInfoString } }); + try { + const response = await axios.get(`${url}/QuickConnect/Connect?secret=${info.Secret}`, { headers: { Authorization: DeviceInfoString } }); - if (response.data.Authenticated) { - const authResponse = await axios.post(`${url}/Users/AuthenticateWithQuickConnect`, { Secret: response.data.Secret }) - callback(authResponse.data.AccessToken); + if (response.data.Authenticated) { + const authResponse = await axios.post(`${url}/Users/AuthenticateWithQuickConnect`, { Secret: response.data.Secret }) + callback(authResponse.data.AccessToken); + } + else { + setTimeout(() => { PollQuickConnect(url, response.data, callback, isCancelled, onError) }, 5000) + } } - else { - setTimeout(() => { PollQuickConnect(url, response.data, callback, isCancelled) }, 5000) + catch (e) { + console.log(e); + onError(e as AxiosError); } } \ No newline at end of file diff --git a/frontend/src/Pages/Admin/ServerManagement/ServerManagement.tsx b/frontend/src/Pages/Admin/ServerManagement/ServerManagement.tsx index 0707bd0..a24819c 100644 --- a/frontend/src/Pages/Admin/ServerManagement/ServerManagement.tsx +++ b/frontend/src/Pages/Admin/ServerManagement/ServerManagement.tsx @@ -1,14 +1,16 @@ import { useEffect, useState } from "react"; -import { Button, Form, Spinner, Table } from "react-bootstrap"; +import { Button, Form, Modal, Spinner, Table } from "react-bootstrap"; import { AddServer, getServerList, RemoveServer, type Server } from "../../../Lib/Servers"; import { useImmer } from "use-immer"; import styles from "../Management.module.scss"; import { BeginQuickConnect } from "../../../Lib/QuickConnect"; +import type { AxiosError } from "axios"; const ServerManagement = () => { const [servers, setServers] = useImmer | undefined>(undefined); - const [addCancelled, setAddCancelled] = useState(false); + const [isCancelled, setIsCancelled] = useState(false); + const [quickConnectCode, setQuickConnectCode] = useState(undefined); const [addServerInfo, setAddServerInfo] = useImmer({ url: "", @@ -39,24 +41,23 @@ const ServerManagement = () => { } const onQuickConnect = () => { - //start quick connect request - //show quick connect code to user (modal) - //poll quick connect state until it's authenticated - //fetch quick connect credentials - //onServerAdd(token); - BeginQuickConnect(addServerInfo.url, onServerAdd, () => addCancelled).then(quickConnectCode => { - //show modal with code - alert(`Your quick connect code is ${quickConnectCode}`); + setIsCancelled(false); + BeginQuickConnect(addServerInfo.url, onServerAdd, () => isCancelled, onQuickConnectError).then(code => { + setQuickConnectCode(code); }).catch(err => { - //alert user to error - //possibly clear fields? console.log(err); alert(err); }); } + const onQuickConnectError = (e: AxiosError) => { + setIsCancelled(true); + setQuickConnectCode(undefined); + alert(e); + } + const onServerAdd = (apiToken: string) => { - //hide quick connect modal + setQuickConnectCode(undefined); AddServer(addServerInfo.owner, addServerInfo.url, apiToken).then(result => { if (result.errored) { alert("Server was added, but is not working. Check the logs for details"); @@ -121,6 +122,17 @@ const ServerManagement = () => { }
+ { setQuickConnectCode(undefined); setIsCancelled(true); }}> + + Jellyfin quick connect code + + + Your quick connect code is {quickConnectCode} + + + + + ) }