Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve browser support #1019

Merged
merged 1 commit into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/coverartarchive-api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable-next-line */
import {HttpClient} from "./httpClient.js";
import {HttpClient} from "./http-client.js";

export type CovertType = 'Front' | 'Back' | 'Booklet' | 'Medium' | 'Obi' | 'Spine' | 'Track' | 'Tray' | 'Sticker' |
'Poster' | 'Liner' | 'Watermark' | 'Raw/Unedited' | 'Matrix/Runout' | 'Top' | 'Bottom' | 'Other';
Expand Down
2 changes: 1 addition & 1 deletion lib/default.cjs → lib/entry-default.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// CommonJS entry point
"use strict";
module.exports = {
loadMusicBrainzApi: () => import('./index.js'),
loadMusicBrainzApi: () => import('./entry-default.js'),
};
File renamed without changes.
5 changes: 5 additions & 0 deletions lib/entry-node.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Node.js CommonJS entry point
"use strict";
module.exports = {
loadMusicBrainzApi: () => import('./entry-node.js'),
};
2 changes: 2 additions & 0 deletions lib/entry-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './coverartarchive-api.js';
export * from './musicbrainz-api-node.js';
27 changes: 27 additions & 0 deletions lib/http-client-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {type Cookie, CookieJar} from "tough-cookie";
import {HttpClient, type IHttpClientOptions} from "./http-client.js";

export type HttpFormData = { [key: string]: string; }

export class HttpClientNode extends HttpClient {

private cookieJar: CookieJar;

public constructor(options: IHttpClientOptions) {
super(options);
this.cookieJar = new CookieJar();
}

protected registerCookies(response: Response): Promise<Cookie | undefined> {
const cookie = response.headers.get('set-cookie');
if (cookie) {
return this.cookieJar.setCookie(cookie, response.url);
}
return Promise.resolve(undefined);
}

public getCookies(): Promise<string | null> {
return this.cookieJar.getCookieString(this.options.baseUrl); // Get cookies for the request
}

}
28 changes: 11 additions & 17 deletions lib/httpClient.ts → lib/http-client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {CookieJar} from "tough-cookie";
import type {Cookie} from "tough-cookie";

export type HttpFormData = { [key: string]: string; }

