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.

Refresh access token

+191 -44
+3 -3
Justfile
··· 1 1 export NODE_NO_WARNINGS := "1" 2 2 3 3 4 - BUILD_DIR := "./build" 4 + BUILD_DIR := "./build" 5 5 NPM_DIR := "./node_modules" 6 6 SRC_DIR := "./src" 7 - SYSTEM_DIR := "./system" 7 + SYSTEM_DIR := "./system" 8 8 9 9 ESBUILD := NPM_DIR + "/.bin/esbuild --target=es2018 --bundle" 10 10 ··· 176 176 177 177 @server: 178 178 echo "> Booting up web server on port 5000" 179 - devd --port 5000 --all --crossdomain --quiet --notfound=301.html {{BUILD_DIR}} 179 + nix-shell --run "simple-http-server --port 5000 --try-file {{BUILD_DIR}}/301.html --cors --index --nocache --silent -- {{BUILD_DIR}}" 180 180 181 181 182 182 @test: doc-tests
+1
elm.json
··· 37 37 "icidasset/elm-binary": "2.1.0", 38 38 "icidasset/elm-material-icons": "9.0.0", 39 39 "icidasset/elm-sha": "2.0.2", 40 + "jzxhuang/http-extras": "2.1.0", 40 41 "mpizenberg/elm-pointer-events": "4.0.2", 41 42 "newlandsvalley/elm-binary-base64": "1.0.3", 42 43 "noahzgordon/elm-color-extra": "1.0.2",
+18 -6
nix/sources.json
··· 5 5 "homepage": "https://github.com/nmattia/niv", 6 6 "owner": "nmattia", 7 7 "repo": "niv", 8 - "rev": "e0ca65c81a2d7a4d82a189f1e23a48d59ad42070", 9 - "sha256": "1pq9nh1d8nn3xvbdny8fafzw87mj7gsmp6pxkdl65w2g18rmcmzx", 8 + "rev": "9cb7ef336bb71fd1ca84fc7f2dff15ef4b033f2a", 9 + "sha256": "1ajyqr8zka1zlb25jx1v4xys3zqmdy3prbm1vxlid6ah27a8qnzh", 10 10 "type": "tarball", 11 - "url": "https://github.com/nmattia/niv/archive/e0ca65c81a2d7a4d82a189f1e23a48d59ad42070.tar.gz", 11 + "url": "https://github.com/nmattia/niv/archive/9cb7ef336bb71fd1ca84fc7f2dff15ef4b033f2a.tar.gz", 12 12 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz" 13 13 }, 14 14 "nixpkgs": { ··· 17 17 "homepage": null, 18 18 "owner": "NixOS", 19 19 "repo": "nixpkgs", 20 - "rev": "16105403bdd843540cbef9c63fc0f16c1c6eaa70", 21 - "sha256": "0sl6hsxlh14kcs38jcra908nvi5hd8p8hlim3lbra55lz0kd9rcl", 20 + "rev": "1882c6b7368fd284ad01b0a5b5601ef136321292", 21 + "sha256": "0zg7ak2mcmwzi2kg29g4v9fvbvs0viykjsg2pwaphm1fi13s7s0i", 22 22 "type": "tarball", 23 - "url": "https://github.com/NixOS/nixpkgs/archive/16105403bdd843540cbef9c63fc0f16c1c6eaa70.tar.gz", 23 + "url": "https://github.com/NixOS/nixpkgs/archive/1882c6b7368fd284ad01b0a5b5601ef136321292.tar.gz", 24 + "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz" 25 + }, 26 + "old-packages": { 27 + "branch": "nixos-21.05", 28 + "description": "Nix Packages collection", 29 + "homepage": "", 30 + "owner": "NixOS", 31 + "repo": "nixpkgs", 32 + "rev": "0fd9ee1aa36ce865ad273f4f07fdc093adeb5c00", 33 + "sha256": "1mr2qgv5r2nmf6s3gqpcjj76zpsca6r61grzmqngwm0xlh958smx", 34 + "type": "tarball", 35 + "url": "https://github.com/NixOS/nixpkgs/archive/0fd9ee1aa36ce865ad273f4f07fdc093adeb5c00.tar.gz", 24 36 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz" 25 37 } 26 38 }
+46 -20
nix/sources.nix
··· 6 6 # The fetchers. fetch_<type> fetches specs of type <type>. 7 7 # 8 8 9 - fetch_file = pkgs: spec: 10 - if spec.builtin or true then 11 - builtins_fetchurl { inherit (spec) url sha256; } 12 - else 13 - pkgs.fetchurl { inherit (spec) url sha256; }; 9 + fetch_file = pkgs: name: spec: 10 + let 11 + name' = sanitizeName name + "-src"; 12 + in 13 + if spec.builtin or true then 14 + builtins_fetchurl { inherit (spec) url sha256; name = name'; } 15 + else 16 + pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; 14 17 15 18 fetch_tarball = pkgs: name: spec: 16 19 let 17 - ok = str: ! builtins.isNull (builtins.match "[a-zA-Z0-9+-._?=]" str); 18 - # sanitize the name, though nix will still fail if name starts with period 19 - name' = stringAsChars (x: if ! ok x then "-" else x) "${name}-src"; 20 + name' = sanitizeName name + "-src"; 20 21 in 21 22 if spec.builtin or true then 22 23 builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 23 24 else 24 25 pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 25 26 26 - fetch_git = spec: 27 - builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; }; 27 + fetch_git = name: spec: 28 + let 29 + ref = 30 + if spec ? ref then spec.ref else 31 + if spec ? branch then "refs/heads/${spec.branch}" else 32 + if spec ? tag then "refs/tags/${spec.tag}" else 33 + abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; 34 + in 35 + builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; }; 28 36 29 37 fetch_local = spec: spec.path; 30 38 ··· 40 48 # Various helpers 41 49 # 42 50 51 + # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 52 + sanitizeName = name: 53 + ( 54 + concatMapStrings (s: if builtins.isList s then "-" else s) 55 + ( 56 + builtins.split "[^[:alnum:]+._?=-]+" 57 + ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) 58 + ) 59 + ); 60 + 43 61 # The set of packages used when specs are fetched using non-builtins. 44 - mkPkgs = sources: 62 + mkPkgs = sources: system: 45 63 let 46 64 sourcesNixpkgs = 47 - import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {}; 65 + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; 48 66 hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 49 67 hasThisAsNixpkgsPath = <nixpkgs> == ./.; 50 68 in ··· 64 82 65 83 if ! builtins.hasAttr "type" spec then 66 84 abort "ERROR: niv spec ${name} does not have a 'type' attribute" 67 - else if spec.type == "file" then fetch_file pkgs spec 85 + else if spec.type == "file" then fetch_file pkgs name spec 68 86 else if spec.type == "tarball" then fetch_tarball pkgs name spec 69 - else if spec.type == "git" then fetch_git spec 87 + else if spec.type == "git" then fetch_git name spec 70 88 else if spec.type == "local" then fetch_local spec 71 89 else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 72 90 else if spec.type == "builtin-url" then fetch_builtin-url name ··· 80 98 saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; 81 99 ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; 82 100 in 83 - if ersatz == "" then drv else ersatz; 101 + if ersatz == "" then drv else 102 + # this turns the string into an actual Nix path (for both absolute and 103 + # relative paths) 104 + if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; 84 105 85 106 # Ports of functions for older nix versions 86 107 ··· 98 119 99 120 # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 100 121 stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 122 + concatMapStrings = f: list: concatStrings (map f list); 101 123 concatStrings = builtins.concatStringsSep ""; 124 + 125 + # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 126 + optionalAttrs = cond: as: if cond then as else {}; 102 127 103 128 # fetchTarball version that is compatible between all the versions of Nix 104 - builtins_fetchTarball = { url, name, sha256 }@attrs: 129 + builtins_fetchTarball = { url, name ? null, sha256 }@attrs: 105 130 let 106 131 inherit (builtins) lessThan nixVersion fetchTarball; 107 132 in 108 133 if lessThan nixVersion "1.12" then 109 - fetchTarball { inherit name url; } 134 + fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 110 135 else 111 136 fetchTarball attrs; 112 137 113 138 # fetchurl version that is compatible between all the versions of Nix 114 - builtins_fetchurl = { url, sha256 }@attrs: 139 + builtins_fetchurl = { url, name ? null, sha256 }@attrs: 115 140 let 116 141 inherit (builtins) lessThan nixVersion fetchurl; 117 142 in 118 143 if lessThan nixVersion "1.12" then 119 - fetchurl { inherit url; } 144 + fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 120 145 else 121 146 fetchurl attrs; 122 147 ··· 135 160 mkConfig = 136 161 { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null 137 162 , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) 138 - , pkgs ? mkPkgs sources 163 + , system ? builtins.currentSystem 164 + , pkgs ? mkPkgs sources system 139 165 }: rec { 140 166 # The sources, i.e. the attribute set of spec name to spec 141 167 inherit sources;
+1 -1
shell.nix
··· 10 10 11 11 # Dev Tools 12 12 pkgs.curl 13 - pkgs.devd 14 13 pkgs.just 14 + pkgs.simple-http-server 15 15 pkgs.watchexec 16 16 17 17 # Language Specific
+76 -8
src/Applications/Brain/User/State.elm
··· 15 15 import Return.Ext as Return 16 16 import Settings 17 17 import Sources.Encoding as Sources 18 + import Task 18 19 import Task.Extra exposing (do) 20 + import Time 19 21 import Tracks exposing (Track) 20 22 import Tracks.Encoding as Tracks 21 23 import Tuple3 22 24 import Url exposing (Url) 23 25 import Url.Ext as Url 24 26 import User.Layer as User exposing (..) 27 + import User.Layer.Methods.Dropbox as Dropbox 25 28 import User.Layer.Methods.Fission as Fission 26 29 import Webnative 27 30 ··· 157 160 158 161 UpdateEncryptionKey a -> 159 162 updateEncryptionKey a 163 + 164 + ----------------------------------------- 165 + -- 📭 Other 166 + ----------------------------------------- 167 + RefreshedDropboxTokens a b c -> 168 + refreshedDropboxTokens a b c 160 169 161 170 162 171 ··· 420 429 in 421 430 case model.authMethod of 422 431 -- 🚀 423 - Just (Dropbox { accessToken, refreshToken }) -> 424 - [ ( "file", file ) 425 - , ( "token", Json.string accessToken ) 426 - ] 427 - |> Json.object 428 - |> Alien.broadcast Alien.AuthDropbox 429 - |> Ports.requestDropbox 430 - |> return model 432 + Just (Dropbox { accessToken, expiresAt, refreshToken }) -> 433 + let 434 + currentTime = 435 + Time.posixToMillis model.currentTime // 1000 436 + 437 + currentTimeWithOffset = 438 + -- We add 60 seconds here because we only get the current time every minute, 439 + -- so there's always the chance the "current time" is 1-60 seconds behind. 440 + currentTime + 60 441 + in 442 + -- If the access token is expired 443 + if currentTimeWithOffset >= expiresAt then 444 + refreshToken 445 + |> Dropbox.refreshAccessToken 446 + |> Task.attempt 447 + (\result -> 448 + case result of 449 + Ok tokens -> 450 + bit 451 + |> RetrieveHypaethralData 452 + |> RefreshedDropboxTokens 453 + { currentTime = currentTime 454 + , refreshToken = refreshToken 455 + } 456 + tokens 457 + |> UserMsg 458 + 459 + Err err -> 460 + err 461 + |> Alien.report Alien.ReportError 462 + |> Ports.toUI 463 + |> Cmd 464 + ) 465 + |> return model 466 + 467 + else 468 + [ ( "file", file ) 469 + , ( "token", Json.string accessToken ) 470 + ] 471 + |> Json.object 472 + |> Alien.broadcast Alien.AuthDropbox 473 + |> Ports.requestDropbox 474 + |> return model 431 475 432 476 Just (Fission params) -> 433 477 filename ··· 758 802 terminate NotAuthenticated model 759 803 760 804 805 + refreshedDropboxTokens : 806 + { currentTime : Int, refreshToken : String } 807 + -> Dropbox.Tokens 808 + -> User.Msg 809 + -> Manager 810 + refreshedDropboxTokens { currentTime, refreshToken } tokens msg model = 811 + { accessToken = tokens.accessToken 812 + , expiresAt = currentTime + tokens.expiresIn 813 + , refreshToken = refreshToken 814 + } 815 + |> Dropbox 816 + |> (\m -> saveMethod m model) 817 + |> andThen (update msg) 818 + 819 + 761 820 retrieveMethod : Manager 762 821 retrieveMethod = 763 822 Alien.AuthMethod 764 823 |> Alien.trigger 765 824 |> Ports.requestCache 766 825 |> Return.communicate 826 + 827 + 828 + saveMethod : Method -> Manager 829 + saveMethod method model = 830 + method 831 + |> encodeMethod 832 + |> Alien.broadcast Alien.AuthMethod 833 + |> Ports.toCache 834 + |> return { model | authMethod = Just method } 767 835 768 836 769 837
+5
src/Applications/Brain/User/Types.elm
··· 3 3 import Debouncer.Basic as Debouncer 4 4 import Json.Decode as Json 5 5 import User.Layer exposing (HypaethralBit) 6 + import User.Layer.Methods.Dropbox as Dropbox 6 7 import Webnative 7 8 8 9 ··· 60 61 ----------------------------------------- 61 62 | RemoveEncryptionKey 62 63 | UpdateEncryptionKey Json.Value 64 + ----------------------------------------- 65 + -- 📭 Other 66 + ----------------------------------------- 67 + | RefreshedDropboxTokens { currentTime : Int, refreshToken : String } Dropbox.Tokens Msg
+2 -1
src/Applications/UI/Authentication/State.elm
··· 21 21 import Return exposing (andThen, return) 22 22 import SHA 23 23 import String.Ext as String 24 + import Time 24 25 import Tracks 25 26 import UI.Authentication.ContextMenu as Authentication 26 27 import UI.Authentication.Types as Authentication exposing (..) ··· 328 329 |> NewEncryptionKeyScreen 329 330 (Dropbox 330 331 { accessToken = tokens.accessToken 331 - , expiresIn = tokens.expiresIn 332 + , expiresAt = Time.posixToMillis model.currentTime // 1000 + tokens.expiresIn 332 333 , refreshToken = refreshToken 333 334 } 334 335 )
+1 -1
src/Applications/UI/Authentication/View.elm
··· 287 287 , outOfOrder = False 288 288 } 289 289 , choiceButton 290 - { action = TriggerExternalAuth (Dropbox { accessToken = "", expiresIn = 0, refreshToken = "" }) "" 290 + { action = TriggerExternalAuth (Dropbox { accessToken = "", expiresAt = 0, refreshToken = "" }) "" 291 291 , icon = \_ _ -> Svg.map never UI.Svg.Elements.dropboxLogo 292 292 , infoLink = Just "https://dropbox.com/" 293 293 , label = "Dropbox"
+4 -4
src/Library/User/Layer.elm
··· 37 37 38 38 39 39 type Method 40 - = Dropbox { accessToken : String, expiresIn : Int, refreshToken : String } 40 + = Dropbox { accessToken : String, expiresAt : Int, refreshToken : String } 41 41 | Fission { initialised : Bool } 42 42 | Ipfs { apiOrigin : String } 43 43 | Local ··· 143 143 Just 144 144 (Dropbox 145 145 { accessToken = a 146 - , expiresIn = Maybe.withDefault 0 (String.toInt e) 146 + , expiresAt = Maybe.withDefault 0 (String.toInt e) 147 147 , refreshToken = r 148 148 } 149 149 ) ··· 167 167 methodToString : Method -> String 168 168 methodToString method = 169 169 case method of 170 - Dropbox { accessToken, expiresIn, refreshToken } -> 170 + Dropbox { accessToken, expiresAt, refreshToken } -> 171 171 String.join 172 172 methodSeparator 173 173 [ "DROPBOX" 174 174 , accessToken 175 - , String.fromInt expiresIn 175 + , String.fromInt expiresAt 176 176 , refreshToken 177 177 ] 178 178
+34
src/Library/User/Layer/Methods/Dropbox.elm
··· 2 2 3 3 import Common 4 4 import Http 5 + import Http.Ext as Http 6 + import Http.Extras as Http 5 7 import Json.Decode as Json 8 + import Task exposing (Task) 6 9 import Url exposing (Url) 7 10 8 11 ··· 83 86 } 84 87 ) 85 88 |> Http.post 89 + 90 + 91 + refreshAccessToken : String -> Task String Tokens 92 + refreshAccessToken refreshToken = 93 + [ ( "client_id", clientId ) 94 + , ( "client_secret", clientSecret ) 95 + , ( "refresh_token", refreshToken ) 96 + , ( "grant_type", "refresh_token" ) 97 + ] 98 + |> Common.queryString 99 + |> String.append "https://api.dropboxapi.com/oauth2/token" 100 + |> (\u -> 101 + { method = "POST" 102 + , headers = [] 103 + , url = u 104 + , body = Http.emptyBody 105 + , resolver = 106 + Http.stringResolver 107 + (\resp -> 108 + resp 109 + |> Http.responseToString 110 + |> Result.mapError Http.errorToString 111 + |> Result.andThen 112 + (Json.decodeString tokensDecoder 113 + >> Result.mapError Json.errorToString 114 + ) 115 + ) 116 + , timeout = Nothing 117 + } 118 + ) 119 + |> Http.task