hawk / files / public / js / signin.js
signin.js
601035fb 2023-10-12 Pavel Safronov | 1 | const clientId = '6c4c04a3751b4e409ddad7d71aad9949'; |
cc1af884 2023-10-13 Pavel Safronov | 2 | const scope = 'user-library-read user-modify-playback-state user-read-playback-state'; |
3bf5fcbb 2023-10-13 Pavel Safronov | 3 | const prematureRefreshSeconds = 60 * 5; // 5 minutes before expiration |
| 4 | ||
8edcc887 2023-10-13 Pavel Safronov | 5 | function getSpotifyRedirectURL() { |
| 6 | // If the current hostname is 'localhost' | |
| 7 | if (window.location.hostname === 'localhost') { | |
| 8 | return 'http://localhost:5500/public/index.html'; | |
| 9 | } else if (window.location.hostname === '127.0.0.1') { | |
| 10 | return 'http://127.0.0.1:5500/public/index.html'; | |
| 11 | } else { | |
dd354806 2026-10-27 Pavel Safronov | 12 | return 'https://apps.fuzzyworld.net/hawk/index.html'; |
8edcc887 2023-10-13 Pavel Safronov | 13 | } |
| 14 | } | |
601035fb 2023-10-12 Pavel Safronov | 15 | |
| 16 | async function signOut() { | |
| 17 | localStorage.removeItem('access_token'); | |
3bf5fcbb 2023-10-13 Pavel Safronov | 18 | localStorage.removeItem('refresh_token'); |
| 19 | localStorage.removeItem('expires_at'); | |
6e3f55a5 2023-10-13 Pavel Safronov | 20 | await deleteDb(); |
714f908c 2023-10-13 Pavel Safronov | 21 | window.location = getSpotifyRedirectURL(); |
601035fb 2023-10-12 Pavel Safronov | 22 | } |
| 23 | ||
| 24 | async function signIn() { | |
| 25 | let codeVerifier = generateRandomString(128); | |
| 26 | const codeChallenge = await generateCodeChallenge(codeVerifier) | |
| 27 | let state = generateRandomString(16); | |
| 28 | ||
| 29 | localStorage.setItem('code_verifier', codeVerifier); | |
| 30 | ||
8edcc887 2023-10-13 Pavel Safronov | 31 | const redirectUrl = getSpotifyRedirectURL(); |
601035fb 2023-10-12 Pavel Safronov | 32 | let args = new URLSearchParams({ |
| 33 | response_type: 'code', | |
| 34 | client_id: clientId, | |
| 35 | scope: scope, | |
8edcc887 2023-10-13 Pavel Safronov | 36 | redirect_uri: redirectUrl, |
601035fb 2023-10-12 Pavel Safronov | 37 | state: state, |
| 38 | code_challenge_method: 'S256', | |
| 39 | code_challenge: codeChallenge | |
| 40 | }); | |
| 41 | ||
| 42 | window.location = 'https://accounts.spotify.com/authorize?' + args; | |
| 43 | } | |
| 44 | ||
| 45 | async function getToken(code) { | |
| 46 | let codeVerifier = localStorage.getItem('code_verifier'); | |
| 47 | ||
8edcc887 2023-10-13 Pavel Safronov | 48 | const redirectUrl = getSpotifyRedirectURL(); |
601035fb 2023-10-12 Pavel Safronov | 49 | let body = new URLSearchParams({ |
| 50 | grant_type: 'authorization_code', | |
| 51 | code: code, | |
8edcc887 2023-10-13 Pavel Safronov | 52 | redirect_uri: redirectUrl, |
601035fb 2023-10-12 Pavel Safronov | 53 | client_id: clientId, |
| 54 | code_verifier: codeVerifier | |
| 55 | }); | |
| 56 | ||
| 57 | try { | |
| 58 | const response = await fetch('https://accounts.spotify.com/api/token', { | |
| 59 | method: 'POST', | |
| 60 | headers: { | |
| 61 | 'Content-Type': 'application/x-www-form-urlencoded' | |
| 62 | }, | |
| 63 | body: body | |
| 64 | }); | |
| 65 | if (!response.ok) { | |
| 66 | console.error('HTTP status ' + response.status); | |
| 67 | throw new Error('HTTP status ' + response.status); | |
| 68 | } | |
| 69 | const data = await response.json(); | |
3bf5fcbb 2023-10-13 Pavel Safronov | 70 | const expiresInSeconds = data.expires_in; |
601035fb 2023-10-12 Pavel Safronov | 71 | localStorage.setItem('access_token', data.access_token); |
09b64e4c 2023-10-14 Pavel Safronov | 72 | localStorage.setItem('refresh_token', data.refresh_token); |
3bf5fcbb 2023-10-13 Pavel Safronov | 73 | localStorage.setItem('expires_at', Date.now() + expiresInSeconds * 1000); |
601035fb 2023-10-12 Pavel Safronov | 74 | } catch (error) { |
| 75 | console.error('Error:', error); | |
| 76 | } | |
| 77 | } | |
| 78 | ||
| 79 | function generateRandomString(length) { | |
| 80 | let text = ''; | |
| 81 | let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | |
| 82 | ||
| 83 | for (let i = 0; i < length; i++) { | |
| 84 | text += possible.charAt(Math.floor(Math.random() * possible.length)); | |
| 85 | } | |
| 86 | return text; | |
| 87 | } | |
| 88 | ||
| 89 | async function generateCodeChallenge(codeVerifier) { | |
| 90 | function base64encode(string) { | |
| 91 | return btoa(String.fromCharCode.apply(null, new Uint8Array(string))) | |
| 92 | .replace(/\+/g, '-') | |
| 93 | .replace(/\//g, '_') | |
| 94 | .replace(/=+$/, ''); | |
| 95 | } | |
| 96 | ||
| 97 | const encoder = new TextEncoder(); | |
| 98 | const data = encoder.encode(codeVerifier); | |
| 99 | const digest = await window.crypto.subtle.digest('SHA-256', data); | |
| 100 | ||
| 101 | return base64encode(digest); | |
| 102 | } | |
3bf5fcbb 2023-10-13 Pavel Safronov | 103 | |
| 104 | async function getAccessToken() { | |
791409a6 2023-10-13 Pavel Safronov | 105 | let accessToken = localStorage.getItem('access_token'); |
3bf5fcbb 2023-10-13 Pavel Safronov | 106 | if (!accessToken) { |
| 107 | throw 'No access token found'; | |
| 108 | } | |
| 109 | ||
| 110 | const expiresAtText = localStorage.getItem('expires_at'); | |
| 111 | if (!expiresAtText) { | |
| 112 | throw 'No expiration found'; | |
| 113 | } | |
| 114 | const expiresAt = parseInt(expiresAtText); | |
| 115 | const shouldRefresh = Date.now() > expiresAt - prematureRefreshSeconds * 1000; | |
| 116 | if (shouldRefresh) { | |
| 117 | console.log('Refreshing access token'); | |
| 118 | accessToken = await refreshToken(); | |
| 119 | } | |
| 120 | ||
| 121 | return accessToken; | |
| 122 | } | |
| 123 | ||
| 124 | async function refreshToken() { | |
| 125 | const refreshToken = localStorage.getItem('refresh_token'); | |
| 126 | if (!refreshToken) { | |
| 127 | throw 'No refresh token found'; | |
| 128 | } | |
| 129 | ||
| 130 | let body = new URLSearchParams({ | |
| 131 | grant_type: 'refresh_token', | |
| 132 | refresh_token: refreshToken, | |
| 133 | client_id: clientId | |
| 134 | }); | |
| 135 | ||
09b64e4c 2023-10-14 Pavel Safronov | 136 | const response = await fetch('https://accounts.spotify.com/api/token', { |
| 137 | method: 'POST', | |
| 138 | headers: { | |
| 139 | 'Content-Type': 'application/x-www-form-urlencoded' | |
| 140 | }, | |
| 141 | body: body, | |
| 142 | }); | |
| 143 | if (!response.ok) { | |
| 144 | console.error('HTTP status ' + response.status); | |
| 145 | throw new Error('HTTP status ' + response.status); | |
3bf5fcbb 2023-10-13 Pavel Safronov | 146 | } |
09b64e4c 2023-10-14 Pavel Safronov | 147 | const data = await response.json(); |
| 148 | const expiresInSeconds = data.expires_in; | |
| 149 | localStorage.setItem('access_token', data.access_token); | |
| 150 | localStorage.setItem('refresh_token', data.refresh_token); | |
| 151 | localStorage.setItem('expires_at', Date.now() + expiresInSeconds * 1000); | |
| 152 | return data.access_token; | |
3bf5fcbb 2023-10-13 Pavel Safronov | 153 | } |