Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/FoxxMD/multi-scrobbler. Scrobble plays from multiple sources to multiple clients docs.multi-scrobbler.app
deezer docker jellyfin koito lastfm listenbrainz maloja mopidy mpris music music-assistant plex scrobble self-hosted spotify subsonic tautulli youtube-music
Something went wrong. Try again.
8.8 kB · 255 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255import { parseRegexSingle } from "@foxxmd/regex-buddy-core";import mergeErrorCause from 'merge-error-cause';import { findCauseByFunc, isAbortReasonErrorLike } from "../../utils/ErrorUtils.ts";import { UpstreamError, type UpstreamErrorOptions } from "./UpstreamError.ts";import {addKnownErrorConstructor, serializeError} from 'serialize-error';import type {LifecycleInput} from "../../../core/Atomic.ts";
export abstract class NamedError extends Error { public abstract name: string; public toJSON() { return serializeError(this); }}
export abstract class StageError extends NamedError {}
export class BuildDataError extends StageError { name = 'Init Build Data';}addKnownErrorConstructor(BuildDataError);
export class ParseCacheError extends StageError { name = 'Init Parse Cache';}addKnownErrorConstructor(ParseCacheError);
export class TransformRulesError extends StageError { name = 'Transform Rules';}addKnownErrorConstructor(TransformRulesError);export class ConnectionCheckError extends StageError { name = 'Connection Check';}addKnownErrorConstructor(ConnectionCheckError);
export class AuthError extends NamedError { override name = 'Authentication Error'; unrecoverable: boolean = false;
public constructor(msg?: string, opts: ErrorOptions & {unrecoverable?: boolean} = {}) { super(msg, opts); this.unrecoverable = opts.unrecoverable ?? false; }}addKnownErrorConstructor(AuthError);export const isAuthError = (e: Error): e is AuthError => e.name === 'Authentication Error' || e instanceof AuthError;export class AuthCheckError extends StageError { override name = 'Authentication Check'; unrecoverable?: boolean;
public constructor(msg?: string, opts: ErrorOptions & {unrecoverable?: boolean} = {}) { super(msg, opts); this.unrecoverable = opts.unrecoverable; }}addKnownErrorConstructor(AuthCheckError);export const isAuthCheckError = (e: Error): e is AuthCheckError => e.name === 'Authentication Check' || e instanceof AuthCheckError;
export class PostInitError extends StageError { name = 'Post Initialization';}addKnownErrorConstructor(PostInitError);
export class StageChangeError extends NamedError { override name = 'Stage Change';}
const STACK_AT_REGEX = new RegExp(/[\n\r]\s*at/);
export class SimpleError extends Error implements HasSimpleError { simple: boolean; override name = 'SimpleError'; public toJSON() { return serializeError(this); }
stackShortened: boolean = false;
shortenStack() { if(this.stack !== undefined) { const atIndex = parseRegexSingle(STACK_AT_REGEX, this.stack); if(atIndex !== undefined) { const firstn = this.stack.indexOf('\n', atIndex.index + atIndex.match.length); if(firstn !== -1) { this.stack = this.stack.slice(0, firstn); this.stackShortened = true; } } } }
public constructor(msg: string, options?: ErrorOptions & { simple?: boolean, shortStack?: boolean }) { super(msg, options); const { simple = true, shortStack = false } = options || {}; this.simple = simple; Error.captureStackTrace(this, this.constructor); if(shortStack) { this.shortenStack(); } }}addKnownErrorConstructor(SimpleError, () => new SimpleError(''))
export class StageTransformError extends NamedError { override name = 'Stage Transform'; lifecycleInputs?: LifecycleInput[] stageName: string; constructor(name: string, message: string, options: ErrorOptions & {inputs?: LifecycleInput[]} = {}) { const { inputs, ...restOpts } = options; super(message, restOpts); this.lifecycleInputs = inputs; this.stageName = name; }}addKnownErrorConstructor(StageTransformError, () => new StageTransformError('',''))
export class SkipTransformStageError extends SimpleError { lifecycleInputs?: LifecycleInput[] override name = 'Skip Transform Stage';
public constructor(msg: string, options: ErrorOptions & { simple?: boolean, shortStack?: boolean, inputs?: LifecycleInput[]} = {}) { const { inputs, ...restOpts } = options; super(msg, restOpts); this.lifecycleInputs = inputs; }}addKnownErrorConstructor(SkipTransformStageError, () => new SkipTransformStageError(''))
export class StagePrerequisiteError extends SimpleError { lifecycleInputs?: LifecycleInput[] override name = 'Stage Prerequisite'; public constructor(msg: string, options: ErrorOptions & { simple?: boolean, shortStack?: boolean, inputs?: LifecycleInput[]} = {}) { const { inputs, ...restOpts } = options; super(msg, restOpts); this.lifecycleInputs = inputs; }}addKnownErrorConstructor(StagePrerequisiteError, () => new StagePrerequisiteError(''))
export interface HasSimpleError extends Error { simple: boolean}
export const isSimpleError = (e: unknown): e is HasSimpleError => { if(!(e instanceof Error)) { return false; } return 'simple' in e;}
export const mergeSimpleError = (err: Error): Error => { const anySimple = findCauseByFunc<SimpleError>(err, (e) => isSimpleError(e)); if(anySimple && anySimple.simple) { // mergeErrorCause mutates the argument // and we want to be able to do more cause/error parsing after merging for logging // so give it a copy instead of the original const mergedErr = mergeErrorCause(structuredClone(err)); if(!('toJson' in mergedErr)) { // @ts-expect-error this is used by serialize error with out custom errors mergedErr.toJSON = () => serializeError(mergedErr); } return mergedErr; } return err;}
export class ScrobbleSubmitError<T extends (object | string) = object> extends UpstreamError { override name = 'Scrobble Submit Error'; payload?: T; constructor(message: string, options?: UpstreamErrorOptions & {payload?: T}) { super(message, options); this.payload = options?.payload; }}addKnownErrorConstructor(ScrobbleSubmitError, () => new ScrobbleSubmitError(''))
export class AbortedError extends SimpleError { override name = 'Aborted Operation';}addKnownErrorConstructor(AbortedError, () => new AbortedError(''));
export const generateLoggableAbortReason = (msg: string, signal: AbortSignal): AbortedError => { const reason = signal.reason; let err: AbortedError; if(isAbortReasonErrorLike(signal)) { err = new AbortedError(msg, {cause: reason}); } else { err = new AbortedError(`${msg} => ${reason ?? 'No Reason Given'}`, {simple: true, shortStack: true}); } Error.captureStackTrace(err, generateLoggableAbortReason); return err;}
export class InvalidRegexError extends SimpleError { override name = 'Invalid Regex Error'; constructor(regex: RegExp | RegExp[], val?: string, url?: string, message?: string) { const msgParts = [ message ?? 'Regex(es) did not match the value given.', ]; const regArr = Array.isArray(regex) ? regex : [regex]; for(const r of regArr) { msgParts.push(`Regex: ${r}`) } if (val !== undefined) { msgParts.push(`Value: ${val}`); } if (url !== undefined) { msgParts.push(`Sample regex: ${url}`); } super(msgParts.join('\r\n')); }}addKnownErrorConstructor(InvalidRegexError, () => new InvalidRegexError(new RegExp(/1/)));
export type AuthErrorMap = { root: AuthError check: AuthCheckError auth: AuthError | AuthCheckError parent: Error}
export const findAuthIssue = <T extends keyof AuthErrorMap = 'auth'>(err: Error, opts: { type?: T, unrecoverable?: boolean } = {}): AuthErrorMap[T] | undefined => { const { type = 'auth', unrecoverable } = opts; const authError = findCauseByFunc(err, (e) => { if (['check','auth'].includes(type) && isAuthCheckError(e)) { return unrecoverable === undefined || e.unrecoverable === unrecoverable; } if (['root','auth'].includes(type) && isAuthError(e)) { return unrecoverable === undefined || e.unrecoverable === unrecoverable; } return false; }); if (authError !== undefined) { if (type === 'parent') { return err as AuthErrorMap[T]; } return authError as AuthErrorMap[T]; } return undefined;}