diff --git a/src/http.ts b/src/http.ts index 0dd6154..8464492 100644 --- a/src/http.ts +++ b/src/http.ts @@ -5,11 +5,12 @@ import { ResponseType, } from "axios"; +// todo: do i need this anymore? export interface Http { (config: AxiosRequestConfig): AxiosPromise; } export interface Http2 extends Http { - with: (defaults: Partial) => Http2; + with: (params: Partial) => Http2; } export type RequestParams = { @@ -21,9 +22,9 @@ export type RequestParams = { method: Method; }; -const wrap = (http2: Http2, defaults: Partial): Http2 => { - const f = ((config: AxiosRequestConfig) => http2(merge(defaults, config))) as Http2; - f.with = (defaults: Partial) => wrap(f, defaults); +const wrap = (http2: Http2, params: Partial): Http2 => { + const f = ((config: AxiosRequestConfig) => http2(merge(params, config))) as Http2; + f.with = (params: Partial) => wrap(f, params); return f; }; diff --git a/src/subsonic/library.ts b/src/subsonic/generic.ts similarity index 91% rename from src/subsonic/library.ts rename to src/subsonic/generic.ts index 8a929bc..a1f2d2e 100644 --- a/src/subsonic/library.ts +++ b/src/subsonic/generic.ts @@ -768,67 +768,3 @@ export class SubsonicGenericMusicLibrary implements SubsonicMusicLibrary { total: albums.length == 500 ? total : (q._index || 0) + albums.length, })); } - -export const navidromeMusicLibrary = ( - url: string, - subsonicLibrary: SubsonicMusicLibrary, - subsonicCredentials: SubsonicCredentials -): SubsonicMusicLibrary => ({ - ...subsonicLibrary, - flavour: () => "navidrome", - bearerToken: ( - credentials: Credentials - ): TE.TaskEither => - pipe( - TE.tryCatch( - () => - // todo: not hardcode axios in here - axios({ - method: "post", - baseURL: url, - url: `/auth/login`, - data: _.pick(credentials, "username", "password"), - }), - () => new AuthFailure("Failed to get bearerToken") - ), - TE.map((it) => it.data.token as string | undefined) - ), - artists: async ( - q: ArtistQuery - ): Promise> => { - let params: any = { - _sort: "name", - _order: "ASC", - _start: q._index || "0", - }; - if (q._count) { - params = { - ...params, - _end: (q._index || 0) + q._count, - }; - } - - const x: Promise> = axios - .get(`${url}/api/artist`, { - params: asURLSearchParams(params), - headers: { - "User-Agent": USER_AGENT, - "x-nd-authorization": `Bearer ${subsonicCredentials.bearer}`, - }, - }) - .catch((e) => { - throw `Navidrome failed with: ${e}`; - }) - .then((response) => { - if (response.status != 200 && response.status != 206) { - throw `Navidrome failed with a ${response.status || "no!"} status`; - } else return response; - }) - .then((it) => ({ - results: (it.data as NDArtist[]).map(artistSummaryFromNDArtist), - total: Number.parseInt(it.headers["x-total-count"] || "0"), - })); - - return x; - }, -}); diff --git a/src/subsonic/index.ts b/src/subsonic/index.ts index 729e4b7..c5ffd5a 100644 --- a/src/subsonic/index.ts +++ b/src/subsonic/index.ts @@ -16,7 +16,7 @@ import { } from "../music_service"; import { b64Encode, b64Decode } from "../b64"; import { axiosImageFetcher, ImageFetcher } from "../images"; -import { navidromeMusicLibrary, SubsonicGenericMusicLibrary } from "./library"; +import { navidromeMusicLibrary, SubsonicGenericMusicLibrary } from "./generic"; import { client } from "./subsonic_http"; export const t = (password: string, s: string) => @@ -101,7 +101,7 @@ export class Subsonic implements MusicService { // todo: why is this in here? externalImageFetcher: ImageFetcher; - subsonic: Http2; + subsonicHttp: Http2; constructor( url: string, @@ -111,7 +111,7 @@ export class Subsonic implements MusicService { this.url = url; this.streamClientApplication = streamClientApplication; this.externalImageFetcher = externalImageFetcher; - this.subsonic = http2From(axios).with({ + this.subsonicHttp = http2From(axios).with({ baseURL: this.url, params: { v: "1.16.1", c: DEFAULT_CLIENT_APPLICATION }, headers: { "User-Agent": "bonob" }, @@ -126,7 +126,7 @@ export class Subsonic implements MusicService { generateToken = (credentials: Credentials) => pipe( TE.tryCatch( - () => client(this.subsonic.with({ params: this.asAuthParams(credentials) } ))({ method: 'get', url: "/rest/ping.view" }).asJSON(), + () => client(this.subsonicHttp.with({ params: this.asAuthParams(credentials) } ))({ method: 'get', url: "/rest/ping.view" }).asJSON(), (e) => new AuthFailure(e as string) ), TE.chain(({ type }) => @@ -161,7 +161,7 @@ export class Subsonic implements MusicService { ): Promise => { const subsonicGenericLibrary = new SubsonicGenericMusicLibrary( this.streamClientApplication, - this.subsonic.with({ params: this.asAuthParams(credentials) } ) + this.subsonicHttp.with({ params: this.asAuthParams(credentials) } ) ); if (credentials.type == "navidrome") { return Promise.resolve( diff --git a/src/subsonic/navidrome.ts b/src/subsonic/navidrome.ts index 6f4c75b..c7b104e 100644 --- a/src/subsonic/navidrome.ts +++ b/src/subsonic/navidrome.ts @@ -1,5 +1,14 @@ -import { ArtistSummary, Sortable } from "../music_service"; -import { artistImageURN } from "./library"; +import { option as O, taskEither as TE } from "fp-ts"; +import * as A from "fp-ts/Array"; +import { pipe } from "fp-ts/lib/function"; +import { ordString } from "fp-ts/lib/Ord"; +import { inject } from "underscore"; +import _ from "underscore"; +import axios from "axios"; + +import { SubsonicCredentials, SubsonicMusicLibrary } from "."; +import { ArtistQuery, ArtistSummary, AuthFailure, Credentials, Result, Sortable } from "../music_service"; +import { artistImageURN } from "./generic"; export type NDArtist = { id: string; @@ -20,3 +29,67 @@ export const artistSummaryFromNDArtist = ( }), }); + +export const navidromeMusicLibrary = ( + url: string, + subsonicLibrary: SubsonicMusicLibrary, + subsonicCredentials: SubsonicCredentials +): SubsonicMusicLibrary => ({ + ...subsonicLibrary, + flavour: () => "navidrome", + bearerToken: ( + credentials: Credentials + ): TE.TaskEither => + pipe( + TE.tryCatch( + () => + // todo: not hardcode axios in here + axios({ + method: "post", + baseURL: url, + url: `/auth/login`, + data: _.pick(credentials, "username", "password"), + }), + () => new AuthFailure("Failed to get bearerToken") + ), + TE.map((it) => it.data.token as string | undefined) + ), + artists: async ( + q: ArtistQuery + ): Promise> => { + let params: any = { + _sort: "name", + _order: "ASC", + _start: q._index || "0", + }; + if (q._count) { + params = { + ...params, + _end: (q._index || 0) + q._count, + }; + } + + const x: Promise> = axios + .get(`${url}/api/artist`, { + params: asURLSearchParams(params), + headers: { + "User-Agent": USER_AGENT, + "x-nd-authorization": `Bearer ${subsonicCredentials.bearer}`, + }, + }) + .catch((e) => { + throw `Navidrome failed with: ${e}`; + }) + .then((response) => { + if (response.status != 200 && response.status != 206) { + throw `Navidrome failed with a ${response.status || "no!"} status`; + } else return response; + }) + .then((it) => ({ + results: (it.data as NDArtist[]).map(artistSummaryFromNDArtist), + total: Number.parseInt(it.headers["x-total-count"] || "0"), + })); + + return x; + }, +}); diff --git a/tests/builders.ts b/tests/builders.ts index 37e2d6a..d3695de 100644 --- a/tests/builders.ts +++ b/tests/builders.ts @@ -17,7 +17,7 @@ import { } from "../src/music_service"; import { b64Encode } from "../src/b64"; -import { artistImageURN } from "../src/subsonic/library"; +import { artistImageURN } from "../src/subsonic/generic"; const randomInt = (max: number) => Math.floor(Math.random() * Math.floor(max)); const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`; diff --git a/tests/server.test.ts b/tests/server.test.ts index 27dd4e7..9f38827 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -167,7 +167,7 @@ describe("RangeBytesFromFilter", () => { describe("server", () => { - jest.setTimeout(Number.parseInt(process.env["JEST_TIMEOUT"] || "2000")); + jest.setTimeout(Number.parseInt(process.env["JEST_TIMEOUT"] || "5000")); beforeEach(() => { jest.clearAllMocks(); diff --git a/tests/smapi.test.ts b/tests/smapi.test.ts index 8111c61..4ccfb60 100644 --- a/tests/smapi.test.ts +++ b/tests/smapi.test.ts @@ -91,7 +91,7 @@ describe("rating to and from ints", () => { }); describe("service config", () => { - jest.setTimeout(Number.parseInt(process.env["JEST_TIMEOUT"] || "2000")); + jest.setTimeout(Number.parseInt(process.env["JEST_TIMEOUT"] || "5000")); const bonobWithNoContextPath = url("http://localhost:1234"); const bonobWithContextPath = url("http://localhost:5678/some-context-path"); diff --git a/tests/subsonic/generic.test.ts b/tests/subsonic/generic.test.ts index 0cc9dd9..0fbb936 100644 --- a/tests/subsonic/generic.test.ts +++ b/tests/subsonic/generic.test.ts @@ -42,7 +42,7 @@ import { isValidImage, song, SubsonicGenericMusicLibrary, -} from "../../src/subsonic/library"; +} from "../../src/subsonic/generic"; import { EMPTY, error, FAILURE, subsonicOK, ok } from "../subsonic.test"; import { DODGY_IMAGE_NAME, t } from "../../src/subsonic"; import { b64Encode } from "../../src/b64"; diff --git a/tests/subsonic/navidrome.test.ts b/tests/subsonic/navidrome.test.ts index 6aca01c..f94d685 100644 --- a/tests/subsonic/navidrome.test.ts +++ b/tests/subsonic/navidrome.test.ts @@ -1,6 +1,6 @@ import { v4 as uuid } from "uuid"; import { DODGY_IMAGE_NAME } from "../../src/subsonic"; -import { artistImageURN } from "../../src/subsonic/library"; +import { artistImageURN } from "../../src/subsonic/generic"; import { artistSummaryFromNDArtist } from "../../src/subsonic/navidrome";