diff --git a/core/agents/lastfm/token_received.html b/core/agents/lastfm/token_received.html
index 1bbd6a256..775a68228 100644
--- a/core/agents/lastfm/token_received.html
+++ b/core/agents/lastfm/token_received.html
@@ -1 +1,17 @@
-Success! Your account is linked to Last.FM. You can close this tab now.
+
+
+
+
+ Account Linking Success
+
+
+
+ Success! Your account is linked to Last.fm. You can close this tab now.
+
+
+
+
diff --git a/ui/src/i18n/en.json b/ui/src/i18n/en.json
index f1d67903a..56e10aebf 100644
--- a/ui/src/i18n/en.json
+++ b/ui/src/i18n/en.json
@@ -297,7 +297,11 @@
"delete_user_title": "Delete user '%{name}'",
"delete_user_content": "Are you sure you want to delete this user and all their data (including playlists and preferences)?",
"notifications_blocked": "You have blocked Notifications for this site in your browser's settings",
- "notifications_not_available": "This browser does not support desktop notifications or you are not accessing Navidrome over https"
+ "notifications_not_available": "This browser does not support desktop notifications or you are not accessing Navidrome over https",
+ "lastfmLinkSuccess": "Last.fm successfully linked and scrobbling enabled",
+ "lastfmLinkFailure": "Last.fm could not be linked",
+ "lastfmUnlinkSuccess": "Last.fm unlinked and scrobbling disabled",
+ "lastfmUnlinkFailure": "Last.fm could not unlinked"
},
"menu": {
"library": "Library",
@@ -310,7 +314,8 @@
"theme": "Theme",
"language": "Language",
"defaultView": "Default View",
- "desktop_notifications": "Desktop Notifications"
+ "desktop_notifications": "Desktop Notifications",
+ "lastfmScrobbling": "Scrobble to Last.fm"
}
},
"albumList": "Albums",
diff --git a/ui/src/personal/LastfmScrobbleToggle.js b/ui/src/personal/LastfmScrobbleToggle.js
new file mode 100644
index 000000000..92faf307f
--- /dev/null
+++ b/ui/src/personal/LastfmScrobbleToggle.js
@@ -0,0 +1,122 @@
+import { useEffect, useRef, useState } from 'react'
+import { useNotify, useTranslate } from 'react-admin'
+import {
+ FormControl,
+ FormControlLabel,
+ LinearProgress,
+ Switch,
+} from '@material-ui/core'
+import { useInterval } from '../common'
+import config from '../config'
+import { baseUrl, openInNewTab } from '../utils'
+import { httpClient } from '../dataProvider'
+
+const Progress = (props) => {
+ const { setLinked, setCheckingLink } = props
+ const notify = useNotify()
+ let linkCheckDelay = 2000
+ let linkChecks = 30
+ const openedTab = useRef()
+
+ useEffect(() => {
+ const callbackEndpoint = baseUrl(
+ `/api/lastfm/link/callback?uid=${localStorage.getItem('userId')}`
+ )
+ const callbackUrl = `${window.location.origin}${callbackEndpoint}`
+ openedTab.current = openInNewTab(
+ `https://www.last.fm/api/auth/?api_key=${config.lastFMApiKey}&cb=${callbackUrl}`
+ )
+ }, [])
+
+ const endChecking = (success) => {
+ linkCheckDelay = null
+ setCheckingLink(false)
+ if (success) {
+ notify('message.lastfmLinkSuccess', 'success')
+ } else {
+ notify('message.lastfmLinkFailure', 'warning')
+ }
+ setLinked(success)
+ }
+
+ useInterval(() => {
+ httpClient('/api/lastfm/link')
+ .then((response) => {
+ let result = false
+ if (response.json.status === true) {
+ result = true
+ endChecking(true)
+ }
+ return result
+ })
+ .then((result) => {
+ if (!result && openedTab.current?.closed === true) {
+ endChecking(false)
+ result = true
+ }
+ return result
+ })
+ .then((result) => {
+ if (!result && --linkChecks === 0) {
+ endChecking(false)
+ }
+ })
+ .catch(() => {
+ endChecking(false)
+ })
+ }, linkCheckDelay)
+
+ return
+}
+
+export const LastfmScrobbleToggle = (props) => {
+ const notify = useNotify()
+ const translate = useTranslate()
+ const [linked, setLinked] = useState(null)
+ const [checkingLink, setCheckingLink] = useState(false)
+
+ const toggleScrobble = () => {
+ if (!linked) {
+ setCheckingLink(true)
+ } else {
+ httpClient('/api/lastfm/link', { method: 'DELETE' })
+ .then(() => {
+ setLinked(false)
+ notify('message.lastfmUnlinkSuccess', 'success')
+ })
+ .catch(() => notify('message.lastfmUnlinkFailure', 'warning'))
+ }
+ }
+
+ useEffect(() => {
+ httpClient('/api/lastfm/link')
+ .then((response) => {
+ setLinked(response.json.status === true)
+ })
+ .catch(() => {
+ setLinked(false)
+ })
+ }, [])
+
+ return (
+
+
+ }
+ label={
+ {translate('menu.personal.options.lastfmScrobbling')}
+ }
+ />
+ {checkingLink && (
+
+ )}
+
+ )
+}
diff --git a/ui/src/personal/Personal.js b/ui/src/personal/Personal.js
index f7f077062..8e0c8067e 100644
--- a/ui/src/personal/Personal.js
+++ b/ui/src/personal/Personal.js
@@ -5,7 +5,7 @@ import { SelectLanguage } from './SelectLanguage'
import { SelectTheme } from './SelectTheme'
import { SelectDefaultView } from './SelectDefaultView'
import { NotificationsToggle } from './NotificationsToggle'
-import { ScrobbleToggle } from './ScrobbleToggle'
+import { LastfmScrobbleToggle } from './LastfmScrobbleToggle'
import config from '../config'
const useStyles = makeStyles({
@@ -24,7 +24,7 @@ const Personal = () => {
- {config.devEnableScrobble && }
+ {config.devEnableScrobble && }
)
diff --git a/ui/src/personal/ScrobbleToggle.js b/ui/src/personal/ScrobbleToggle.js
deleted file mode 100644
index 06f870128..000000000
--- a/ui/src/personal/ScrobbleToggle.js
+++ /dev/null
@@ -1,84 +0,0 @@
-import { useState } from 'react'
-import { useNotify, useTranslate } from 'react-admin'
-import {
- FormControl,
- FormControlLabel,
- LinearProgress,
- Switch,
-} from '@material-ui/core'
-import { useInterval } from '../common'
-import { baseUrl } from '../utils'
-
-const Progress = (props) => {
- const { setLinked, setCheckingLink } = props
- const translate = useTranslate()
- const notify = useNotify()
- let linkCheckDelay = 2000
- let linkChecks = 5
- // openInNewTab(
- // '/api/lastfm/link'
- // )
-
- const endChecking = (success) => {
- linkCheckDelay = null
- setCheckingLink(false)
- if (success) {
- notify(translate('Last.fm successfully linked!'), 'success')
- } else {
- notify(translate('Last.fm could not be linked'), 'warning')
- }
- setLinked(success)
- }
-
- useInterval(() => {
- fetch(baseUrl(`/api/lastfm/link/status?c=${linkChecks}`))
- .then((response) => response.text())
- .then((response) => {
- if (response === 'true') {
- endChecking(true)
- }
- })
- .catch((error) => {
- endChecking(false)
- throw new Error(error)
- })
-
- if (--linkChecks === 0) {
- endChecking(false)
- }
- }, linkCheckDelay)
-
- return linkChecks > 0 &&
-}
-
-export const ScrobbleToggle = (props) => {
- const translate = useTranslate()
- const [linked, setLinked] = useState(false)
- const [checkingLink, setCheckingLink] = useState(false)
- const toggleScrobble = () => {
- if (!linked) {
- return setCheckingLink(true)
- }
- setLinked(!linked)
- }
-
- return (
-
-
- }
- label={{translate('Scrobble to Last.FM')}}
- />
- {checkingLink && (
-
- )}
-
- )
-}
diff --git a/ui/src/utils/openInNewTab.js b/ui/src/utils/openInNewTab.js
index b5ad35612..ea34a0662 100644
--- a/ui/src/utils/openInNewTab.js
+++ b/ui/src/utils/openInNewTab.js
@@ -1,4 +1,5 @@
export const openInNewTab = (url) => {
const win = window.open(url, '_blank')
win.focus()
+ return win
}