Atoll: add in-app Spotify login sheet for sp_dc cookie capture

Wraps accounts.spotify.com login in a WKWebView so users can sign in
without opening the browser inspector to copy the sp_dc cookie. The
sheet uses a non-persistent data store, observes the cookie store, and
hands the captured value to the existing SpotifyAuthManager.validate
flow on a match.

Spotify's anti-bot blocks the WebView path for some accounts, so this
sits alongside (not replaces) the manual paste field, with an "Open in
Browser" escape hatch in the sheet toolbar.

Signed-off-by: Jis G Jacob <studiokeys@blissroms.org>
This commit is contained in:
Jis G Jacob 2026-05-12 23:27:23 +05:30
commit 2e796b08a7
3 changed files with 312 additions and 42 deletions

View file

@ -23,6 +23,7 @@ import SwiftUI
struct SpotifyAuthSettingsSection: View {
@Default(.spotifySPDCCookie) private var spotifySPDCCookie
@ObservedObject private var spotifyAuthManager = SpotifyAuthManager.shared
@State private var showingLoginSheet = false
private var hasCookie: Bool {
!SpotifyAuthManager.sanitizeCookie(spotifySPDCCookie).isEmpty
@ -31,10 +32,17 @@ struct SpotifyAuthSettingsSection: View {
var body: some View {
Section {
VStack(alignment: .leading, spacing: 8) {
Text("Paste the `sp_dc` cookie from your logged-in Spotify web session.")
Text("Sign in to Spotify to capture the `sp_dc` cookie automatically, or paste it in below.")
.font(.caption)
.foregroundStyle(.secondary)
Button {
showingLoginSheet = true
} label: {
Label("Sign in with Spotify", systemImage: "person.crop.circle.badge.checkmark")
}
.buttonStyle(.borderedProminent)
TextField("sp_dc cookie", text: $spotifySPDCCookie, axis: .vertical)
.textFieldStyle(.roundedBorder)
.font(.caption.monospaced())
@ -77,25 +85,25 @@ struct SpotifyAuthSettingsSection: View {
.disabled(!hasCookie && !spotifyAuthManager.isAuthenticated)
}
VStack(alignment: .leading, spacing: 6) {
Text("How to get it:")
.font(.caption)
.foregroundStyle(.secondary)
DisclosureGroup("Get the cookie manually") {
VStack(alignment: .leading, spacing: 6) {
Text("1. Open Spotify in a browser and log in")
.font(.caption)
Text("2. Developer Tools -> Application/Storage -> Cookies -> https://open.spotify.com")
.font(.caption)
Text("3. Copy the value of `sp_dc` and paste it here")
.font(.caption)
Text("1. Open Spotify in a browser and log in")
HStack(spacing: 12) {
Link("Open Spotify Web Player", destination: URL(string: "https://open.spotify.com")!)
Link("Method source", destination: URL(string: "https://github.com/Paxsenix0/Spotify-Canvas-API")!)
}
.font(.caption)
Text("2. Developer Tools -> Application/Storage -> Cookies -> https://open.spotify.com")
.font(.caption)
Text("3. Copy the value of `sp_dc` and paste it here")
.font(.caption)
HStack(spacing: 12) {
Link("Open Spotify Web Player", destination: URL(string: "https://open.spotify.com")!)
Link("Method source", destination: URL(string: "https://github.com/Paxsenix0/Spotify-Canvas-API")!)
}
.font(.caption)
.padding(.top, 4)
}
.padding(.top, 2)
.font(.caption)
.foregroundStyle(.secondary)
} header: {
Text("Spotify Canvas Session")
} footer: {
@ -103,6 +111,15 @@ struct SpotifyAuthSettingsSection: View {
.foregroundStyle(.secondary)
.font(.caption)
}
.sheet(isPresented: $showingLoginSheet) {
SpotifyLoginSheet { capturedValue in
let sanitized = SpotifyAuthManager.sanitizeCookie(capturedValue)
spotifySPDCCookie = sanitized
Task {
await spotifyAuthManager.validateSession()
}
}
}
}
private func pasteCookieFromClipboard() {