[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
0

Configure Feed

Select the types of activity you want to include in your feed.

refactor: Reduce usage of ErrorWithCause

Error Cause (https://github.com/tc39/proposal-error-cause) was finalized and introduced in ES2022 so we don't need to ponyfill this anymore. Still using the helpful helper functions thought.

FoxxMD (Mar 28, 2024, 9:20 AM EDT) 7e6eddce 41900b00

+74 -99
+2 -3
src/backend/index.ts
··· 17 17 import {SimpleIntervalJob, ToadScheduler} from "toad-scheduler"; 18 18 import { createHeartbeatSourcesTask } from "./tasks/heartbeatSources.js"; 19 19 import { createHeartbeatClientsTask } from "./tasks/heartbeatClients.js"; 20 - import {ErrorWithCause} from "pony-cause"; 21 20 import {loggerDebug, childLogger, LogData, Logger as FoxLogger} from '@foxxmd/logging'; 22 21 23 22 dayjs.extend(utc) ··· 42 41 let logger: FoxLogger; 43 42 44 43 process.on('uncaughtExceptionMonitor', (err, origin) => { 45 - const appError = new ErrorWithCause(`Uncaught exception is crashing the app! :( Type: ${origin}`, {cause: err}); 44 + const appError = new Error(`Uncaught exception is crashing the app! :( Type: ${origin}`, {cause: err}); 46 45 if(logger !== undefined) { 47 46 logger.error(appError) 48 47 } else { ··· 149 148 logger.info('Scheduler started.'); 150 149 151 150 } catch (e) { 152 - const appError = new ErrorWithCause('Exited with uncaught error', {cause: e}); 151 + const appError = new Error('Exited with uncaught error', {cause: e}); 153 152 if(logger !== undefined) { 154 153 logger.error(appError); 155 154 } else {
+7 -7
src/backend/utils.ts
··· 21 21 } from "./common/infrastructure/Atomic.js"; 22 22 import {Request} from "express"; 23 23 import pathUtil from "path"; 24 - import {ErrorWithCause, getErrorCause} from "pony-cause"; 24 + import {getErrorCause} from "pony-cause"; 25 25 import backoffStrategies from '@kenyip/backoff-strategies'; 26 26 import {replaceResultTransformer, stripIndentTransformer, TemplateTag, trimResultTransformer} from 'common-tags'; 27 27 import {Duration} from "dayjs/plugin/duration.js"; ··· 40 40 const {code} = e; 41 41 if (code === 'ENOENT') { 42 42 if (throwOnNotFound) { 43 - throw new ErrorWithCause(`No file found at given path: ${path}`, {cause: e}); 43 + throw new Error(`No file found at given path: ${path}`, {cause: e}); 44 44 } else { 45 45 return; 46 46 } 47 47 } 48 - throw new ErrorWithCause(`Encountered error while parsing file: ${path}`, {cause: e}) 48 + throw new Error(`Encountered error while parsing file: ${path}`, {cause: e}) 49 49 } 50 50 } 51 51 ··· 536 536 // also can't access directory :( 537 537 throw new Error(`No ${isDir ? 'directory' : 'file'} exists at ${location} and application does not have permission to write to the parent directory`); 538 538 } else { 539 - throw new ErrorWithCause(`No ${isDir ? 'directory' : 'file'} exists at ${location} and application is unable to access the parent directory due to a system error`, {cause: accessError}); 539 + throw new Error(`No ${isDir ? 'directory' : 'file'} exists at ${location} and application is unable to access the parent directory due to a system error`, {cause: accessError}); 540 540 } 541 541 } 542 542 } else if(code === 'EACCES') { 543 543 throw new Error(`${isDir ? 'Directory' : 'File'} exists at ${location} but application does not have permission to write to it.`); 544 544 } else { 545 - throw new ErrorWithCause(`${isDir ? 'Directory' : 'File'} exists at ${location} but application is unable to access it due to a system error`, {cause: err}); 545 + throw new Error(`${isDir ? 'Directory' : 'File'} exists at ${location} but application is unable to access it due to a system error`, {cause: err}); 546 546 } 547 547 } 548 548 } ··· 600 600 const results = parseRegex(reg, val); 601 601 if (results !== undefined) { 602 602 if (results.length > 1) { 603 - throw new ErrorWithCause(`Expected Regex to match once but got ${results.length} results. Either Regex must NOT be global (using 'g' flag) or parsed value must only match regex once. Given: ${val} || Regex: ${reg.toString()}`); 603 + throw new Error(`Expected Regex to match once but got ${results.length} results. Either Regex must NOT be global (using 'g' flag) or parsed value must only match regex once. Given: ${val} || Regex: ${reg.toString()}`); 604 604 } 605 605 return results[0]; 606 606 } ··· 733 733 } catch (e) { 734 734 if (process.env.DEBUG_MODE === 'true') { 735 735 if (logger !== undefined) { 736 - logger.warn(new ErrorWithCause('Could not get machine IP address', {cause: e})); 736 + logger.warn(new Error('Could not get machine IP address', {cause: e})); 737 737 } else { 738 738 console.warn('Could not get machine IP address'); 739 739 console.warn(e);
+5 -5
src/backend/scrobblers/AbstractScrobbleClient.ts
··· 41 41 import { compareScrobbleArtists, compareScrobbleTracks, normalizeStr } from "../utils/StringUtils.js"; 42 42 import { hasUpstreamError, UpstreamError } from "../common/errors/UpstreamError.js"; 43 43 import {nanoid} from "nanoid"; 44 - import {ErrorWithCause, messageWithCauses} from "pony-cause"; 44 + import {messageWithCauses} from "pony-cause"; 45 45 import { hasNodeNetworkException } from "../common/errors/NodeErrors.js"; 46 46 import { 47 47 comparePlayTemporally, ··· 187 187 // only signal as auth failure if error was NOT either a node network error or a non-showstopping upstream error 188 188 this.authFailure = !(hasNodeNetworkException(e) || hasUpstreamError(e, false)); 189 189 this.authed = false; 190 - this.logger.error(new ErrorWithCause(`Authentication test failed!${this.authFailure === false ? ' Due to a network issue. Will retry authentication on next heartbeat.' : ''}`, {cause: e})); 190 + this.logger.error(new Error(`Authentication test failed!${this.authFailure === false ? ' Due to a network issue. Will retry authentication on next heartbeat.' : ''}`, {cause: e})); 191 191 } 192 192 } 193 193 ··· 583 583 } catch (e) { 584 584 if (e instanceof UpstreamError && e.showStopper === false) { 585 585 this.addDeadLetterScrobble(currQueuedPlay, e); 586 - this.logger.warn(new ErrorWithCause(`Could not scrobble ${buildTrackString(currQueuedPlay.play)} from Source '${currQueuedPlay.source}' but error was not show stopping. Adding scrobble to Dead Letter Queue and will retry on next heartbeat.`, {cause: e})); 586 + this.logger.warn(new Error(`Could not scrobble ${buildTrackString(currQueuedPlay.play)} from Source '${currQueuedPlay.source}' but error was not show stopping. Adding scrobble to Dead Letter Queue and will retry on next heartbeat.`, {cause: e})); 587 587 } else { 588 588 this.queuedScrobbles.unshift(currQueuedPlay); 589 - throw new ErrorWithCause('Error occurred while trying to scrobble', {cause: e}); 589 + throw new Error('Error occurred while trying to scrobble', {cause: e}); 590 590 } 591 591 } 592 592 } else if (!timeFrameValid) { ··· 666 666 deadScrobble.retries++; 667 667 deadScrobble.error = messageWithCauses(e); 668 668 deadScrobble.lastRetry = dayjs(); 669 - this.logger.error(new ErrorWithCause(`Could not scrobble ${buildTrackString(deadScrobble.play)} from Source '${deadScrobble.source}' due to error`, {cause: e})); 669 + this.logger.error(new Error(`Could not scrobble ${buildTrackString(deadScrobble.play)} from Source '${deadScrobble.source}' due to error`, {cause: e})); 670 670 this.deadLetterScrobbles[deadScrobbleIndex] = deadScrobble; 671 671 return [false, deadScrobble]; 672 672 } finally {
+1 -2
src/backend/scrobblers/LastfmScrobbler.ts
··· 20 20 import { UpstreamError } from "../common/errors/UpstreamError.js"; 21 21 import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 22 22 import { getScrobbleTsSOCDate } from "../utils/TimeUtils.js"; 23 - import {ErrorWithCause} from "pony-cause"; 24 23 25 24 export default class LastfmScrobbler extends AbstractScrobbleClient { 26 25 ··· 47 46 this.logger.info('Initialized'); 48 47 } catch (e) { 49 48 this.initialized = false; 50 - this.logger.warn(new ErrorWithCause('Initialization failed', {cause: e})); 49 + this.logger.warn(new Error('Initialization failed', {cause: e})); 51 50 } 52 51 53 52 return this.initialized;
+1 -2
src/backend/scrobblers/ListenbrainzScrobbler.ts
··· 11 11 import EventEmitter from "events"; 12 12 import { UpstreamError } from "../common/errors/UpstreamError.js"; 13 13 import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 14 - import {ErrorWithCause} from "pony-cause"; 15 14 16 15 export default class ListenbrainzScrobbler extends AbstractScrobbleClient { 17 16 ··· 40 39 this.initialized = true; 41 40 this.logger.info('Initialized'); 42 41 } catch (e) { 43 - this.logger.warn(new ErrorWithCause('Could not initialize', {cause: e})); 42 + this.logger.warn(new Error('Could not initialize', {cause: e})); 44 43 this.initialized = false; 45 44 } 46 45 }
+5 -7
src/backend/scrobblers/MalojaScrobbler.ts
··· 24 24 import EventEmitter from "events"; 25 25 import normalizeUrl from "normalize-url"; 26 26 import { UpstreamError } from "../common/errors/UpstreamError.js"; 27 - import {ErrorWithCause} from "pony-cause"; 28 27 import { getScrobbleTsSOCDate, getScrobbleTsSOCDateWithContext } from "../utils/TimeUtils.js"; 29 - import e from "express"; 30 28 import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 31 29 import { isSuperAgentResponseError } from "../common/errors/ErrorUtils.js"; 32 30 ··· 170 168 throw new UpstreamError(`API Call failed (HTTP ${status}) => ${message}`, {cause: e}) 171 169 } 172 170 } else { 173 - throw new ErrorWithCause('Unexpected error occurred during API call', {cause : e}); 171 + throw new Error('Unexpected error occurred during API call', {cause : e}); 174 172 } 175 173 } 176 174 } ··· 208 206 } 209 207 return true; 210 208 } catch (e) { 211 - this.logger.error(new ErrorWithCause('Communication test failed', {cause: e})); 209 + this.logger.error(new Error('Communication test failed', {cause: e})); 212 210 return false; 213 211 } 214 212 } ··· 244 242 245 243 return [true]; 246 244 } catch (e) { 247 - this.logger.error(new ErrorWithCause('Unexpected error encountered while testing server health', {cause: e})); 245 + this.logger.error(new Error('Unexpected error encountered while testing server health', {cause: e})); 248 246 throw e; 249 247 } 250 248 } ··· 293 291 } 294 292 } catch (e) { 295 293 if(e instanceof UpstreamError) { 296 - if(e.cause.status === 403) { 294 + if((e?.cause as any)?.status === 403) { 297 295 // may be an older version that doesn't support auth readiness before db upgrade 298 296 // and if it was before api was accessible during db build then test would fail during testConnection() 299 297 if(compareVersions(this.serverVersion, '2.12.19') < 0) { ··· 322 320 this.serverIsHealthy = true; 323 321 } 324 322 } catch (e) { 325 - this.logger.error(new ErrorWithCause(`Testing server health failed due to an unexpected error`, {cause: e})); 323 + this.logger.error(new Error(`Testing server health failed due to an unexpected error`, {cause: e})); 326 324 this.serverIsHealthy = false; 327 325 } 328 326 return this.serverIsHealthy
+3 -4
src/backend/server/index.ts
··· 8 8 import { setupApi } from "./api.js"; 9 9 import { getAddress, mergeArr, parseBool } from "../utils.js"; 10 10 import {stripIndents} from "common-tags"; 11 - import {ErrorWithCause} from "pony-cause"; 12 11 import {childLogger, LogData, LogDataPretty} from "@foxxmd/logging"; 13 12 import {PassThrough} from "node:stream"; 14 13 import {Logger} from '@foxxmd/logging'; ··· 85 84 logger.info(`User-defined base URL for UI and redirect URLs (spotify, deezer, lastfm): ${local}`) 86 85 } 87 86 }).on('error', (err) => { 88 - throw new ErrorWithCause('Server encountered unrecoverable error', {cause: err}); 87 + throw new Error('Server encountered unrecoverable error', {cause: err}); 89 88 }); 90 89 } catch (e) { 91 - throw new ErrorWithCause('Server encountered unrecoverable error', {cause: e}); 90 + throw new Error('Server encountered unrecoverable error', {cause: e}); 92 91 } 93 92 94 93 } catch (e) { 95 - throw new ErrorWithCause('Server crashed with uncaught exception', {cause: e}); 94 + throw new Error('Server crashed with uncaught exception', {cause: e}); 96 95 } 97 96 }
+6 -7
src/backend/sources/AbstractSource.ts
··· 36 36 import { PlayObject, TA_CLOSE } from "../../core/Atomic.js"; 37 37 import { buildTrackString, capitalize } from "../../core/StringUtils.js"; 38 38 import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 39 - import {ErrorWithCause} from "pony-cause"; 40 39 import { comparePlayTemporally, temporalAccuracyIsAtLeast } from "../utils/TimeUtils.js"; 41 40 42 41 export interface RecentlyPlayedOptions { ··· 111 110 this.logger.info('Fully Initialized!'); 112 111 return true; 113 112 } catch(e) { 114 - this.logger.error(new ErrorWithCause('Initialization failed', {cause: e})); 113 + this.logger.error(new Error('Initialization failed', {cause: e})); 115 114 return false; 116 115 } 117 116 } ··· 135 134 this.buildOK = true; 136 135 } catch (e) { 137 136 this.buildOK = false; 138 - throw new ErrorWithCause('Building required data for initialization failed', {cause: e}); 137 + throw new Error('Building required data for initialization failed', {cause: e}); 139 138 } 140 139 } 141 140 ··· 166 165 this.connectionOK = true; 167 166 } catch (e) { 168 167 this.connectionOK = false; 169 - throw new ErrorWithCause('Communicating with upstream service failed', {cause: e}); 168 + throw new Error('Communicating with upstream service failed', {cause: e}); 170 169 } 171 170 } 172 171 ··· 202 201 // only signal as auth failure if error was NOT a node network error 203 202 this.authFailure = findCauseByFunc(e, isNodeNetworkException) === undefined; 204 203 this.authed = false; 205 - throw new ErrorWithCause(`Authentication test failed!${this.authFailure === false ? ' Due to a network issue. Will retry authentication on next heartbeat.' : ''}`, {cause: e}) 204 + throw new Error(`Authentication test failed!${this.authFailure === false ? ' Due to a network issue. Will retry authentication on next heartbeat.' : ''}`, {cause: e}) 206 205 } 207 206 } 208 207 ··· 322 321 try { 323 322 backlogPlays = await this.getBackloggedPlays(); 324 323 } catch (e) { 325 - throw new ErrorWithCause('Error occurred while fetching backlogged plays', {cause: e}); 324 + throw new Error('Error occurred while fetching backlogged plays', {cause: e}); 326 325 } 327 326 const discovered = this.discover(backlogPlays); 328 327 ··· 385 384 try { 386 385 await this.processBacklog(); 387 386 } catch (e) { 388 - this.logger.error(new ErrorWithCause('Cannot start polling because error occurred while processing backlog', {cause: e})); 387 + this.logger.error(new Error('Cannot start polling because error occurred while processing backlog', {cause: e})); 389 388 this.notify({ 390 389 title: `${this.identifier} - Polling Error`, 391 390 message: 'Cannot start polling because error occurred while processing backlog.',
+15 -15
src/backend/sources/ChromecastSource.ts
··· 11 11 import {EventEmitter} from "events"; 12 12 import {MediaController, PersistentClient, Media, createPlatform} from "@foxxmd/chromecast-client"; 13 13 import {Client as CastClient} from 'castv2'; 14 - import {ErrorWithCause, findCauseByReference} from "pony-cause"; 14 + import {findCauseByReference} from "pony-cause"; 15 15 import { PlayObject } from "../../core/Atomic.js"; 16 16 import dayjs from "dayjs"; 17 17 import { RecentlyPlayedOptions } from "./AbstractSource.js"; ··· 140 140 141 141 for (const device of devices) { 142 142 this.initializeDevice({name: device.name, addresses: [device.address], type: 'googlecast'}).catch((err) => { 143 - this.logger.error(new ErrorWithCause('Uncaught error occurred while connecting to manually configured device', {cause: err})); 143 + this.logger.error(new Error('Uncaught error occurred while connecting to manually configured device', {cause: err})); 144 144 }); 145 145 } 146 146 147 147 if (useAutoDiscovery) { 148 148 if (useAvahi) { 149 149 this.discoverAvahi(initial).catch((err) => { 150 - this.logger.error(new ErrorWithCause('Uncaught error occurred during mDNS discovery via Avahi', {cause: err})); 150 + this.logger.error(new Error('Uncaught error occurred during mDNS discovery via Avahi', {cause: err})); 151 151 }); 152 152 } else { 153 153 this.discoverNative(initial).catch((err) => { 154 - this.logger.error(new ErrorWithCause('Uncaught error occurred during mDNS discovery', {cause: err})); 154 + this.logger.error(new Error('Uncaught error occurred during mDNS discovery', {cause: err})); 155 155 }); 156 156 } 157 157 } ··· 167 167 }, 168 168 }); 169 169 } catch (e) { 170 - this.logger.error(new ErrorWithCause('Uncaught error occurred during mDNS discovery via Avahi', {cause: e})); 170 + this.logger.error(new Error('Uncaught error occurred during mDNS discovery via Avahi', {cause: e})); 171 171 } 172 172 } 173 173 ··· 181 181 }, 182 182 }); 183 183 } catch (e) { 184 - this.logger.error(new ErrorWithCause('Uncaught error occurred during mDNS discovery', {cause: e})); 184 + this.logger.error(new Error('Uncaught error occurred during mDNS discovery', {cause: e})); 185 185 } 186 186 } 187 187 ··· 261 261 await client.connect(); 262 262 } catch (e) { 263 263 if(index < device.addresses.length - 1) { 264 - this.logger.warn(new ErrorWithCause(`Could not connect to ${device.name} but more interfaces exist, will attempt next host.`, {cause: e})); 264 + this.logger.warn(new Error(`Could not connect to ${device.name} but more interfaces exist, will attempt next host.`, {cause: e})); 265 265 continue; 266 266 } else { 267 - throw new ErrorWithCause(`Could not connect to ${device.name} and no additional interfaces exist`, {cause: e}); 267 + throw new Error(`Could not connect to ${device.name} and no additional interfaces exist`, {cause: e}); 268 268 } 269 269 } 270 270 ··· 284 284 } 285 285 if(event === "reconnect") { 286 286 if(payload instanceof Error) { 287 - info.logger.warn(new ErrorWithCause(`Failed to reconnect, will retry ${5 - info.retries} more times`, {cause: e})) 287 + info.logger.warn(new Error(`Failed to reconnect, will retry ${5 - info.retries} more times`, {cause: e})) 288 288 } else { 289 289 info.logger.verbose(`Reconnected`); 290 290 info.retries = 0; ··· 312 312 break; 313 313 case 'error': 314 314 if(info === undefined) { 315 - this.logger.error(new ErrorWithCause(`(${clientName}) Encountered error in castv2 lib`, {cause: payload as Error})); 315 + this.logger.error(new Error(`(${clientName}) Encountered error in castv2 lib`, {cause: payload as Error})); 316 316 } else { 317 317 if(NETWORK_ERROR_FAILURE_CODES.some(x => (payload as Error).message.includes(x))) { 318 - info.logger.warn(new ErrorWithCause(`Encountered network error. Will try to reconnect to device`, {cause: payload as Error})); 318 + info.logger.warn(new Error(`Encountered network error. Will try to reconnect to device`, {cause: payload as Error})); 319 319 info.client.client.close(); 320 320 } else { 321 - info.logger.error(new ErrorWithCause(`Encountered error in castv2 lib`, {cause: payload as Error})); 321 + info.logger.error(new Error(`Encountered error in castv2 lib`, {cause: payload as Error})); 322 322 } 323 323 } 324 324 break; ··· 336 336 apps = await getCurrentPlatformApplications(v.platform); 337 337 v.retries = 0; 338 338 } catch (e) { 339 - v.logger.warn(new ErrorWithCause(`Could not refresh applications. Will remove after ${5 - v.retries} retries if error does not resolve itself.`, {cause: e})); 339 + v.logger.warn(new Error(`Could not refresh applications. Will remove after ${5 - v.retries} retries if error does not resolve itself.`, {cause: e})); 340 340 const validationError = findCauseByReference(e, ContextualValidationError); 341 341 if(validationError && validationError.data !== undefined) { 342 342 v.logger.warn(JSON.stringify(validationError.data)); ··· 495 495 try { 496 496 await this.refreshApplications(); 497 497 } catch (e) { 498 - this.logger.warn(new ErrorWithCause('Could not refresh all applications', {cause: e})); 498 + this.logger.warn(new Error('Could not refresh all applications', {cause: e})); 499 499 } 500 500 501 501 for (const [k, v] of this.devices.entries()) { ··· 629 629 plays.push(playerState); 630 630 631 631 } catch (e) { 632 - application.logger.warn(new ErrorWithCause(`Could not get Player State`, {cause: e})) 632 + application.logger.warn(new Error(`Could not get Player State`, {cause: e})) 633 633 const validationError = findCauseByReference(e, ContextualValidationError); 634 634 if (validationError && validationError.data !== undefined) { 635 635 application.logger.warn(JSON.stringify(validationError.data));
+1 -2
src/backend/sources/DeezerSource.ts
··· 14 14 import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js"; 15 15 import EventEmitter from "events"; 16 16 import { PlayObject } from "../../core/Atomic.js"; 17 - import {ErrorWithCause} from "pony-cause"; 18 17 19 18 export default class DeezerSource extends AbstractSource { 20 19 workingCredsPath; ··· 102 101 this.logger.warn(`No Deezer credentials file found at ${this.workingCredsPath}`); 103 102 } 104 103 } catch (e) { 105 - throw new ErrorWithCause('Current deezer credentials file exists but could not be parsed', {cause: e}); 104 + throw new Error('Current deezer credentials file exists but could not be parsed', {cause: e}); 106 105 } 107 106 if (this.config.data.accessToken === undefined) { 108 107 if (this.config.data.clientId === undefined) {
+3 -4
src/backend/sources/LastfmSource.ts
··· 9 9 import { LastfmSourceConfig } from "../common/infrastructure/config/source/lastfm.js"; 10 10 import dayjs from "dayjs"; 11 11 import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; 12 - import {ErrorWithCause} from "pony-cause"; 13 - import request, {options} from "superagent"; 12 + import request from "superagent"; 14 13 15 14 export default class LastfmSource extends MemorySource { 16 15 ··· 57 56 return true; 58 57 } catch (e) { 59 58 if(isNodeNetworkException(e)) { 60 - throw new ErrorWithCause('Could not communicate with Last.fm API server', {cause: e}); 59 + throw new Error('Could not communicate with Last.fm API server', {cause: e}); 61 60 } else if(e.status >= 500) { 62 - throw new ErrorWithCause('Last.fm API server returning an unexpected response', {cause: e}) 61 + throw new Error('Last.fm API server returning an unexpected response', {cause: e}) 63 62 } 64 63 return true; 65 64 }
+3 -4
src/backend/sources/ListenbrainzSource.ts
··· 4 4 import { ListenBrainzSourceConfig } from "../common/infrastructure/config/source/listenbrainz.js"; 5 5 import { ListenbrainzApiClient } from "../common/vendor/ListenbrainzApiClient.js"; 6 6 import MemorySource from "./MemorySource.js"; 7 - import {ErrorWithCause} from "pony-cause"; 8 7 import request from "superagent"; 9 8 import {isNodeNetworkException} from "../common/errors/NodeErrors.js"; 10 9 import {PlayObject, SOURCE_SOT} from "../../core/Atomic.js"; ··· 42 41 return true; 43 42 } catch (e) { 44 43 if(isNodeNetworkException(e)) { 45 - throw new ErrorWithCause('Could not communicate with Listenbrainz API server', {cause: e}); 44 + throw new Error('Could not communicate with Listenbrainz API server', {cause: e}); 46 45 } else if(e.status !== 410) { 47 - throw new ErrorWithCause('Listenbrainz API server returning an unexpected response', {cause: e}) 46 + throw new Error('Listenbrainz API server returning an unexpected response', {cause: e}) 48 47 } 49 48 return true; 50 49 } ··· 58 57 return await this.api.testAuth(); 59 58 } catch (e) { 60 59 throw e; 61 - //throw new ErrorWithCause('Could not communicate with Listenbrainz API', {cause: e}); 60 + //throw new Error('Could not communicate with Listenbrainz API', {cause: e}); 62 61 } 63 62 } 64 63
+5 -6
src/backend/sources/MPRISSource.ts
··· 13 13 import { RecentlyPlayedOptions } from "./AbstractSource.js"; 14 14 import { removeDuplicates } from "../utils.js"; 15 15 import EventEmitter from "events"; 16 - import {ErrorWithCause} from "pony-cause"; 17 16 import { PlayObject } from "../../core/Atomic.js"; 18 17 import {DBusInterface, sessionBus} from 'dbus-ts'; 19 18 import { Interfaces as Notifications } from '@dbus-types/notifications' ··· 96 95 await this.getDBus(); 97 96 return true; 98 97 } catch (e) { 99 - throw new ErrorWithCause('Could not get DBus interface from operating system', {cause: e}); 98 + throw new Error('Could not get DBus interface from operating system', {cause: e}); 100 99 } 101 100 } 102 101 ··· 144 143 }); 145 144 } 146 145 catch (e) { 147 - this.logger.warn(new ErrorWithCause(`Could not parse D-bus info for player ${plainPlayerName}`, {cause: e})); 146 + this.logger.warn(new Error(`Could not parse D-bus info for player ${plainPlayerName}`, {cause: e})); 148 147 } 149 148 150 149 } ··· 158 157 // microseconds 159 158 return dayjs.duration({milliseconds: Number(pos / 1000)}).asSeconds(); 160 159 } catch(e) { 161 - throw new ErrorWithCause('Could not get player Position', {cause: e}); 160 + throw new Error('Could not get player Position', {cause: e}); 162 161 } 163 162 } 164 163 ··· 167 166 const status = await props['PlaybackStatus']; 168 167 return status as PlaybackStatus; 169 168 } catch (e) { 170 - throw new ErrorWithCause('Could not get player PlaybackStatus', {cause: e}) 169 + throw new Error('Could not get player PlaybackStatus', {cause: e}) 171 170 } 172 171 } 173 172 ··· 176 175 const metadata = await props['Metadata']; 177 176 return this.metadataToPlain(metadata); 178 177 } catch(e) { 179 - throw new ErrorWithCause('Could not get player Metadata', {cause: e}); 178 + throw new Error('Could not get player Metadata', {cause: e}); 180 179 } 181 180 } 182 181
+1 -2
src/backend/sources/MopidySource.ts
··· 15 15 import { RecentlyPlayedOptions } from "./AbstractSource.js"; 16 16 import { PlayObject } from "../../core/Atomic.js"; 17 17 import { buildTrackString } from "../../core/StringUtils.js"; 18 - import {ErrorWithCause} from "pony-cause"; 19 18 import {loggerTest} from "@foxxmd/logging"; 20 19 21 20 export class MopidySource extends MemorySource { ··· 118 117 return true; 119 118 } else { 120 119 this.client.close(); 121 - throw new ErrorWithCause(`Could not connect to Mopidy server`, {cause: (res as Error)}); 120 + throw new Error(`Could not connect to Mopidy server`, {cause: (res as Error)}); 122 121 } 123 122 } 124 123
+3 -7
src/backend/sources/SpotifySource.ts
··· 30 30 import AlbumObjectSimplified = SpotifyApi.AlbumObjectSimplified; 31 31 import UserDevice = SpotifyApi.UserDevice; 32 32 import MemorySource from "./MemorySource.js"; 33 - import {ErrorWithCause} from "pony-cause"; 34 33 import { PlayObject, SCROBBLE_TS_SOC_END, SCROBBLE_TS_SOC_START, ScrobbleTsSOC } from "../../core/Atomic.js"; 35 34 import { buildTrackString, truncateStringToLength } from "../../core/StringUtils.js"; 36 35 import { isNodeNetworkException } from "../common/errors/NodeErrors.js"; ··· 256 255 return true; 257 256 } catch (e) { 258 257 if(isNodeNetworkException(e)) { 259 - throw new ErrorWithCause('Could not communicate with Spotify API server', {cause: e}); 258 + throw new Error('Could not communicate with Spotify API server', {cause: e}); 260 259 } 261 260 if(e.status >= 500) { 262 - throw new ErrorWithCause('Spotify API server returned an unexpected response', { cause: e}); 261 + throw new Error('Spotify API server returned an unexpected response', { cause: e}); 263 262 } 264 263 return true; 265 264 } ··· 277 276 if(isNodeNetworkException(e)) { 278 277 this.logger.error('Could not communicate with Spotify API'); 279 278 } 280 - // this.authFailure = !(e instanceof ErrorWithCause && e.cause !== undefined && isNodeNetworkException(e.cause)); 281 - // this.logger.error(new ErrorWithCause('Could not successfully communicate with Spotify API', {cause: e})); 282 - // this.authed = false; 283 279 throw e; 284 280 } 285 281 } ··· 405 401 if(hasApiError(e)) { 406 402 throw new UpstreamError('Error occurred while trying to retrieve current playback state', {cause: e}); 407 403 } 408 - throw new ErrorWithCause('Error occurred while trying to retrieve current playback state', {cause: e}); 404 + throw new Error('Error occurred while trying to retrieve current playback state', {cause: e}); 409 405 } 410 406 } 411 407
+1 -4
src/backend/sources/SubsonicSource.ts
··· 10 10 import EventEmitter from "events"; 11 11 import { PlayObject } from "../../core/Atomic.js"; 12 12 import {isNodeNetworkException} from "../common/errors/NodeErrors.js"; 13 - import {ErrorWithCause} from "pony-cause"; 14 13 import {UpstreamError} from "../common/errors/UpstreamError.js"; 15 14 import {getSubsonicResponse, SubsonicResponse, SubsonicResponseCommon} from "../common/vendor/subsonic/interfaces.js"; 16 - import {hash} from "@astronautlabs/mdns/dist/hash.js"; 17 - import e from "express"; 18 15 19 16 dayjs.extend(isSameOrAfter); 20 17 ··· 216 213 } else if(e.status >= 500) { 217 214 throw new UpstreamError('Subsonic server returning an unexpected response', {cause: e}) 218 215 } else { 219 - throw new ErrorWithCause('Unexpected error occurred', {cause: e}) 216 + throw new Error('Unexpected error occurred', {cause: e}) 220 217 } 221 218 } 222 219 }
+4 -5
src/backend/utils/MDNSUtils.ts
··· 2 2 import AvahiBrowser from 'avahi-browse'; 3 3 import { MaybeLogger } from "../common/logging.js"; 4 4 import { sleep } from "../utils.js"; 5 - import {ErrorWithCause} from "pony-cause"; 6 5 import { MdnsDeviceInfo } from "../common/infrastructure/Atomic.js"; 7 6 import {Browser, Service, ServiceType} from "@astronautlabs/mdns"; 8 7 import {debounce, DebouncedFunction} from "./debounce.js"; ··· 78 77 } 79 78 }); 80 79 browser.on(AvahiBrowser.EVENT_DNSSD_ERROR, (err) => { 81 - const e = new ErrorWithCause('Error occurred while using avahi-browse', {cause: err}); 80 + const e = new Error('Error occurred while using avahi-browse', {cause: err}); 82 81 if (onDnsError) { 83 82 onDnsError(e) 84 83 } else { ··· 97 96 } 98 97 maybeLogger.debug('Stopped discovery'); 99 98 } catch (e) { 100 - maybeLogger.warn(new ErrorWithCause('mDNS device discovery with avahi-browse failed', {cause: e})); 99 + maybeLogger.warn(new Error('mDNS device discovery with avahi-browse failed', {cause: e})); 101 100 } 102 101 } 103 102 ··· 121 120 }) 122 121 .start(); 123 122 testBrowser.on('error', (err) => { 124 - maybeLogger.error(new ErrorWithCause('Error occurred during mDNS service discovery', {cause: err})); 123 + maybeLogger.error(new Error('Error occurred during mDNS service discovery', {cause: err})); 125 124 }); 126 125 maybeLogger.debug('Waiting 1s to gather advertised mdns services...'); 127 126 await sleep(1000); ··· 141 140 } 142 141 }) 143 142 browser.on('error', (err) => { 144 - const e = new ErrorWithCause('Error occurred during mDNS discovery', {cause: err}); 143 + const e = new Error('Error occurred during mDNS discovery', {cause: err}); 145 144 if (onDnsError) { 146 145 onDnsError(e) 147 146 } else {
+1 -2
src/backend/common/errors/UpstreamError.ts
··· 1 - import {ErrorWithCause} from "pony-cause"; 2 1 import { findCauseByFunc } from "../../utils.js"; 3 2 import {Response} from 'superagent'; 4 3 5 - export class UpstreamError<T = undefined> extends ErrorWithCause<T> { 4 + export class UpstreamError<T = undefined> extends Error { 6 5 7 6 showStopper: boolean = false; 8 7 response?: Response
+2 -3
src/backend/common/vendor/JRiverApiClient.ts
··· 2 2 import {JRiverData} from "../infrastructure/config/source/jriver.js"; 3 3 import request, {Request, Response} from 'superagent'; 4 4 import xml2js from 'xml2js'; 5 - import {ErrorWithCause} from "pony-cause"; 6 5 import {AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER} from "../infrastructure/Atomic.js"; 7 6 8 7 const parser = new xml2js.Parser({'async': true}); ··· 134 133 this.logger.verbose(`Found ${data.ProgramName} ${data.ProgramVersion} (${data.FriendlyName})`); 135 134 return true; 136 135 } catch (e) { 137 - throw new ErrorWithCause('Could not communicate with JRiver server. Verify your server URL is correct.', {cause: e}); 136 + throw new Error('Could not communicate with JRiver server. Verify your server URL is correct.', {cause: e}); 138 137 } 139 138 } 140 139 ··· 152 151 if(this.config.username === undefined || this.config.password === undefined) { 153 152 msg = 'Authentication failed. No username/password was provided in config! Did you mean to do this?'; 154 153 } 155 - this.logger.error(new ErrorWithCause(msg, {cause: e})); 154 + this.logger.error(new Error(msg, {cause: e})); 156 155 return false; 157 156 } 158 157 }
+1 -2
src/backend/common/vendor/KodiApiClient.ts
··· 1 1 import AbstractApiClient from "./AbstractApiClient.js"; 2 - import {ErrorWithCause} from "pony-cause"; 3 2 import { KodiData } from "../infrastructure/config/source/kodi.js"; 4 3 import { KodiClient } from 'kodi-api' 5 4 import normalizeUrl from "normalize-url"; ··· 143 142 if(this.config.username === undefined || this.config.password === undefined) { 144 143 msg = 'Authentication failed. No username/password was provided in config! Did you mean to do this?'; 145 144 } 146 - this.logger.error(new ErrorWithCause(msg, {cause: e})); 145 + this.logger.error(new Error(msg, {cause: e})); 147 146 return false; 148 147 } 149 148 }
+1 -2
src/backend/common/vendor/LastfmApiClient.ts
··· 13 13 import { PlayObject } from "../../../core/Atomic.js"; 14 14 import {getNodeNetworkException, isNodeNetworkException} from "../errors/NodeErrors.js"; 15 15 import {nonEmptyStringOrDefault, splitByFirstFound} from "../../../core/StringUtils.js"; 16 - import {ErrorWithCause} from "pony-cause"; 17 16 import {getScrobbleTsSOCDate} from "../../utils/TimeUtils.js"; 18 17 import {UpstreamError} from "../errors/UpstreamError.js"; 19 18 ··· 168 167 } 169 168 return true; 170 169 } catch (e) { 171 - throw new ErrorWithCause('Current lastfm credentials file exists but could not be parsed', {cause: e}); 170 + throw new Error('Current lastfm credentials file exists but could not be parsed', {cause: e}); 172 171 } 173 172 } 174 173
+3 -4
src/backend/common/vendor/chromecast/ChromecastClientUtils.ts
··· 1 1 import { REPORTED_PLAYER_STATUSES, ReportedPlayerStatus } from "../../infrastructure/Atomic.js"; 2 2 import { PlatformApplication, PlatformType } from "./interfaces.js"; 3 3 import {Media, MediaController, Result} from "@foxxmd/chromecast-client"; 4 - import {ErrorWithCause} from "pony-cause"; 5 4 import objectHash from "object-hash"; 6 5 import { PlayObject } from "../../../../core/Atomic.js"; 7 6 ··· 27 26 try { 28 27 statusRes = await platform.getStatus() 29 28 } catch (e) { 30 - throw new ErrorWithCause('Unable to fetch platform statuses', {cause: e}); 29 + throw new Error('Unable to fetch platform statuses', {cause: e}); 31 30 } 32 31 33 32 let status: {applications?: PlatformApplication[]}; ··· 39 38 } 40 39 return status.applications; 41 40 } catch (e) { 42 - throw new ErrorWithCause('Unable to fetch platform statuses', {cause: e}); 41 + throw new Error('Unable to fetch platform statuses', {cause: e}); 43 42 } 44 43 } 45 44 ··· 51 50 status = statusRes.unwrapAndThrow(); 52 51 return status; 53 52 } catch (e) { 54 - throw new ErrorWithCause('Unable to fetch media status', {cause: e}); 53 + throw new Error('Unable to fetch media status', {cause: e}); 55 54 } 56 55 } 57 56