Fix: Improvements to MusicKit auth workflow (#2230)
authorMarvin Schenkel <marvinschenkel@gmail.com>
Mon, 16 Jun 2025 07:30:08 +0000 (09:30 +0200)
committerGitHub <noreply@github.com>
Mon, 16 Jun 2025 07:30:08 +0000 (09:30 +0200)
music_assistant/helpers/auth.py
music_assistant/providers/apple_music/__init__.py
music_assistant/providers/apple_music/musickit_auth/musickit_wrapper.html

index 8a5ba50cea389e23f2d8740bf1ca9e593ac60d3b..2dec70b101488badadd528b253fe2e7776ca76f5 100644 (file)
@@ -11,6 +11,8 @@ from aiohttp.web import Request, Response
 from music_assistant_models.enums import EventType
 from music_assistant_models.errors import LoginFailed
 
+from music_assistant.helpers.json import json_loads
+
 if TYPE_CHECKING:
     from music_assistant.mass import MusicAssistant
 
@@ -20,18 +22,19 @@ LOGGER = logging.getLogger(__name__)
 class AuthenticationHelper:
     """Context manager helper class for authentication with a forward and redirect URL."""
 
-    def __init__(self, mass: MusicAssistant, session_id: str) -> None:
+    def __init__(self, mass: MusicAssistant, session_id: str, method: str = "GET") -> None:
         """
         Initialize the Authentication Helper.
 
         Params:
-        - url: The URL the user needs to open for authentication.
         - session_id: a unique id for this auth session.
+        - method: the HTTP request method to expect, either "GET" or "POST" (default: GET).
         """
         self.mass = mass
         self.session_id = session_id
         self._cb_path = f"/callback/{self.session_id}"
         self._callback_response: asyncio.Queue[dict[str, str]] = asyncio.Queue(1)
+        self._method = method
 
     @property
     def callback_url(self) -> str:
@@ -40,7 +43,9 @@ class AuthenticationHelper:
 
     async def __aenter__(self) -> AuthenticationHelper:
         """Enter context manager."""
-        self.mass.webserver.register_dynamic_route(self._cb_path, self._handle_callback, "GET")
+        self.mass.webserver.register_dynamic_route(
+            self._cb_path, self._handle_callback, self._method
+        )
         return self
 
     async def __aexit__(
@@ -50,11 +55,17 @@ class AuthenticationHelper:
         exc_tb: TracebackType | None,
     ) -> bool | None:
         """Exit context manager."""
-        self.mass.webserver.unregister_dynamic_route(self._cb_path, "GET")
+        self.mass.webserver.unregister_dynamic_route(self._cb_path, self._method)
         return None
 
     async def authenticate(self, auth_url: str, timeout: int = 60) -> dict[str, str]:
-        """Start the auth process and return any query params if received on the callback."""
+        """
+        Start the auth process and return any query params if received on the callback.
+
+        Params:
+        - url: The URL the user needs to open for authentication.
+        - timeout: duration in seconds helpers waits for callback (default: 60).
+        """
         self.send_url(auth_url)
         LOGGER.debug("Waiting for authentication callback on %s", self.callback_url)
         return await self.wait_for_callback(timeout)
@@ -75,6 +86,14 @@ class AuthenticationHelper:
     async def _handle_callback(self, request: Request) -> Response:
         """Handle callback response."""
         params = dict(request.query)
+        if request.method == "POST" and request.can_read_body:
+            try:
+                raw_data = await request.read()
+                data = json_loads(raw_data)
+                params.update(data)
+            except Exception as err:
+                LOGGER.error("Failed to parse POST data: %s", err)
+
         await self._callback_response.put(params)
         LOGGER.debug("Received callback with params: %s", params)
         return_html = """
index d57d9efd5173c665a2a31caad2d4eac24954dbdf..2c89c740f6c84b21b115f7732c51faf57baad070 100644 (file)
@@ -1,4 +1,17 @@
-"""Apple Music musicprovider support for MusicAssistant."""
+"""
+Apple Music musicprovider support for MusicAssistant.
+
+TODO MUSIC_APP_TOKEN expires after 6 months so should have a distribution mechanism outside
+  compulsory application updates. It is only a semi-private key in JWT format so code be refreshed
+  daily by a GitHub action and downloaded by the provider each initialise.
+TODO Widevine keys can be obtained dynamically from Apple Music API rather than copied into Docker
+  build. This is undocumented but @maxlyth has a working example.
+TODO MUSIC_USER_TOKEN must be refreshed (~min 180 days) and needs mechanism to prompt user to
+  re-authenticate in browser.
+TODO Current provider ignores private tracks that are not available in the storefront catalog as
+  streamable url is derived from the catalog id. It is undecumented but @maxlyth has a working
+  example to get a streamable url from the library id.
+"""
 
 from __future__ import annotations
 
@@ -7,6 +20,7 @@ import json
 import os
 import pathlib
 import re
+import time
 from typing import TYPE_CHECKING, Any
 
 import aiofiles
@@ -82,6 +96,7 @@ UNKNOWN_PLAYLIST_NAME = "Unknown Apple Music Playlist"
 
 CONF_MUSIC_APP_TOKEN = "music_app_token"
 CONF_MUSIC_USER_TOKEN = "music_user_token"
+CONF_MUSIC_USER_TOKEN_TIMESTAMP = "music_user_token_timestamp"
 
 
 async def setup(
@@ -126,42 +141,67 @@ async def get_config_entries(
             default_app_token_valid = True
 
     # Action is to launch MusicKit flow
-    if action == "CONF_ACTION_AUTH":
-        # TODO: check the developer token is valid otherwise user is going to have bad experience
-        async with AuthenticationHelper(mass, values["session_id"]) as auth_helper:
+    if action == "CONF_ACTION_AUTH" and default_app_token_valid:
+        callback_method = "POST"
+        async with AuthenticationHelper(mass, values["session_id"], callback_method) as auth_helper:
             callback_url = auth_helper.callback_url
             flow_base_path = f"apple_music_auth/{values['session_id']}/"
             flow_timeout = 600
             parent_file_path = pathlib.Path(__file__).parent.resolve()
+            base_url = f"{mass.webserver.base_url}/{flow_base_path}"
+            flow_base_url = f"{base_url}index.html"
 
             async def serve_mk_auth_page(request: web.Request) -> web.Response:
                 auth_html_path = parent_file_path.joinpath("musickit_auth/musickit_wrapper.html")
-                return web.FileResponse(auth_html_path, headers={"content-type": "text/html"})
+                return web.FileResponse(
+                    auth_html_path,
+                    headers={"content-type": "text/html"},
+                )
 
             async def serve_mk_auth_css(request: web.Request) -> web.Response:
                 auth_css_path = parent_file_path.joinpath("musickit_auth/musickit_wrapper.css")
-                return web.FileResponse(auth_css_path, headers={"content-type": "text/css"})
+                return web.FileResponse(
+                    auth_css_path,
+                    headers={
+                        "content-type": "text/css",
+                    },
+                )
 
             async def serve_mk_glue(request: web.Request) -> web.Response:
-                return_html = f"const app_token='{values[CONF_MUSIC_APP_TOKEN]}';"
-                return_html += f"const user_token='{values[CONF_MUSIC_USER_TOKEN]}';"
-                return_html += f"const return_url='{callback_url}';"
-                return_html += f"const flow_timeout={flow_timeout - 10};"
-                return_html += f"const mass_buid='{mass.version}';"
-                return web.Response(body=return_html, headers={"content-type": "text/javascript"})
+                return_html = f"""
+                const return_url='{callback_url}';
+                const base_url='{base_url}';
+                const app_token='{values[CONF_MUSIC_APP_TOKEN]}';
+                const callback_method='{callback_method}';
+                const user_token='{
+                    values[CONF_MUSIC_USER_TOKEN]
+                    if validate_user_token(values[CONF_MUSIC_USER_TOKEN])
+                    else ""
+                }';
+                const user_token_timestamp='{values[CONF_MUSIC_USER_TOKEN_TIMESTAMP]}';
+                const flow_timeout={max([flow_timeout - 10, 60])};
+                const flow_start_time={int(time.time())};
+                const mass_version='{mass.version}';
+                """
+                return web.Response(
+                    body=return_html,
+                    headers={
+                        "content-type": "text/javascript",
+                    },
+                )
 
             mass.webserver.register_dynamic_route(
                 f"/{flow_base_path}index.html", serve_mk_auth_page
             )
             mass.webserver.register_dynamic_route(f"/{flow_base_path}index.css", serve_mk_auth_css)
             mass.webserver.register_dynamic_route(f"/{flow_base_path}index.js", serve_mk_glue)
-            flow_base_url = f"{mass.webserver.base_url}/{flow_base_path}index.html"
+
             try:
-                values[CONF_MUSIC_USER_TOKEN] = (
-                    await auth_helper.authenticate(flow_base_url, flow_timeout)
-                )["music-user-token"]
+                result = await auth_helper.authenticate(flow_base_url, flow_timeout)
+                values[CONF_MUSIC_USER_TOKEN] = result["music-user-token"]
+                values[CONF_MUSIC_USER_TOKEN_TIMESTAMP] = result["music-user-token-timestamp"]
             except KeyError:
-                # no music-user-token URL param was found so user probably cancelled the auth
+                # no music-user-token URL param was found so likely user cancelled the auth
                 pass
             except Exception as error:
                 raise LoginFailed(f"Failed to authenticate with Apple '{error}'.")
@@ -186,9 +226,27 @@ async def get_config_entries(
             label="Music User Token",
             required=True,
             action="CONF_ACTION_AUTH",
-            description="You need to authenticate on Apple Music.",
+            description="Authenticate with Apple Music to retrieve a valid music user token.",
             action_label="Authenticate with Apple Music",
-            value=values.get(CONF_MUSIC_USER_TOKEN) if values else None,
+            value=values.get(CONF_MUSIC_USER_TOKEN)
+            if (
+                values
+                and isinstance(values.get(CONF_MUSIC_USER_TOKEN_TIMESTAMP), int)
+                and (
+                    values.get(CONF_MUSIC_USER_TOKEN_TIMESTAMP) > (time.time() - (3600 * 24 * 150))
+                )
+            )
+            else None,
+        ),
+        ConfigEntry(
+            key=CONF_MUSIC_USER_TOKEN_TIMESTAMP,
+            type=ConfigEntryType.INTEGER,
+            description="Timestamp music user token was updated.",
+            label="Music User Token Timestamp",
+            hidden=True,
+            required=True,
+            default_value=0,
+            value=values.get(CONF_MUSIC_USER_TOKEN_TIMESTAMP) if values else 0,
         ),
     )
 
index 5dc1dd2162ca9fcb2d8920cce5213d94ed6c9f2d..a7a16c4bce915134fce8b79a9eb9b80bb4a6dfaf 100644 (file)
 <head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>Music Assistant MusicKit Redirect</title>
+  <!-- Required to ensure authentication works behind a reverse proxy -->
+  <meta name="referrer" content="strict-origin-when-cross-origin">
+  <title>Apple Music Sign-in for Music Assistant</title>
+  <link rel="stylesheet" href="./index.css">
   <script src="./index.js"></script>
   <script src="https://js-cdn.music.apple.com/musickit/v3/musickit.js" data-web-components async></script>
-  <link rel="stylesheet" href="./index.css">
   <script>
     var music = null;
-    var music_user_token = "";
+    var music_user_token = (typeof user_token !== 'undefined' ) ? user_token : "";
+    var music_user_token_timestamp = (typeof user_token_timestamp !== 'undefined' ) ? user_token_timestamp : 0;
+    var callbackConsumed = false;
+    var closing = false;
 
     // The underlying MA auth helper times-out after fixed period so this window can no longer pass back any data so just close it.
     // Self closing windows is anti-pattern user experience but better than leaving non-functioning UI presented
-    setTimeout(() => {
-      window.close()
-    }, flow_timeout * 1000);
-    function mkSignIn() {
-      document.getElementById('signin_button').disabled = true;
-      document.getElementById('message').innerHTML = "Apple Signin window should open…";
-      document.getElementById('sub-message').innerHTML = "If the Apple Music authentication window does not open, please check the MusicKit token and try again.";
-      music.authorize().then(function (token) {
-        music_user_token = encodeURIComponent(token);
-        document
-          .getElementById('close_button')
-          .style
-          .display = "inline-block";
-        window.location.href = return_url + "?music-user-token=" + music_user_token;
-      })
-    }
-    function mkSignOut() {
-      document.getElementById('message').innerHTML = "This window should close…";
-      music
-        .unauthorize()
-      document
-        .getElementById('close_button')
-        .style
-        .display = "inline-block";
-      window.location.href = return_url;
-    }
-    function interceptWindowClose() {
-      var xmlHttp = new XMLHttpRequest();
-      xmlHttp.open("GET", return_url + (music_user_token ? "?music-user-token=" + music_user_token : ""), true);
-      xmlHttp.send(null);
+    if (typeof flow_timeout !== 'undefined' && flow_timeout > 0) {
+      setTimeout(sendTokenAndCloseWindow, flow_timeout * 1000);
     }
 
-    // Attempt to intercept window close and redirect back to the auth helper which will in turn close the helper panel
-    window.addEventListener("beforeunload", interceptWindowClose, false);
+    // Attempt to intercept window close and send a request to auth helper before closing the window.
+    // This is not always possible, but we try.
+    window.addEventListener("unload", sendTokenAndCloseWindow, false);
 
-    document.addEventListener('musickitloaded', function () { // MusicKit global is now defined
+    document.addEventListener('musickitloaded', function () {
+      // MusicKit global is now defined
       document.getElementById('message').innerHTML = "Apple MusicKit loaded";
-      document.getElementById('signin_button').disabled = false;
       MusicKit.configure({
         developerToken: app_token,
         app: {
           name: 'Music Assistant',
-          build: mass_buid
+          build: (typeof mass_version !== 'undefined' ) ? mass_version : '0.0.0'
         }
       }).then(() => {
         music = MusicKit.getInstance();
+        document.getElementById('signin_button').disabled = false;
         if (music.isAuthorized) {
+          // user token must have been issued in last 6 months
+          music_user_token_timestamp = Math.max(music_user_token_timestamp, (Date.now() / 1000) - (3600 * 24 * 180));
           document.getElementById('message').innerHTML = "Already signed in";
-          document.getElementById('sub-message').innerHTML = "Choose Continue to refresh token or Sign out to switch accounts.";
+          document.getElementById('sub-message').innerHTML = "Choose Continue to refresh token or Sign Out to remove token.";
           document.getElementById('signin_button').innerHTML = "Continue";
-          document
-            .getElementById('signout_button')
-            .style
-            .display = "inline-block";
+          document.getElementById('signout_button').style.display = "inline-block";
         } else {
           document.getElementById('message').innerHTML = "Not signed in";
-          document
-            .getElementById('signin_button')
-            .style
-            .display = "inline-block";
-          document
-            .getElementById('signout_button')
-            .style
-            .display = "none";
+          document.getElementById('signin_button').style.display = "inline-block";
+          document.getElementById('signout_button').style.display = "none";
         }
       });
     });
+
+    document.addEventListener("DOMContentLoaded", function () {
+      if (typeof mass_version !== 'undefined' && mass_version) {
+        document.getElementById('mass_version').innerHTML = `v${mass_version} `;
+      }
+    });
+
+    function sendTokenAndCloseWindow() {
+      if (closing) {
+        // anti race condition
+        return;
+      }
+      closing = true;
+      if (callbackConsumed) {
+        window.close();
+        return;
+      }
+      var xmlHttp = new XMLHttpRequest();
+      var url = new URL(return_url);
+      var data = null;
+      if (callback_method === "POST") {
+        data = JSON.stringify({'music-user-token': music_user_token, 'music-user-token-timestamp': music_user_token_timestamp});
+      } else {
+        if (music_user_token) {
+          url.searchParams.set('music-user-token', music_user_token);
+          url.searchParams.set('music-user-token-timestamp', music_user_token_timestamp);
+        }
+      }
+      xmlHttp.open(callback_method, url, true);
+      xmlHttp.onreadystatechange = () => {
+        if (xmlHttp.readyState === XMLHttpRequest.DONE) {
+          callbackConsumed = true;
+          if (xmlHttp.status === 200) {
+            document.getElementById('message').innerHTML = "Apple MusicKit token sent to Music Assistant";
+            document.getElementById('sub-message').innerHTML = "You can now close this window.";
+            document.getElementById('signin_button').style.display = "none";
+            document.getElementById('signout_button').style.display = "none";
+            document.getElementById('close_button').innerHTML = "Closing…";
+            setTimeout(() => {
+              window.close();
+            }, 500);
+          } else if (xmlHttp.status >= 400) {
+            console.error("Error sending token to Music Assistant: ", xmlHttp.statusText);
+            document.getElementById('message').innerHTML = "Error sending token to Music Assistant";
+            document.getElementById('sub-message').innerHTML = "Please try again.";
+            document.getElementById('signin_button').style.display = "inline-block";
+            document.getElementById('signout_button').style.display = "none";
+          }
+        }
+      };
+      xmlHttp.send(data);
+    }
+
+    function mkSignInButton() {
+      document.getElementById('signin_button').disabled = true;
+      document.getElementById('message').innerHTML = "Apple Signin window should open…";
+      document.getElementById('sub-message').innerHTML = "If the Apple Music authentication window does not open, please check the MusicKit token and try again.";
+      music.authorize().then(function (token) {
+        music_user_token = token;
+        music_user_token_timestamp = Math.floor(Date.now() / 1000);
+        document.getElementById('message').innerHTML = "Successfully signed in to Apple Music";
+        document.getElementById('close_button').style.display = "inline-block";
+        sendTokenAndCloseWindow();
+      }).catch(function (error) {
+        console.error("Error signing in: ", error);
+        document.getElementById('message').innerHTML = "Error signing in to Apple Music";
+        document.getElementById('sub-message').innerHTML = "Please try again.";
+        document.getElementById('signin_button').disabled = false;
+        document.getElementById('signin_button').style.display = "inline-block";
+        document.getElementById('signout_button').style.display = "none";
+      });
+    }
+
+    function mkSignOutButton() {
+      music.unauthorize();
+      music_user_token = "";
+      music_user_token_timestamp = 0;
+      document.getElementById('signout_button').disabled = true;
+      document.getElementById('message').innerHTML = "This window should close…";
+      document.getElementById('close_button').style.display = "inline-block";
+      sendTokenAndCloseWindow();
+    }
+
+    function mkCloseButton() {
+      sendTokenAndCloseWindow();
+    }
+
+    function mkSwitchAccountButton() {
+      music.unauthorize()
+      music_user_token = "";
+      music_user_token_timestamp = 0;
+      document.getElementById('message').innerHTML = "Not signed in";
+      document.getElementById('sub-message').innerHTML = "Sign in with Apple to allow Music Assistant to access your Apple Music account.";
+      document.getElementById('signin_button').innerHTML = "Sign In";
+      document.getElementById('signin_button').style.display = "inline-block";
+      document.getElementById('signout_button').style.display = "none";
+    }
   </script>
 </head>
 
-<body onunload="interceptWindowClose()">
+<body onunload="sendTokenAndCloseWindow();">
   <div id="app" class="container">
     <div class="base-content-wrapper theme-music base-authorization-request"
       data-test="oauth-authorization-request-third-party">
         <div class="icons base-authorization-request__icons-container">
           <div class="base-authorization-request__icon">
             <div class="base-icon">
-              <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
-                id="Artwork" x="0px" y="0px" width="76px" height="76px" viewbox="0 0 361 361"
+              <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+                x="0px" y="0px" width="76px" height="76px" viewbox="0 0 361 361"
                 style="enable-background:new 0 0 361 361;" xml:space="preserve">
                 <style type="text/css">
-                  .st0 {
-                    fill-rule: evenodd;
-                    clip-rule: evenodd;
-                    fill: url('#SVGID_1_');
-                  }
-
-                  .st1 {
-                    fill-rule: evenodd;
-                    clip-rule: evenodd;
-                    fill: #FFFFFF;
-                  }
+                  .st0 {fill-rule: evenodd;clip-rule: evenodd;fill: url('#SVGID_1_');}
+                  .st1 {fill-rule: evenodd;clip-rule: evenodd;fill: #FFFFFF;}
                 </style>
-                <g id="Layer_5">
-                  <lineargradient id="SVGID_1_" gradientunits="userSpaceOnUse" x1="180" y1="358.6047" x2="180"
-                    y2="7.7586">
-                    <stop offset="0" style="stop-color:#FA233B" />
-                    <stop offset="1" style="stop-color:#FB5C74" />
-                  </lineargradient>
-                  <path class="st0"
-                    d="M360,112.61c0-4.3,0-8.6-0.02-12.9c-0.02-3.62-0.06-7.24-0.16-10.86c-0.21-7.89-0.68-15.84-2.08-23.64c-1.42-7.92-3.75-15.29-7.41-22.49c-3.6-7.07-8.3-13.53-13.91-19.14c-5.61-5.61-12.08-10.31-19.15-13.91c-7.19-3.66-14.56-5.98-22.47-7.41c-7.8-1.4-15.76-1.87-23.65-2.08c-3.62-0.1-7.24-0.14-10.86-0.16C255.99,0,251.69,0,247.39,0H112.61c-4.3,0-8.6,0-12.9,0.02c-3.62,0.02-7.24,0.06-10.86,0.16C80.96,0.4,73,0.86,65.2,2.27c-7.92,1.42-15.28,3.75-22.47,7.41c-7.07,3.6-13.54,8.3-19.15,13.91c-5.61,5.61-10.31,12.07-13.91,19.14c-3.66,7.2-5.99,14.57-7.41,22.49c-1.4,7.8-1.87,15.76-2.08,23.64c-0.1,3.62-0.14,7.24-0.16,10.86C0,104.01,0,108.31,0,112.61v134.77c0,4.3,0,8.6,0.02,12.9c0.02,3.62,0.06,7.24,0.16,10.86c0.21,7.89,0.68,15.84,2.08,23.64c1.42,7.92,3.75,15.29,7.41,22.49c3.6,7.07,8.3,13.53,13.91,19.14c5.61,5.61,12.08,10.31,19.15,13.91c7.19,3.66,14.56,5.98,22.47,7.41c7.8,1.4,15.76,1.87,23.65,2.08c3.62,0.1,7.24,0.14,10.86,0.16c4.3,0.03,8.6,0.02,12.9,0.02h134.77c4.3,0,8.6,0,12.9-0.02c3.62-0.02,7.24-0.06,10.86-0.16c7.89-0.21,15.85-0.68,23.65-2.08c7.92-1.42,15.28-3.75,22.47-7.41c7.07-3.6,13.54-8.3,19.15-13.91c5.61-5.61,10.31-12.07,13.91-19.14c3.66-7.2,5.99-14.57,7.41-22.49c1.4-7.8,1.87-15.76,2.08-23.64c0.1-3.62,0.14-7.24,0.16-10.86c0.03-4.3,0.02-8.6,0.02-12.9V112.61z" />
-                </g>
-                <g id="Glyph_2_">
-                  <g>
-                    <path class="st1"
-                      d="M254.5,55c-0.87,0.08-8.6,1.45-9.53,1.64l-107,21.59l-0.04,0.01c-2.79,0.59-4.98,1.58-6.67,3c-2.04,1.71-3.17,4.13-3.6,6.95c-0.09,0.6-0.24,1.82-0.24,3.62c0,0,0,109.32,0,133.92c0,3.13-0.25,6.17-2.37,8.76c-2.12,2.59-4.74,3.37-7.81,3.99c-2.33,0.47-4.66,0.94-6.99,1.41c-8.84,1.78-14.59,2.99-19.8,5.01c-4.98,1.93-8.71,4.39-11.68,7.51c-5.89,6.17-8.28,14.54-7.46,22.38c0.7,6.69,3.71,13.09,8.88,17.82c3.49,3.2,7.85,5.63,12.99,6.66c5.33,1.07,11.01,0.7,19.31-0.98c4.42-0.89,8.56-2.28,12.5-4.61c3.9-2.3,7.24-5.37,9.85-9.11c2.62-3.75,4.31-7.92,5.24-12.35c0.96-4.57,1.19-8.7,1.19-13.26l0-116.15c0-6.22,1.76-7.86,6.78-9.08c0,0,88.94-17.94,93.09-18.75c5.79-1.11,8.52,0.54,8.52,6.61l0,79.29c0,3.14-0.03,6.32-2.17,8.92c-2.12,2.59-4.74,3.37-7.81,3.99c-2.33,0.47-4.66,0.94-6.99,1.41c-8.84,1.78-14.59,2.99-19.8,5.01c-4.98,1.93-8.71,4.39-11.68,7.51c-5.89,6.17-8.49,14.54-7.67,22.38c0.7,6.69,3.92,13.09,9.09,17.82c3.49,3.2,7.85,5.56,12.99,6.6c5.33,1.07,11.01,0.69,19.31-0.98c4.42-0.89,8.56-2.22,12.5-4.55c3.9-2.3,7.24-5.37,9.85-9.11c2.62-3.75,4.31-7.92,5.24-12.35c0.96-4.57,1-8.7,1-13.26V64.46C263.54,58.3,260.29,54.5,254.5,55z" />
-                  </g>
-                </g>
+                <lineargradient id="SVGID_1_" gradientunits="userSpaceOnUse" x1="180" y1="358.6047" x2="180" y2="7.7586">
+                  <stop offset="0" style="stop-color:#FA233B" />
+                  <stop offset="1" style="stop-color:#FB5C74" />
+                </lineargradient>
+                <path class="st0" d="M360,112.61c0-4.3,0-8.6-0.02-12.9c-0.02-3.62-0.06-7.24-0.16-10.86c-0.21-7.89-0.68-15.84-2.08-23.64c-1.42-7.92-3.75-15.29-7.41-22.49c-3.6-7.07-8.3-13.53-13.91-19.14c-5.61-5.61-12.08-10.31-19.15-13.91c-7.19-3.66-14.56-5.98-22.47-7.41c-7.8-1.4-15.76-1.87-23.65-2.08c-3.62-0.1-7.24-0.14-10.86-0.16C255.99,0,251.69,0,247.39,0H112.61c-4.3,0-8.6,0-12.9,0.02c-3.62,0.02-7.24,0.06-10.86,0.16C80.96,0.4,73,0.86,65.2,2.27c-7.92,1.42-15.28,3.75-22.47,7.41c-7.07,3.6-13.54,8.3-19.15,13.91c-5.61,5.61-10.31,12.07-13.91,19.14c-3.66,7.2-5.99,14.57-7.41,22.49c-1.4,7.8-1.87,15.76-2.08,23.64c-0.1,3.62-0.14,7.24-0.16,10.86C0,104.01,0,108.31,0,112.61v134.77c0,4.3,0,8.6,0.02,12.9c0.02,3.62,0.06,7.24,0.16,10.86c0.21,7.89,0.68,15.84,2.08,23.64c1.42,7.92,3.75,15.29,7.41,22.49c3.6,7.07,8.3,13.53,13.91,19.14c5.61,5.61,12.08,10.31,19.15,13.91c7.19,3.66,14.56,5.98,22.47,7.41c7.8,1.4,15.76,1.87,23.65,2.08c3.62,0.1,7.24,0.14,10.86,0.16c4.3,0.03,8.6,0.02,12.9,0.02h134.77c4.3,0,8.6,0,12.9-0.02c3.62-0.02,7.24-0.06,10.86-0.16c7.89-0.21,15.85-0.68,23.65-2.08c7.92-1.42,15.28-3.75,22.47-7.41c7.07-3.6,13.54-8.3,19.15-13.91c5.61-5.61,10.31-12.07,13.91-19.14c3.66-7.2,5.99-14.57,7.41-22.49c1.4-7.8,1.87-15.76,2.08-23.64c0.1-3.62,0.14-7.24,0.16-10.86c0.03-4.3,0.02-8.6,0.02-12.9V112.61z" />
+                <path class="st1" d="M254.5,55c-0.87,0.08-8.6,1.45-9.53,1.64l-107,21.59l-0.04,0.01c-2.79,0.59-4.98,1.58-6.67,3c-2.04,1.71-3.17,4.13-3.6,6.95c-0.09,0.6-0.24,1.82-0.24,3.62c0,0,0,109.32,0,133.92c0,3.13-0.25,6.17-2.37,8.76c-2.12,2.59-4.74,3.37-7.81,3.99c-2.33,0.47-4.66,0.94-6.99,1.41c-8.84,1.78-14.59,2.99-19.8,5.01c-4.98,1.93-8.71,4.39-11.68,7.51c-5.89,6.17-8.28,14.54-7.46,22.38c0.7,6.69,3.71,13.09,8.88,17.82c3.49,3.2,7.85,5.63,12.99,6.66c5.33,1.07,11.01,0.7,19.31-0.98c4.42-0.89,8.56-2.28,12.5-4.61c3.9-2.3,7.24-5.37,9.85-9.11c2.62-3.75,4.31-7.92,5.24-12.35c0.96-4.57,1.19-8.7,1.19-13.26l0-116.15c0-6.22,1.76-7.86,6.78-9.08c0,0,88.94-17.94,93.09-18.75c5.79-1.11,8.52,0.54,8.52,6.61l0,79.29c0,3.14-0.03,6.32-2.17,8.92c-2.12,2.59-4.74,3.37-7.81,3.99c-2.33,0.47-4.66,0.94-6.99,1.41c-8.84,1.78-14.59,2.99-19.8,5.01c-4.98,1.93-8.71,4.39-11.68,7.51c-5.89,6.17-8.49,14.54-7.67,22.38c0.7,6.69,3.92,13.09,9.09,17.82c3.49,3.2,7.85,5.56,12.99,6.6c5.33,1.07,11.01,0.69,19.31-0.98c4.42-0.89,8.56-2.22,12.5-4.55c3.9-2.3,7.24-5.37,9.85-9.11c2.62-3.75,4.31-7.92,5.24-12.35c0.96-4.57,1-8.7,1-13.26V64.46C263.54,58.3,260.29,54.5,254.5,55z" />
               </svg>
               <p class="base-icon__label">Apple Music</p>
             </div>
           </div>
           <div class="base-authorization-request__arrows"></div>
           <div class="base-authorization-request__icon">
-            <div class="base-icon"><img
-                src="data:image/png;base64,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"
+            <div class="base-icon">
+              <img src="data:image/png;base64,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"
                 height="76px" width="76px">
               <p class="base-icon__label">Music Assistant</p>
             </div>
         <h1 class="base-content-wrapper__title typography-label">
           <span id="message">Apple MusicKit is loading…</span>
         </h1>
-        <p id="sub-message" class="base-content-wrapper__description typography-body-tight">Sign
-          in with Apple to allow Music Assistant to access your Apple Music
-          account.</p>
+        <p id="sub-message" class="base-content-wrapper__description typography-body-tight">
+          Sign in with Apple to allow Music Assistant to access your Apple Music account.</p>
         <p>
           <button id="signin_button"
             class="button button-block button-elevated button-primary base-button base-button--primary base-content-wrapper__button"
-            onclick="mkSignIn();" disabled>Sign In</button>
+            onclick="mkSignInButton();" disabled>Sign In</button>
         </p>
         <p>
           <button id="signout_button"
             class="button button-block button-elevated button-primary base-button base-button--primary base-content-wrapper__button"
-            onclick="mkSignOut();" style="display: none">Sign Out</button>
+            onclick="mkSignOutButton();" style="display: none">Sign Out</button>
         </p>
         <p>
           <button id="close_button"
             class="button button-block button-elevated button-secondary base-button base-button--secondary base-content-wrapper__button"
-            onclick="window.location.href = return_url;">Close</button>
+            onclick="mkCloseButton();">Close</button>
         </p>
       </section>
       <div class="privacy-summary__link-container">
         <a class="privacy-summary__link-text privacy-summary__link-text--margin-top typography-caption"
-          onclick="music.unauthorize(); location.reload();" href="#">Use a
-          different Apple&nbsp;Account</a>
+          onclick="mkSwitchAccountButton();" href="#">Use a different Apple&nbsp;Account</a>
       </div>
       <footer class="base-footer">
-        <p class="base-footer__copyright">© 2025 Music Assistant - Part of the
-          <a href="https://www.openhomefoundation.org">Open
-            Home Foundation</a>. Support us on
-          <a href="https://github.com/sponsors/music-assistant">GitHub</a>.
+        <p class="base-footer__copyright">© 2025 Music Assistant <span id="mass_version"></span>- Part of the
+          <a href="https://www.openhomefoundation.org" target="_blank">Open Home Foundation</a>. Support us on
+          <a href="https://github.com/sponsors/music-assistant" target="_blank">GitHub</a>.
         </p>
       </footer>
     </div>
   </div>
 </body>
-
 </html>