Expand All @@ -18,10 +18,7 @@ export interface IFetchOptions {

export class HttpClient {

private cookieJar: CookieJar;

public constructor(private options: IHttpClientOptions) {
this.cookieJar = new CookieJar();
public constructor(protected options: IHttpClientOptions) {
}

public get(path: string, options?: IFetchOptions): Promise<Response> {
Expand Down Expand Up @@ -54,11 +51,11 @@ export class HttpClient {

const cookies = await this.getCookies();

const headers: HeadersInit = {
...options.headers,
'User-Agent': this.options.userAgent,
'Cookie': cookies
};
const headers: HeadersInit = new Headers(options.headers);
headers.set('User-Agent', this.options.userAgent);
if (cookies !== null) {
headers.set('Cookie', cookies);
}

const response = await fetch(url, {
method,
Expand All @@ -71,15 +68,12 @@ export class HttpClient {
return response;
}

private registerCookies(response: Response) {
const cookie = response.headers.get('set-cookie');
if (cookie) {
return this.cookieJar.setCookie(cookie, response.url);
}
protected registerCookies(response: Response): Promise<Cookie | undefined> {
return Promise.resolve(undefined);
}

public getCookies(): Promise<string> {
return this.cookieJar.getCookieString(this.options.baseUrl); // Get cookies for the request
public async getCookies(): Promise<string | null> {
return Promise.resolve(null);
}

}
84 changes: 84 additions & 0 deletions lib/musicbrainz-api-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { StatusCodes as HttpStatus } from 'http-status-codes';
import Debug from 'debug';

export { XmlMetadata } from './xml/xml-metadata.js';
export { XmlIsrc } from './xml/xml-isrc.js';
export { XmlIsrcList } from './xml/xml-isrc-list.js';
export { XmlRecording } from './xml/xml-recording.js';
import {HttpClientNode, type HttpFormData} from "./http-client-node.js";
import {MusicBrainzApi as MusicBrainzApiDefault} from "./musicbrainz-api.js";

export * from './musicbrainz.types.js';
export * from './http-client.js';

/*
* https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2#Subqueries
*/

const debug = Debug('musicbrainz-api-node');

export class MusicBrainzApi extends MusicBrainzApiDefault {

protected initHttpClient(): HttpClientNode {
return new HttpClientNode({
baseUrl: this.config.baseUrl,
timeout: 20 * 1000,
userAgent: `${this.config.appName}/${this.config.appVersion} ( ${this.config.appContactInfo} )`
});
}

public async login(): Promise<boolean> {

if (!this.config.botAccount?.username) throw new Error('bot username should be set');
if (!this.config.botAccount?.password) throw new Error('bot password should be set');

if (this.session?.loggedIn) {
const cookies = await this.httpClient.getCookies() as string;
return cookies.indexOf('musicbrainz_server_session') !== -1;
}
this.session = await this.getSession();

const redirectUri = '/success';

const formData: HttpFormData = {
username: this.config.botAccount.username,
password: this.config.botAccount.password,
csrf_session_key: this.session.csrf.sessionKey,
csrf_token: this.session.csrf.token,
remember_me: '1'
};

const response = await this.httpClient.postForm('login', formData, {
query: {
returnto: redirectUri
},
followRedirects: false
});

const success = response.status === HttpStatus.MOVED_TEMPORARILY && response.headers.get('location') === redirectUri;
if (success) {
this.session.loggedIn = true;
}
return success;
}

/**
* Logout
*/
public async logout(): Promise<boolean> {
const redirectUri = '/success';

const response = await this.httpClient.post('logout', {
followRedirects: false,
query: {
returnto: redirectUri
}
});
const success = response.status === HttpStatus.MOVED_TEMPORARILY && response.headers.get('location') === redirectUri;
if (success && this.session) {
this.session.loggedIn = true;
}
return success;
}

}
76 changes: 13 additions & 63 deletions lib/musicbrainz-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { DigestAuth } from './digest-auth.js';

import { RateLimitThreshold } from 'rate-limit-threshold';
import * as mb from './musicbrainz.types.js';
import {HttpClient, type HttpFormData} from "./httpClient.js";
import {HttpClient} from "./http-client.js";

export * from './musicbrainz.types.js';

Expand Down Expand Up @@ -167,9 +167,9 @@ export class MusicBrainzApi {

public readonly config: IInternalConfig;

private rateLimiter: RateLimitThreshold;
private httpClient: HttpClient;
private session?: ISessionInformation;
protected rateLimiter: RateLimitThreshold;
protected httpClient: HttpClient;
protected session?: ISessionInformation;

public static fetchCsrf(html: string): ICsrfSession {
return {
Expand Down Expand Up @@ -199,13 +199,17 @@ export class MusicBrainzApi {
..._config
}

this.httpClient = new HttpClient({
this.httpClient = this.initHttpClient();

this.rateLimiter = new RateLimitThreshold(15, 18);
}

protected initHttpClient(): HttpClient {
return new HttpClient({
baseUrl: this.config.baseUrl,
timeout: 20 * 1000,
userAgent: `${this.config.appName}/${this.config.appVersion} ( ${this.config.appContactInfo} )`
});

this.rateLimiter = new RateLimitThreshold(15, 18);
}

public async restGet<T>(relUrl: string, query: { [key: string]: string; } = {}): Promise<T> {
Expand Down Expand Up @@ -341,60 +345,6 @@ export class MusicBrainzApi {
} while (n++ < 5);
}

public async login(): Promise<boolean> {

if(!this.config.botAccount?.username) throw new Error('bot username should be set');
if(!this.config.botAccount?.password) throw new Error('bot password should be set');

if (this.session?.loggedIn) {
const cookies = await this.httpClient.getCookies();
return cookies.indexOf('musicbrainz_server_session') !== -1;
}
this.session = await this.getSession();

const redirectUri = '/success';

const formData: HttpFormData = {
username: this.config.botAccount.username,
password: this.config.botAccount.password,
csrf_session_key: this.session.csrf.sessionKey,
csrf_token: this.session.csrf.token,
remember_me: '1'
};

const response = await this.httpClient.postForm('login', formData, {
query: {
returnto: redirectUri
},
followRedirects: false
});

const success = response.status === HttpStatus.MOVED_TEMPORARILY && response.headers.get('location') === redirectUri;
if (success) {
this.session.loggedIn = true;
}
return success;
}

/**
* Logout
*/
public async logout(): Promise<boolean> {
const redirectUri = '/success';

const response = await this.httpClient.post('logout', {
followRedirects: false,
query: {
returnto: redirectUri
}
});
const success = response.status === HttpStatus.MOVED_TEMPORARILY && response.headers.get('location') === redirectUri;
if (success && this.session) {
this.session.loggedIn = true;
}
return success;
}

/**
* Submit entity
* @param entity Entity type e.g. 'recording'
Expand Down Expand Up @@ -498,7 +448,7 @@ export class MusicBrainzApi {
}, editNote);
}

private async getSession(): Promise<ISessionInformation> {
protected async getSession(): Promise<ISessionInformation> {

const response = await this.httpClient.get('login', {
followRedirects: false
Expand All @@ -508,7 +458,7 @@ export class MusicBrainzApi {
};
}

private async applyRateLimiter() {
protected async applyRateLimiter() {
if (!this.config.disableRateLimiting) {
const delay = await this.rateLimiter.limit();
debug(`Client side rate limiter activated: cool down for ${Math.round(delay / 100)/10} s...`);
Expand Down
13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@
"version": "0.19.1",
"description": "MusicBrainz API client for reading and submitting metadata",
"exports": {
"import": "./lib/index.js",
"require": "./lib/default.cjs"
"node": {
"import": "./lib/entry-node.js",
"require": "./lib/entry-node.cjs"
},
"default": {
"import": "./lib/entry-default.js",
"require": "./lib/entry-default.cjs"
}
},
"types": "lib/index.d.ts",
"files": [
"lib/**/*.js",
"lib/**/*.d.ts",
"lib/default.cjs"
"lib/entry-node.cjs",
"lib/entry-default.cjs"
],
"type": "module",
"author": {
Expand Down
Loading
Loading