A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

Notify Elm apps of access token refreshing

+149 -44
+7
src/Applications/Brain.elm
··· 144 144 ----------------------------------------- 145 145 -- 📭 Other 146 146 ----------------------------------------- 147 + RefreshedAccessToken a -> 148 + Other.refreshedAccessToken a 149 + 147 150 SetCurrentTime a -> 148 151 Other.setCurrentTime a 149 152 ··· 160 163 Sub.batch 161 164 [ Ports.fromAlien alien 162 165 , Ports.makeArtworkTrackUrls MakeArtworkTrackUrls 166 + , Ports.refreshedAccessToken RefreshedAccessToken 163 167 , Ports.receiveSearchResults GotSearchResults 164 168 , Ports.receiveTags (ProcessingMsg << Processing.TagsStep) 165 169 , Ports.replaceTags ReplaceTrackTags ··· 229 233 230 234 Alien.ProcessSources -> 231 235 ProcessingMsg (Processing.Process data) 236 + 237 + Alien.RefreshedAccessToken -> 238 + RefreshedAccessToken data 232 239 233 240 Alien.RemoveEncryptionKey -> 234 241 UserMsg User.RemoveEncryptionKey
+22
src/Applications/Brain/Other/State.elm
··· 7 7 import Json.Decode as Json 8 8 import Return 9 9 import Return.Ext as Return 10 + import Sources exposing (Service(..)) 11 + import Sources.Refresh.AccessToken 10 12 import Time 11 13 12 14 13 15 14 16 -- 🔱 17 + 18 + 19 + refreshedAccessToken : Json.Value -> Manager 20 + refreshedAccessToken value model = 21 + case Json.decodeValue Sources.Refresh.AccessToken.portArgumentsDecoder value of 22 + Ok portArguments -> 23 + case portArguments.service of 24 + Google -> 25 + -- TODO: 26 + -- 1. Find source 27 + -- 2. Add `access token` and `expires at` to source data 28 + -- 3. Replace source in collection 29 + -- 4. Save user data 30 + Return.singleton model 31 + 32 + _ -> 33 + Return.singleton model 34 + 35 + Err err -> 36 + Common.reportUI Alien.ToCache (Json.errorToString err) model 15 37 16 38 17 39 setCurrentTime : Time.Posix -> Manager
+3
src/Applications/Brain/Ports.elm
··· 99 99 port receiveSearchResults : (List String -> msg) -> Sub msg 100 100 101 101 102 + port refreshedAccessToken : (Json.Value -> msg) -> Sub msg 103 + 104 + 102 105 port receiveTags : (ContextForTags -> msg) -> Sub msg 103 106 104 107
+2 -2
src/Applications/Brain/Sources/Processing/Steps.elm
··· 320 320 321 321 mapFn = 322 322 \path -> 323 - { getUrl = maker currentTime source.data Get path 324 - , headUrl = maker currentTime source.data Head path 323 + { getUrl = maker currentTime source.id source.data Get path 324 + , headUrl = maker currentTime source.id source.data Head path 325 325 } 326 326 in 327 327 List.map mapFn filePaths
+2 -1
src/Applications/Brain/Tracks/State.elm
··· 306 306 Sources.Services.makeTrackUrl 307 307 source.service 308 308 timestamp 309 + source.id 309 310 source.data 310 311 httpMethod 311 312 trackPath ··· 318 319 tagUrls currentTime path source = 319 320 let 320 321 maker = 321 - Sources.Services.makeTrackUrl source.service currentTime source.data 322 + Sources.Services.makeTrackUrl source.service currentTime source.id source.data 322 323 in 323 324 { getUrl = maker Get path 324 325 , headUrl = maker Head path
+1
src/Applications/Brain/Types.elm
··· 66 66 ----------------------------------------- 67 67 -- 📭 Other 68 68 ----------------------------------------- 69 + | RefreshedAccessToken Json.Value 69 70 | SetCurrentTime Time.Posix 70 71 | ToCache Json.Value 71 72
+19 -15
src/Applications/Brain/User/State.elm
··· 840 840 terminate NotAuthenticated model 841 841 842 842 843 - refreshedDropboxTokens : 844 - { currentTime : Int, refreshToken : String } 845 - -> Dropbox.Tokens 846 - -> User.Msg 847 - -> Manager 848 - refreshedDropboxTokens { currentTime, refreshToken } tokens msg model = 849 - { accessToken = tokens.accessToken 850 - , expiresAt = currentTime + tokens.expiresIn 851 - , refreshToken = refreshToken 852 - } 853 - |> Dropbox 854 - |> (\m -> saveMethod m model) 855 - |> andThen (update msg) 856 - 857 - 858 843 retrieveMethod : Manager 859 844 retrieveMethod = 860 845 Alien.AuthMethod ··· 955 940 model 956 941 |> Common.nudgeUI Alien.NotAuthenticated 957 942 |> andThen (Common.nudgeUI Alien.HideLoadingScreen) 943 + 944 + 945 + 946 + -- 📭 ░░ OTHER 947 + 948 + 949 + refreshedDropboxTokens : 950 + { currentTime : Int, refreshToken : String } 951 + -> Dropbox.Tokens 952 + -> User.Msg 953 + -> Manager 954 + refreshedDropboxTokens { currentTime, refreshToken } tokens msg model = 955 + { accessToken = tokens.accessToken 956 + , expiresAt = currentTime + tokens.expiresIn 957 + , refreshToken = refreshToken 958 + } 959 + |> Dropbox 960 + |> (\m -> saveMethod m model) 961 + |> andThen (update msg)
+4
src/Applications/UI.elm
··· 529 529 InstallingServiceWorker -> 530 530 Other.installingServiceWorker 531 531 532 + RedirectToBrain a -> 533 + Other.redirectToBrain a 534 + 532 535 ReloadApp -> 533 536 Other.reloadApp 534 537 ··· 603 606 ----------------------------------------- 604 607 , Ports.installedNewServiceWorker (\_ -> InstalledServiceWorker) 605 608 , Ports.installingNewServiceWorker (\_ -> InstallingServiceWorker) 609 + , Ports.refreshedAccessToken (Alien.broadcast Alien.RefreshedAccessToken >> RedirectToBrain) 606 610 , Ports.setIsOnline SetIsOnline 607 611 , Ports.webnativeResponse GotWebnativeResponse 608 612 , Sub.map KeyboardMsg Keyboard.subscriptions
+5
src/Applications/UI/Other/State.elm
··· 31 31 Return.singleton { model | serviceWorkerStatus = InstallingNew } 32 32 33 33 34 + redirectToBrain : Alien.Event -> Manager 35 + redirectToBrain event model = 36 + return model (Ports.toBrain event) 37 + 38 + 34 39 reloadApp : Manager 35 40 reloadApp model = 36 41 return model (Ports.reloadApp ())
+3
src/Applications/UI/Ports.elm
··· 83 83 port noteProgress : ({ trackId : String, progress : Float } -> msg) -> Sub msg 84 84 85 85 86 + port refreshedAccessToken : (Json.Value -> msg) -> Sub msg 87 + 88 + 86 89 port preferredColorSchemaChanged : ({ dark : Bool } -> msg) -> Sub msg 87 90 88 91
+2
src/Applications/UI/Types.elm
··· 1 1 module UI.Types exposing (..) 2 2 3 3 import Alfred exposing (Alfred) 4 + import Alien 4 5 import Browser 5 6 import Browser.Navigation as Nav 6 7 import Color exposing (Color) ··· 305 306 ----------------------------------------- 306 307 | InstalledServiceWorker 307 308 | InstallingServiceWorker 309 + | RedirectToBrain Alien.Event 308 310 | ReloadApp 309 311 | SetCurrentTime Time.Posix 310 312 | SetCurrentTimeZone Time.Zone
+18 -9
src/Javascript/urls.js
··· 41 41 let finalAccessToken 42 42 43 43 const googleBits = parts[ 1 ].split("@") 44 - const [ accessToken, expiresAtString, refreshToken, clientId, clientSecret ] = googleBits[ 0 ].split(":") 44 + const [ accessToken, expiresAtString, refreshToken, clientId, clientSecret, srcId ] = googleBits[ 0 ].split(":") 45 45 const fileId = googleBits[ 1 ] 46 46 47 47 // Unix timestamp in milliseconds 48 - const in15minutes = Date.now() + 15 * 60 * 1000 48 + const inXminutes = Date.now() + 5 * 60 * 1000 // 5 minutes 49 49 const expiresAt = parseInt(expiresAtString, 10) 50 - const isAlmostExpired = expiresAt <= in15minutes 50 + const isAlmostExpired = expiresAt <= inXminutes 51 51 52 52 if (EXPIRED_ACCESS_TOKENS.GOOGLE[ accessToken ]) { 53 53 const replacement = EXPIRED_ACCESS_TOKENS.GOOGLE[ accessToken ] 54 54 55 - if (replacement.newExpiresAt <= in15minutes) { 55 + if (replacement.newExpiresAt <= inXminutes) { 56 56 finalAccessToken = await refreshGoogleAccessToken({ 57 - clientId, clientSecret, refreshToken, oldToken: accessToken 57 + app, clientId, clientSecret, refreshToken, srcId, oldToken: accessToken 58 58 }) 59 59 } else { 60 60 finalAccessToken = replacement.newToken ··· 62 62 63 63 } else if (isAlmostExpired) { 64 64 finalAccessToken = await refreshGoogleAccessToken({ 65 - clientId, clientSecret, refreshToken, oldToken: accessToken 65 + app, clientId, clientSecret, refreshToken, srcId, oldToken: accessToken 66 66 }) 67 67 68 68 } else { ··· 86 86 // GOOGLE 87 87 88 88 89 - async function refreshGoogleAccessToken({ clientId, clientSecret, oldToken, refreshToken }) { 89 + async function refreshGoogleAccessToken({ app, clientId, clientSecret, oldToken, refreshToken, srcId }) { 90 90 console.log("🔐 Refreshing Google Drive access token") 91 91 92 92 const url = new URL("https://www.googleapis.com/oauth2/v4/token") ··· 97 97 url.searchParams.set("grant_type", "refresh_token") 98 98 99 99 const serverResponse = await fetch(url, { method: "POST" }).then(r => r.json()) 100 + const newToken = serverResponse.access_token 101 + const newExpiresAt = Date.now() + (serverResponse.expires_in * 1000) 100 102 101 103 EXPIRED_ACCESS_TOKENS.GOOGLE[ oldToken ] = { 102 104 oldToken, 103 - newToken: serverResponse.access_token, 104 - newExpiresAt: Date.now() + (serverResponse.expires_in * 1000), 105 + newToken, 106 + newExpiresAt, 105 107 refreshToken 106 108 } 109 + 110 + app.ports.refreshAccessToken.send({ 111 + service: "google", 112 + sourceId: srcId, 113 + accessToken: newToken, 114 + expiresAt: newExpiresAt 115 + }) 107 116 108 117 return serverResponse.access_token 109 118 }
+2
src/Library/Alien.elm
··· 40 40 | DownloadTracks 41 41 | ImportLegacyData 42 42 | ProcessSources 43 + | RefreshedAccessToken 43 44 | RemoveEncryptionKey 44 45 | RemoveTracksBySourceId 45 46 | RemoveTracksFromCache ··· 98 99 , ( "DOWNLOAD_TRACKS", DownloadTracks ) 99 100 , ( "IMPORT_LEGACY_DATA", ImportLegacyData ) 100 101 , ( "PROCESS_SOURCES", ProcessSources ) 102 + , ( "REFRESHED_ACCESS_TOKEN", RefreshedAccessToken ) 101 103 , ( "REMOVE_ENCRYPTION_KEY", RemoveEncryptionKey ) 102 104 , ( "REMOVE_TRACKS_BY_SOURCE_ID", RemoveTracksBySourceId ) 103 105 , ( "REMOVE_TRACKS_FROM_CACHE", RemoveTracksFromCache )
+1
src/Library/Queue.elm
··· 70 70 Sources.Services.makeTrackUrl 71 71 source.service 72 72 timestamp 73 + source.id 73 74 source.data 74 75 Get 75 76 track.path
+39
src/Library/Sources/Refresh/AccessToken.elm
··· 1 + module Sources.Refresh.AccessToken exposing (..) 2 + 3 + import Json.Decode exposing (Decoder) 4 + import Sources exposing (Service) 5 + import Sources.Encoding exposing (serviceDecoder) 6 + 7 + 8 + 9 + -- 🌳 10 + 11 + 12 + type alias PortArguments = 13 + { service : Service 14 + , sourceId : String 15 + , accessToken : String 16 + 17 + -- Unix timestamp in milliseconds 18 + , expiresAt : Int 19 + } 20 + 21 + 22 + 23 + -- 🛠 24 + 25 + 26 + portArgumentsDecoder : Decoder PortArguments 27 + portArgumentsDecoder = 28 + Json.Decode.map4 29 + (\service sourceId accessToken expiresAt -> 30 + { service = service 31 + , sourceId = sourceId 32 + , accessToken = accessToken 33 + , expiresAt = expiresAt 34 + } 35 + ) 36 + (Json.Decode.field "service" serviceDecoder) 37 + (Json.Decode.field "sourceId" Json.Decode.string) 38 + (Json.Decode.field "accessToken" Json.Decode.string) 39 + (Json.Decode.field "expiresAt" Json.Decode.int)
+1 -1
src/Library/Sources/Services.elm
··· 49 49 WebDav.initialData 50 50 51 51 52 - makeTrackUrl : Service -> Time.Posix -> SourceData -> HttpMethod -> String -> String 52 + makeTrackUrl : Service -> Time.Posix -> String -> SourceData -> HttpMethod -> String -> String 53 53 makeTrackUrl service = 54 54 case service of 55 55 AmazonS3 ->
+2 -2
src/Library/Sources/Services/AmazonS3.elm
··· 185 185 Creates a presigned url that's valid for 48 hours 186 186 187 187 -} 188 - makeTrackUrl : Time.Posix -> SourceData -> HttpMethod -> String -> String 189 - makeTrackUrl currentTime srcData method pathToFile = 188 + makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String 189 + makeTrackUrl currentTime _ srcData method pathToFile = 190 190 presignedUrl method 172800 [] currentTime srcData pathToFile
+2 -2
src/Library/Sources/Services/AzureBlob.elm
··· 163 163 (!) Creates a presigned url that's valid for 48 hours 164 164 165 165 -} 166 - makeTrackUrl : Time.Posix -> SourceData -> HttpMethod -> String -> String 167 - makeTrackUrl currentTime srcData _ pathToFile = 166 + makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String 167 + makeTrackUrl currentTime _ srcData _ pathToFile = 168 168 presignedUrl Blob Read Get 48 currentTime srcData pathToFile []
+2 -2
src/Library/Sources/Services/AzureFile.elm
··· 167 167 (!) Creates a presigned url that's valid for 48 hours 168 168 169 169 -} 170 - makeTrackUrl : Time.Posix -> SourceData -> HttpMethod -> String -> String 171 - makeTrackUrl currentTime srcData _ pathToFile = 170 + makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String 171 + makeTrackUrl currentTime _ srcData _ pathToFile = 172 172 presignedUrl File Read Get 48 currentTime srcData pathToFile []
+2 -2
src/Library/Sources/Services/Btfs.elm
··· 116 116 We need this to play the track. 117 117 118 118 -} 119 - makeTrackUrl : Time.Posix -> SourceData -> HttpMethod -> String -> String 120 - makeTrackUrl _ srcData _ path = 119 + makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String 120 + makeTrackUrl _ _ srcData _ path = 121 121 Ipfs.extractGateway srcData ++ "/btfs/" ++ Ipfs.rootHash srcData ++ "/" ++ Ipfs.encodedPath path
+2 -2
src/Library/Sources/Services/Dropbox.elm
··· 225 225 We need this to play the track. 226 226 227 227 -} 228 - makeTrackUrl : Time.Posix -> SourceData -> HttpMethod -> String -> String 229 - makeTrackUrl _ srcData _ pathToFile = 228 + makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String 229 + makeTrackUrl _ _ srcData _ pathToFile = 230 230 "dropbox://" ++ Dict.fetch "accessToken" "" srcData ++ "@" ++ pathToFile
+4 -2
src/Library/Sources/Services/Google.elm
··· 288 288 We need this to play the track. 289 289 290 290 -} 291 - makeTrackUrl : Time.Posix -> SourceData -> HttpMethod -> String -> String 292 - makeTrackUrl currentTime srcData _ path = 291 + makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String 292 + makeTrackUrl currentTime srcId srcData _ path = 293 293 let 294 294 file = 295 295 String.Path.file path ··· 317 317 , Dict.fetch "clientId" "" srcData 318 318 , ":" 319 319 , Dict.fetch "clientSecret" "" srcData 320 + , ":" 321 + , srcId 320 322 , "@" 321 323 , fileId 322 324 ]
+2 -2
src/Library/Sources/Services/Ipfs.elm
··· 247 247 We need this to play the track. 248 248 249 249 -} 250 - makeTrackUrl : Time.Posix -> SourceData -> HttpMethod -> String -> String 251 - makeTrackUrl _ srcData _ path = 250 + makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String 251 + makeTrackUrl _ _ srcData _ path = 252 252 if not (String.contains "/" path) && not (String.contains "." path) then 253 253 -- If it still uses the old way of doing things 254 254 -- (ie. each path was a cid)
+2 -2
src/Library/Sources/Services/WebDav.elm
··· 168 168 We need this to play the track. 169 169 170 170 -} 171 - makeTrackUrl : Time.Posix -> SourceData -> HttpMethod -> String -> String 172 - makeTrackUrl _ srcData _ filePath = 171 + makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String 172 + makeTrackUrl _ _ srcData _ filePath = 173 173 url { addAuth = True } srcData filePath 174 174 175 175