From 35a3a54343c4d102d98fc24472b8d693876f8a23 Mon Sep 17 00:00:00 2001 From: Steven Vandevelde Date: Sat, 20 Apr 2019 23:15:06 +0200 Subject: [PATCH] Add support for Textile --- src/Applications/Brain.elm | 6 + src/Applications/Brain/Authentication.elm | 17 +++ src/Applications/Brain/Ports.elm | 10 +- src/Applications/UI/Authentication.elm | 55 ++++++-- src/Applications/UI/Settings.elm | 3 + src/Javascript/Workers/brain.js | 55 ++++++++ src/Javascript/textile.js | 158 ++++++++++++++++++++++ src/Library/Alien.elm | 7 + src/Library/Authentication.elm | 11 ++ src/Library/Sources/Services/AmazonS3.elm | 2 +- 10 files changed, 307 insertions(+), 17 deletions(-) create mode 100644 src/Javascript/textile.js diff --git a/src/Applications/Brain.elm b/src/Applications/Brain.elm index f1bed3ac..901acd42 100644 --- a/src/Applications/Brain.elm +++ b/src/Applications/Brain.elm @@ -342,6 +342,9 @@ translateAlienData event = Just Alien.AuthRemoteStorage -> AuthenticationMsg (Authentication.HypaethralDataRetrieved event.data) + Just Alien.AuthTextile -> + AuthenticationMsg (Authentication.HypaethralDataRetrieved event.data) + Just Alien.FabricateSecretKey -> AuthenticationMsg Authentication.SecretKeyFabricated @@ -417,6 +420,9 @@ translateAlienError event err = Just Alien.AuthRemoteStorage -> report Alien.AuthRemoteStorage "I couldn't decrypt your data, maybe you used the wrong passphrase?" + Just Alien.AuthTextile -> + report Alien.AuthTextile "Something went wrong regarding Textile. Maybe Textile isn't running?" + Just tag -> report tag err diff --git a/src/Applications/Brain/Authentication.elm b/src/Applications/Brain/Authentication.elm index 886eb801..9c5b47e4 100644 --- a/src/Applications/Brain/Authentication.elm +++ b/src/Applications/Brain/Authentication.elm @@ -182,6 +182,14 @@ update msg model = |> Ports.requestRemoteStorage |> Return.commandWithModel model + Just (Textile { apiOrigin }) -> + [ ( "apiOrigin", Json.string apiOrigin ) + ] + |> Json.object + |> Alien.broadcast Alien.AuthTextile + |> Ports.requestTextile + |> Return.commandWithModel model + -- ✋ Nothing -> return model @@ -250,6 +258,15 @@ update msg model = |> Ports.toRemoteStorage |> Return.commandWithModel model + Just (Textile { apiOrigin }) -> + [ ( "apiOrigin", Json.string apiOrigin ) + , ( "data", json ) + ] + |> Json.object + |> Alien.broadcast Alien.AuthTextile + |> Ports.toTextile + |> Return.commandWithModel model + -- ✋ Nothing -> return model diff --git a/src/Applications/Brain/Ports.elm b/src/Applications/Brain/Ports.elm index a80f7ab6..7c421d61 100644 --- a/src/Applications/Brain/Ports.elm +++ b/src/Applications/Brain/Ports.elm @@ -1,4 +1,4 @@ -port module Brain.Ports exposing (fabricateSecretKey, fromAlien, receiveSearchResults, receiveTags, removeCache, removeIpfs, requestCache, requestIpfs, requestRemoteStorage, requestSearch, requestTags, toCache, toIpfs, toRemoteStorage, toUI, updateSearchIndex) +port module Brain.Ports exposing (fabricateSecretKey, fromAlien, receiveSearchResults, receiveTags, removeCache, removeIpfs, requestCache, requestIpfs, requestRemoteStorage, requestSearch, requestTags, requestTextile, toCache, toIpfs, toRemoteStorage, toTextile, toUI, updateSearchIndex) import Alien import Json.Encode as Json @@ -33,13 +33,19 @@ port requestSearch : String -> Cmd msg port requestTags : ContextForTags -> Cmd msg +port requestTextile : Alien.Event -> Cmd msg + + port toCache : Alien.Event -> Cmd msg +port toIpfs : Alien.Event -> Cmd msg + + port toRemoteStorage : Alien.Event -> Cmd msg -port toIpfs : Alien.Event -> Cmd msg +port toTextile : Alien.Event -> Cmd msg port toUI : Alien.Event -> Cmd msg diff --git a/src/Applications/UI/Authentication.elm b/src/Applications/UI/Authentication.elm index 6b335276..928df833 100644 --- a/src/Applications/UI/Authentication.elm +++ b/src/Applications/UI/Authentication.elm @@ -13,13 +13,14 @@ import Conditional exposing (..) import Css exposing (pct, px, solid, transparent) import Html.Events.Extra.Mouse as Mouse import Html.Styled as Html exposing (Html, a, button, em, fromUnstyled, img, span, text) -import Html.Styled.Attributes as Attributes exposing (attribute, css, href, placeholder, src, style, title, width) +import Html.Styled.Attributes as Attributes exposing (attribute, css, href, placeholder, src, style, title, value, width) import Html.Styled.Events exposing (onClick, onSubmit) import Json.Encode import Material.Icons.Av as Icons import Material.Icons.Navigation as Icons import Return3 as Return exposing (..) import SHA +import String.Ext as String import Svg exposing (Svg) import Tachyons.Classes as T import UI.Kit @@ -47,12 +48,19 @@ passphraseLengthErrorMessage = type Model = Authenticated Method - | InputScreen Method { input : String, placeholder : String, question : String } + | InputScreen Method Question | NewEncryptionKeyScreen Method (Maybe String) | UpdateEncryptionKeyScreen Method (Maybe String) | Unauthenticated +type alias Question = + { placeholder : String + , question : String + , value : String + } + + initialModel : Url -> Model initialModel url = case String.split "/" (String.dropLeft 1 url.path) of @@ -120,7 +128,7 @@ type Msg ----------------------------------------- -- More Input ----------------------------------------- - | AskForInput Method { placeholder : String, question : String } + | AskForInput Method Question | Input String | ConfirmInput @@ -238,9 +246,9 @@ update msg model = -- More Input ----------------------------------------- AskForInput method opts -> - { input = "" - , placeholder = opts.placeholder + { placeholder = opts.placeholder , question = opts.question + , value = opts.value } |> InputScreen method |> return @@ -248,22 +256,33 @@ update msg model = Input string -> case model of InputScreen method opts -> - return (InputScreen method { opts | input = string }) + return (InputScreen method { opts | value = string }) m -> return m ConfirmInput -> case model of - InputScreen method { input } -> + InputScreen (RemoteStorage r) { value } -> addReply - (ExternalAuth method input) + (ExternalAuth (RemoteStorage r) value) (return model) + InputScreen (Textile t) { value } -> + { t | apiOrigin = String.chopEnd "/" value } + |> Textile + |> SignIn + |> updateWithModel model + _ -> return model +updateWithModel : Model -> Msg -> Return Model Msg Reply +updateWithModel model msg = + update msg model + + hashPassphrase : String -> String hashPassphrase phrase = phrase @@ -418,9 +437,10 @@ choicesScreen = , choiceButton { action = AskForInput - (Authentication.RemoteStorage { userAddress = "", token = "" }) + (RemoteStorage { userAddress = "", token = "" }) { placeholder = "username@5apps.com" , question = "What's your user address?" + , value = "" } , icon = \_ _ -> Svg.map never UI.Svg.Elements.remoteStorageLogo , isLast = False @@ -428,11 +448,17 @@ choicesScreen = , outOfOrder = False } , choiceButton - { action = Bypass + { action = + AskForInput + (Textile { apiOrigin = "" }) + { placeholder = "http://localhost:40600" + , question = "Where's your Textile API located?" + , value = "http://localhost:40600" + } , icon = \_ _ -> Svg.map never UI.Svg.Elements.textileLogo , isLast = False , label = "Textile" - , outOfOrder = True + , outOfOrder = False } -- More options @@ -525,8 +551,8 @@ encryptionKeyScreen msg = -- INPUT SCREEN -inputScreen : { question : String, input : String, placeholder : String } -> Html Msg -inputScreen opts = +inputScreen : Question -> Html Msg +inputScreen question = slab Html.form [ onSubmit ConfirmInput ] @@ -534,8 +560,9 @@ inputScreen opts = , T.flex_column ] [ UI.Kit.textFieldAlt - [ placeholder opts.placeholder + [ placeholder question.placeholder , Html.Styled.Events.onInput Input + , value question.value ] , UI.Kit.button UI.Kit.Normal diff --git a/src/Applications/UI/Settings.elm b/src/Applications/UI/Settings.elm index 87e07738..0f9e7f16 100644 --- a/src/Applications/UI/Settings.elm +++ b/src/Applications/UI/Settings.elm @@ -70,6 +70,9 @@ index model = Just (RemoteStorage _) -> text "on a RemoteStorage server." + Just (Textile _) -> + text "on Textile." + Nothing -> text "on nothing, wtf?" , lineBreak diff --git a/src/Javascript/Workers/brain.js b/src/Javascript/Workers/brain.js index c533b70d..71908212 100644 --- a/src/Javascript/Workers/brain.js +++ b/src/Javascript/Workers/brain.js @@ -280,6 +280,61 @@ app.ports.requestTags.subscribe(context => { +// Textile +// ------- + +let tt + + +function textile() { + if (!tt) { + importScripts("/textile.js") + tt = true + } +} + + +app.ports.requestTextile.subscribe(event => { + const apiOrigin = event.data.apiOrigin + + textile() + + Textile.ensureThread + (apiOrigin) + + .then(_ => Textile.getFile(apiOrigin)) + .then(f => f ? Textile.readFile(apiOrigin, f) : null) + .then(data => { + app.ports.fromAlien.send({ + tag: event.tag, + data: typeof data === "string" ? JSON.parse(data) : data, + error: null + }) + }) + + .catch(reportError(event)) +}) + + +app.ports.toTextile.subscribe(event => { + const apiOrigin = event.data.apiOrigin + const json = JSON.stringify(event.data.data) + + textile() + + Textile.ensureThread + (apiOrigin) + + .then(_ => Textile.getFile(apiOrigin)) + .then(f => f ? Textile.deleteBlock(apiOrigin, f) : null) + .then(_ => Textile.useMill(apiOrigin, json)) + .then(m => Textile.addFileToThread(apiOrigin, m)) + + .catch(reportError(event)) +}) + + + // 🔱 // -- diff --git a/src/Javascript/textile.js b/src/Javascript/textile.js new file mode 100644 index 00000000..f3ca33fb --- /dev/null +++ b/src/Javascript/textile.js @@ -0,0 +1,158 @@ +// +// Textile +// \ (•◡•) / +// +// Communicating with the Textile HTTP API. + + +const TEXTILE_THREAD = "diffuse" +const TEXTILE_BLOCK = "userdata" +const TEXTILE_SCHEMA = { + name: TEXTILE_THREAD, + pin: true, + mill: "/blob", + plaintext: false +} + + +const Textile = { + activeThreadId: undefined +} + + + +// Blocks +// ------ + +Textile.deleteBlock = (apiOrigin, file) => { + return fetch(apiOrigin + "/api/v0/blocks/" + file.block, { + method: "DELETE" + }) + .then(r => r.json()) +} + + + +// Threads +// ------- + +Textile.ensureThread = (apiOrigin) => { + if (!Textile.activeThreadId) { + return Textile.getThread(apiOrigin) + .then(thread => { + if (thread) return thread + + return Textile + .createSchema(apiOrigin) + .then(schema => Textile.createThread(apiOrigin, schema)) + }) + .then(t => { + Textile.activeThreadId = t.id + return t.id + }) + } + + return Promise.resolve(Textile.activeThreadId) +} + + +Textile.getThread = (apiOrigin) => { + return fetch(apiOrigin + "/api/v0/threads") + .then(r => r.json()) + .then(r => r.items.find(i => i.name === TEXTILE_THREAD)) +} + + +Textile.createThread = (apiOrigin, schema) => { + const headers = new Headers() + headers.append("X-Textile-Args", TEXTILE_THREAD) + headers.append("X-Textile-Opts", "schema=" + schema.hash + ",type=private,sharing=invite_only") + + return fetch(apiOrigin + "/api/v0/threads", { + headers: headers, + method: "POST" + }) + .then(r => r.json()) +} + + + +// Files +// ----- + +Textile.addFileToThread = (apiOrigin, millResult) => { + const headers = new Headers() + headers.append("Content-Type", "application/json") + + const files = {} + files[":single"] = millResult + + return fetch(apiOrigin + "/api/v0/threads/" + Textile.activeThreadId + "/files", { + body: JSON.stringify({ + items: [{ files: files }] + }), + headers: headers, + method: "POST" + }) + .then(r => r.json()) +} + + +Textile.getFile = (apiOrigin) => { + const headers = new Headers() + headers.append("X-Textile-Opts", "thread=" + Textile.activeThreadId) + + return fetch(apiOrigin + "/api/v0/files", { + headers: headers, + method: "GET" + }) + .then(r => r.json()) + .then(r => r.items.find(i => i.files[0].file.name === TEXTILE_BLOCK)) +} + + +Textile.readFile = (apiOrigin, file) => { + const headers = new Headers() + headers.append("X-Textile-Opts", "key=" + file.files[0].file.key) + + return fetch(apiOrigin + "/api/v0/ipfs/cat/" + file.files[0].file.hash, { + headers: headers, + method: "GET" + }) + .then(r => r.text()) +} + + + +// Mills +// ----- + +Textile.createSchema = (apiOrigin) => { + const headers = new Headers() + headers.append("Content-Type", "application/json") + + return fetch(apiOrigin + "/api/v0/mills/schema", { + body: JSON.stringify(TEXTILE_SCHEMA), + headers: headers, + method: "POST" + }) + .then(r => r.json()) +} + + +Textile.useMill = (apiOrigin, data) => { + const blob = new Blob([data], { type : "application/json" }) + const file = new File([blob], TEXTILE_BLOCK) + + const formData = new FormData() + formData.append("file", file) + + const headers = new Headers() + headers.append("X-Textile-Opts", "plaintext=false") + + return fetch(apiOrigin + "/api/v0/mills/blob", { + body: formData, + method: "POST" + }) + .then(r => r.json()) +} diff --git a/src/Library/Alien.elm b/src/Library/Alien.elm index 296be148..44e2aa58 100644 --- a/src/Library/Alien.elm +++ b/src/Library/Alien.elm @@ -26,6 +26,7 @@ type Tag | AuthMethod | AuthRemoteStorage | AuthSecretKey + | AuthTextile | FabricateSecretKey | SearchTracks -- from UI @@ -100,6 +101,9 @@ tagToString tag = AuthSecretKey -> "AUTH_SECRET_KEY" + AuthTextile -> + "AUTH_TEXTILE" + FabricateSecretKey -> "FABRICATE_SECRET_KEY" @@ -191,6 +195,9 @@ tagFromString string = "AUTH_SECRET_KEY" -> Just AuthSecretKey + "AUTH_TEXTILE" -> + Just AuthTextile + "FABRICATE_SECRET_KEY" -> Just FabricateSecretKey diff --git a/src/Library/Authentication.elm b/src/Library/Authentication.elm index dd56044c..a3ef0b77 100644 --- a/src/Library/Authentication.elm +++ b/src/Library/Authentication.elm @@ -20,6 +20,7 @@ type Method = Ipfs | Local | RemoteStorage { userAddress : String, token : String } + | Textile { apiOrigin : String } type alias EnclosedUserData = @@ -78,6 +79,13 @@ methodToString method = , token ] + Textile { apiOrigin } -> + String.join + methodSeparator + [ "TEXTILE" + , apiOrigin + ] + methodFromString : String -> Maybe Method methodFromString string = @@ -91,6 +99,9 @@ methodFromString string = [ "REMOTE_STORAGE", u, t ] -> Just (RemoteStorage { userAddress = u, token = t }) + [ "TEXTILE", a ] -> + Just (Textile { apiOrigin = a }) + _ -> Nothing diff --git a/src/Library/Sources/Services/AmazonS3.elm b/src/Library/Sources/Services/AmazonS3.elm index e526e087..97a6bee0 100644 --- a/src/Library/Sources/Services/AmazonS3.elm +++ b/src/Library/Sources/Services/AmazonS3.elm @@ -63,7 +63,7 @@ properties = } , { key = "host" , label = "Host (optional)" - , placeholder = "http://127.0.0.1:9000" + , placeholder = "http://localhost:9000" , password = False } ] -- 2.51.2