From 2aac56c97a2b6cdc5f2527e7d522d48b7a666ee2 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 29 Jan 2026 17:13:16 +0000 Subject: [PATCH] feat(scrobblers): Serialize error correctly and add debug info to dead letter ui --- package-lock.json | 56 +++++++++++++++++++ package.json | 1 + .../scrobblers/AbstractScrobbleClient.ts | 8 ++- src/backend/sources/SpotifySource.ts | 2 +- src/client/deadLetter/DeadPage.tsx | 29 +++++++++- src/core/Atomic.ts | 3 +- 6 files changed, 91 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index df9aa8f9..629a8d23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,6 +93,7 @@ "react-router-dom": "^6.15.0", "round-robin-js": "^3.0.10", "sass": "^1.93.3", + "serialize-error": "^13.0.1", "spotify-web-api-node": "^5.0.2", "superagent": "^8.0.9", "tailwindcss": "^4.1.16", @@ -9477,6 +9478,18 @@ "node": ">=4" } }, + "node_modules/non-error": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/non-error/-/non-error-0.1.0.tgz", + "integrity": "sha512-TMB1uHiGsHRGv1uYclfhivcnf0/PdFp2pNqRxXjncaAsjYMoisaQJI+SSZCqRq+VliwRTC8tsMQfmrWjDMhkPQ==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/normalize-exception": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/normalize-exception/-/normalize-exception-4.0.1.tgz", @@ -11481,6 +11494,37 @@ "node": ">= 0.8" } }, + "node_modules/serialize-error": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-13.0.1.tgz", + "integrity": "sha512-bBZaRwLH9PN5HbLCjPId4dP5bNGEtumcErgOX952IsvOhVPrm3/AeK1y0UHA/QaPG701eg0yEnOKsCOC6X/kaA==", + "license": "MIT", + "dependencies": { + "non-error": "^0.1.0", + "type-fest": "^5.4.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.2.tgz", + "integrity": "sha512-FLEenlVYf7Zcd34ISMLo3ZzRE1gRjY1nMDTp+bQRBiPsaKyIW8K3Zr99ioHDUgA9OGuGGJPyYpNcffGmBhJfGg==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -12022,6 +12066,18 @@ "node": ">=8" } }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tailwindcss": { "version": "4.1.16", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", diff --git a/package.json b/package.json index 1aba8b6a..af5ee5b9 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,7 @@ "react-router-dom": "^6.15.0", "round-robin-js": "^3.0.10", "sass": "^1.93.3", + "serialize-error": "^13.0.1", "spotify-web-api-node": "^5.0.2", "superagent": "^8.0.9", "tailwindcss": "^4.1.16", diff --git a/src/backend/scrobblers/AbstractScrobbleClient.ts b/src/backend/scrobblers/AbstractScrobbleClient.ts index 9bd5ae0f..4cef5ce1 100644 --- a/src/backend/scrobblers/AbstractScrobbleClient.ts +++ b/src/backend/scrobblers/AbstractScrobbleClient.ts @@ -62,6 +62,7 @@ import { comparePlayArtistsNormalized, comparePlayTracksNormalized } from "../ut import { normalizeStr } from "../utils/StringUtils.js"; import prom, { Counter, Gauge } from 'prom-client'; import { ScrobbleSubmitError } from "../common/errors/MSErrors.js"; +import {serializeError} from 'serialize-error'; type PlatformMappedPlays = Map; type NowPlayingQueue = Map; @@ -812,15 +813,16 @@ ${closestMatch.breakdowns.join('\n')}`, {leaf: ['Dupe Check']}); this.addScrobbledTrack(scrobbledPlay, scrobbledPlay.meta.lifecycle.scrobble.mergedScrobble ?? scrobbledPlay); } catch (e) { currQueuedPlay.play.meta.lifecycle.scrobble = { - error: e, - payload: this.playToClientPayload(transformedScrobble) }; const submitError = findCauseByReference(e, ScrobbleSubmitError); if(submitError !== undefined) { - currQueuedPlay.play.meta.lifecycle.scrobble.error = submitError; currQueuedPlay.play.meta.lifecycle.scrobble.payload = submitError.payload; currQueuedPlay.play.meta.lifecycle.scrobble.response = submitError.responseBody; + currQueuedPlay.play.meta.lifecycle.scrobble.error = serializeError(submitError); + } else { + currQueuedPlay.play.meta.lifecycle.scrobble.payload = this.playToClientPayload(transformedScrobble); + currQueuedPlay.play.meta.lifecycle.scrobble.error = serializeError(e); } if (hasUpstreamError(e, false)) { diff --git a/src/backend/sources/SpotifySource.ts b/src/backend/sources/SpotifySource.ts index 588ebe60..d92ea998 100644 --- a/src/backend/sources/SpotifySource.ts +++ b/src/backend/sources/SpotifySource.ts @@ -405,7 +405,7 @@ export default class SpotifySource extends MemoryPositionalSource { getPlayHistory = async (options: RecentlyPlayedOptions = {}) => { const {limit = 20} = options; const func = (api: SpotifyWebApi) => api.getMyRecentlyPlayedTracks({ - limit: 1 + limit }); const result = await this.callApi>(func); return result.body.items.map((x: PlayHistoryObject) => SpotifySource.formatPlayObj(x)).sort(sortByOldestPlayDate); diff --git a/src/client/deadLetter/DeadPage.tsx b/src/client/deadLetter/DeadPage.tsx index 344d783a..723e06ba 100644 --- a/src/client/deadLetter/DeadPage.tsx +++ b/src/client/deadLetter/DeadPage.tsx @@ -14,6 +14,10 @@ import { import dayjs from "dayjs"; import {RootState} from "../store"; import {connect, ConnectedProps} from "react-redux"; +import { faBug } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import clsx from "clsx"; +import { useCopyToClipboard } from '../components/copyToClipboardHook'; const displayOpts = { include: recentIncludes, @@ -31,6 +35,17 @@ const dead = (props: PropsFromRedux) => { isSuccess } = useGetDeadQuery({name: searchParams.get('name'), type: searchParams.get('type')}); + const { copy, isCopied } = useCopyToClipboard(); + + const [copiedIndex, setIndex] = useState(null); + + const copyActionCB = useCallback((obj, index) => { + copy(JSON.stringify(obj, null, 2)); + setIndex(index); + },[copy, setIndex]); + + const baseClass = ['mr-3']; + const [removeDeadFetch, removeResult] = useRemoveDeadSingleMutation(); const [retryDeadFetch, processResult] = useProcessDeadSingleMutation(); const [removeAllDeadFetch] = useLazyRemoveDeadQuery(); @@ -51,15 +66,23 @@ const dead = (props: PropsFromRedux) => {
{isSuccess && !isLoading && data.length === 0 ? 'No failed scrobbles!' : null} -
    {data.map(x => (
  • -
    +
      {data.map(x => + { + const classes = [...baseClass].concat(copiedIndex !== x.id ? ['underline','cursor-pointer'] : []); + return (
    • +
      + +
      Source:{x.source.replace('Source -', '')}
      Retries: {x.retries}
      Last Retried: {x.lastRetry === undefined ? 'Never' : dayjs.duration(dayjs(x.lastRetry).diff(dayjs())).humanize(true)}
      Error: {x.error}
      retryDead(x.id)} className="capitalize underline cursor-pointer max-w-fit">Retry
      removeDead(x.id)} className="capitalize underline cursor-pointer max-w-fit">Remove
      -
    • ))}
    +
  • ) + } + )}
diff --git a/src/core/Atomic.ts b/src/core/Atomic.ts index 6e117ad3..c572f9cb 100644 --- a/src/core/Atomic.ts +++ b/src/core/Atomic.ts @@ -4,6 +4,7 @@ import { ListenProgress } from "../backend/sources/PlayerState/ListenProgress.js import { AdditionalTrackInfoResponse } from "../backend/common/vendor/listenbrainz/interfaces.js"; import { Delta } from 'jsondiffpatch'; import { MarkOptional } from "ts-essentials"; +import { ErrorObject } from "serialize-error"; export interface SourceStatusData { status: string; @@ -290,7 +291,7 @@ export interface PlayLifecycle { scrobble?: { payload?: ScrobblePayload warnings?: string[] - error?: Error + error?: Error | ErrorObject response?: ScrobbleResponse mergedScrobble?: PlayObjectLifecycleless } -- 2.51.2