@@ -15,6 +15,8 @@ import {
1515 // @ts -expect-error weird typings?
1616 SessionInfo ,
1717} from "@jellyfin/sdk/lib/generated-client/index.js" ;
18+ // @ts -expect-error weird typings?
19+ import { getImageApi } from "@jellyfin/sdk/lib/utils/api/index.js" ;
1820import { PlayerStateDataMaybePlay } from "../../common/infrastructure/Atomic.js" ;
1921
2022const dataAsFixture = ( data : any ) : TestFixture => {
@@ -132,6 +134,120 @@ describe("Jellyfin API Source", function() {
132134 } ) ;
133135 } ) ;
134136
137+ describe ( 'Parses and replaces frontendUrlOverride correctly if set' , function ( ) {
138+
139+ const item = {
140+ AlbumId : 123 ,
141+ AlbumPrimaryImageTag : 'Primary' ,
142+ ParentId : 456 ,
143+ ServerId : 789 ,
144+ } ;
145+ const sourceUrl = 'http://192.168.10.11:8096' ;
146+ const frontendUrlOverride = 'https://myjellyfin.com' ;
147+
148+ it ( 'Should use default url if frontendUrlOverride unset' , async function ( ) {
149+ const jf = createJfApi ( { ...defaultJfApiCreds , url : sourceUrl } ) ;
150+ await jf . buildInitData ( ) ;
151+ jf . address = sourceUrl ;
152+ jf . api = jf . client . createApi ( jf . address ) ;
153+ jf . imageApi = getImageApi ( jf . api ) ;
154+
155+ expect ( jf . formatPlayObjAware ( item ) . meta . art . album ) . to . be . eql ( `${ sourceUrl } /Items/123/Images/Primary?maxHeight=500` ) ;
156+ expect ( jf . formatPlayObjAware ( item ) . meta . url . web ) . to . be . eql ( `${ sourceUrl } /web/#/details?id=456&serviceId=789` ) ;
157+
158+ await jf . destroy ( ) ;
159+ } ) ;
160+
161+ it ( 'Should parse and replace frontendUrlOverride correctly' , async function ( ) {
162+ const jf = createJfApi ( { ...defaultJfApiCreds , frontendUrlOverride : frontendUrlOverride , url : sourceUrl } ) ;
163+ await jf . buildInitData ( ) ;
164+ jf . address = sourceUrl ;
165+ jf . api = jf . client . createApi ( jf . address ) ;
166+ jf . imageApi = getImageApi ( jf . api ) ;
167+
168+ expect ( jf . formatPlayObjAware ( item ) . meta . art . album ) . to . be . eql ( `${ frontendUrlOverride } /Items/123/Images/Primary?maxHeight=500` ) ;
169+ expect ( jf . formatPlayObjAware ( item ) . meta . url . web ) . to . be . eql ( `${ frontendUrlOverride } /web/#/details?id=456&serviceId=789` ) ;
170+
171+ await jf . destroy ( ) ;
172+ } ) ;
173+ } ) ;
174+
175+ describe ( 'Correctly replaces URLs with frontendUrlOverride' , function ( ) {
176+
177+ const sourceUrl = 'http://192.168.10.11:8096' ;
178+ const frontendUrlOverride = 'https://myjellyfin.com' ;
179+
180+ it ( 'Should return original URL when frontendUrlOverride is not set' , async function ( ) {
181+ const jf = createJfApi ( { ...defaultJfApiCreds , url : sourceUrl } ) ;
182+ await jf . buildInitData ( ) ;
183+
184+ const testUrl = `${ sourceUrl } /Items/123/Images/Primary` ;
185+ expect ( jf . replaceUrlIfNeeded ( testUrl ) ) . to . be . eql ( testUrl ) ;
186+
187+ await jf . destroy ( ) ;
188+ } ) ;
189+
190+ it ( 'Should return original URL when frontendUrlOverride is empty string' , async function ( ) {
191+ const jf = createJfApi ( { ...defaultJfApiCreds , url : sourceUrl , frontendUrlOverride : '' } ) ;
192+ await jf . buildInitData ( ) ;
193+
194+ const testUrl = `${ sourceUrl } /Items/123/Images/Primary` ;
195+ expect ( jf . replaceUrlIfNeeded ( testUrl ) ) . to . be . eql ( testUrl ) ;
196+
197+ await jf . destroy ( ) ;
198+ } ) ;
199+
200+ it ( 'Should replace source URL with frontendUrlOverride when set' , async function ( ) {
201+ const jf = createJfApi ( { ...defaultJfApiCreds , url : sourceUrl , frontendUrlOverride : frontendUrlOverride } ) ;
202+ await jf . buildInitData ( ) ;
203+
204+ const testUrl = `${ sourceUrl } /Items/123/Images/Primary` ;
205+ const expectedUrl = `${ frontendUrlOverride } /Items/123/Images/Primary` ;
206+ expect ( jf . replaceUrlIfNeeded ( testUrl ) ) . to . be . eql ( expectedUrl ) ;
207+
208+ await jf . destroy ( ) ;
209+ } ) ;
210+
211+ it ( 'Should return original URL when input URL is undefined' , async function ( ) {
212+ const jf = createJfApi ( { ...defaultJfApiCreds , url : sourceUrl , frontendUrlOverride : frontendUrlOverride } ) ;
213+ await jf . buildInitData ( ) ;
214+
215+ expect ( jf . replaceUrlIfNeeded ( undefined ) ) . to . be . undefined ;
216+
217+ await jf . destroy ( ) ;
218+ } ) ;
219+
220+ it ( 'Should return original URL when input URL is empty string' , async function ( ) {
221+ const jf = createJfApi ( { ...defaultJfApiCreds , url : sourceUrl , frontendUrlOverride : frontendUrlOverride } ) ;
222+ await jf . buildInitData ( ) ;
223+
224+ expect ( jf . replaceUrlIfNeeded ( '' ) ) . to . be . eql ( '' ) ;
225+
226+ await jf . destroy ( ) ;
227+ } ) ;
228+
229+ it ( 'Should not replace URL when source URL is not present' , async function ( ) {
230+ const jf = createJfApi ( { ...defaultJfApiCreds , url : sourceUrl , frontendUrlOverride : frontendUrlOverride } ) ;
231+ await jf . buildInitData ( ) ;
232+
233+ const testUrl = 'https://some-other-domain.com/Items/123/Images/Primary' ;
234+ expect ( jf . replaceUrlIfNeeded ( testUrl ) ) . to . be . eql ( testUrl ) ;
235+
236+ await jf . destroy ( ) ;
237+ } ) ;
238+
239+ it ( 'Should not replace multiple occurrences of source URL' , async function ( ) {
240+ const jf = createJfApi ( { ...defaultJfApiCreds , url : sourceUrl , frontendUrlOverride : frontendUrlOverride } ) ;
241+ await jf . buildInitData ( ) ;
242+
243+ const testUrl = `${ sourceUrl } /redirect?url=${ sourceUrl } /Items/123` ;
244+ const expectedUrl = `${ frontendUrlOverride } /redirect?url=${ sourceUrl } /Items/123` ;
245+ expect ( jf . replaceUrlIfNeeded ( testUrl ) ) . to . be . eql ( expectedUrl ) ;
246+
247+ await jf . destroy ( ) ;
248+ } ) ;
249+ } ) ;
250+
135251 describe ( 'Correctly detects activity as valid/invalid' , function ( ) {
136252
137253 describe ( 'Filters from Configuration' , function ( ) {
0 commit comments