DEV Community

Cover image for Using Fission with Elm - Part 3: Adding authentication and using the Fission Drive
Matt "Verge" Virgin for XetiCode

Posted on • Updated on

Using Fission with Elm - Part 3: Adding authentication and using the Fission Drive

[UPDATE] The Fission team released fission-suite/webnative-elm version 7.0.0 since the original publish date of this article. This update includes a sign out function. The following sections of the article have been changed to reflect this update: Walkthrough/Authentication/'Prep work'and Walkthrough/Authentication/'Signing out'. This article no longer makes any changes to the js/index.js file as defined in Fission with Elm - Part 2.
Make sure the npm and elm-module packages for webnative-elm are at 6.1.0 and 7.0.0, respectively. To update them, run the following from the root of the project:
npm install --save-dev webnative-elm
npx elm-json install fission-suite/webnative-elm
Enter fullscreen mode Exit fullscreen mode

Prelude

This is a three-part series. In part one, I showed you how to use Vite to manage an Elm application. In part two, I showed you how to integrate the basics of Fission via the Webnative SDK into the project. In part three (this article), I will show how to add authentication and storage via the Webnative SDK to the application. This article is written assuming the developer already has a Fission account and has completed parts 1 and 2.

It should be noted that the last two articles were fairly short. Both have set the stage for this article, the pinnacle of the series. There will be much to add to our growing application, some of these additions are due to Elm being a strongly typed language.

It should also be noted that if you are using a browser that using something similar to Brave Shields in the Brave browser, you will need to disable the feature so Fission can communicate with the application.

This article will also require the use of a Fission account. If you do not yet have one, take a moment to create one now. Check Appendix A at the end of this article for steps on how to do so.

The Github repository associated with this article can be found here:

https://github.com/xeticode/fission-with-elm-demos/tree/Part-3


Intro

Welcome to the final article. Here you will be tested beyond anything you have ever experienced… Nah, this is not some epic set of trials. That being said, it is my hope that by the end of this article, you have been exposed enough to Fission to continue on in your own Fission-based applications.
It is time to add some usefulness to this application. Right now, it is like a wallpaper: good to look at and not much else. The first feature we will add is user authentication. After authentication, we will add code that will interact with the Fission Drive to write a file to storage. There are no introductions to be made this go-around, so we will review important concepts for what I am about to show you.

Things to review

  • elm ports are the medium by which an Elm application communicates with JavaScript. For more information, see Elm Ports .

  • IPFS "is a distributed system for storing and accessing files, websites, applications, and data". For more information, see IPFS .

  • Wnfs stands for Webnative File System and "is a file system built on top of the (IPFS)". For more information, see Webnative File System .

  • Fission Drive is a military-grade encrypted file system located within a distributed network. For more information, see Fission Drive .
    Let us begin!


Walkthrough

Download an updated Main.elm by running this command in the terminal from the src directory:

curl -o Main.elm https://raw.githubusercontent.com/xeticode/fission-with-elm-demos/interim/part-3/Main.elm
Enter fullscreen mode Exit fullscreen mode

This new Main.elm file lays down some new groundwork for our application. It is also divided into sections, the dividers being noticeable comments. Instead of indicating a line in the file to add or find code, I will indicate a region of the code to look. The differences between the new file versus the old one includes additions to our type alias Model, type Msg, and update function (see section: MODEL/UPDATE); a section for helper functions (see section: HELPER FUNCTIONS); and a new UI (see section: VIEW). Let’s quickly go over the new Msg variants and the Model.

Msg variants

Here are the new variants and their purposes:

  • SignIn will be associated with the login button and will initiate the login process with Fission.
  • SignOut will be associated with the logout button and will initiate the logout process with Fission.
  • UpdateNote is the variant that will update the note field in the Model. This is the value that will be written to the Wnfs.
  • SaveNote is associated with the Save Note button and will initiate the process of writing and publishing our note to the Wnfs.

Model

Here is a breakdown of the new fields in Model:

  • username will store the username of the logged-in Fission user of the current session (if you are not familiar with the Maybe type, or simply need a refresher, see Maybe).
  • logs is a list of strings and will store log entries associated with the different stages of interaction with Fission. These will be displayed on the left side of the screen. Consider these as a log that make it easier to see the processes when they occur.
  • note this will store the content that will be sent to the Wnfs.

Before we dive in

Run npm run dev from the root of the project to have the application up and running on the dev server. Once you do this, you should see this:

fission-with-elm login

At this point, you can click on the login button, but it will not take you anywhere. Its associated message, SignIn, is currently a dead end. Not to worry, the first thing we will do is implement authentication.

Authentication

Prep work

One of the reasons my meals are so simple and quick is my hungry stomach will usually override my willingness to spend time on quality meal prep. Lucky for us, this is not a food situation. Before we can sign in, we must add a few things, a metaphorical meal prep, if you will, before we can dig into authentication.

The first thing we must do is expand the GotWebnativeResponse branch in our update function. To begin, replace the current GotWebnativeResponse branch (section: MODEL/UPDATE, function: update) with the following code (be aware, the alignment may change when copying and pasting into your developing environment. You will get the red squiggles if the case statement branches are mis-aligned):

GotWebnativeResponse response ->
    case Webnative.decodeResponse tagFromString response of
        Webnative (Initialisation state) ->
            case state of 
                NotAuthorised ->
                    ( { model
                        | username = Nothing
                        , logs = "Auth: NotAuthorised" :: model.logs
                    }
                    , Cmd.none
                    )
                AuthSucceeded { username } ->
                    ( { model
                        | username = Just username
                        , logs =  "Auth: AuthSucceeded" :: model.logs
                    }
                    , Cmd.none
                    )
                AuthCancelled _ ->
                    ( { model
                        | username = Nothing
                        , logs = "Auth: AuthCancelled" :: model.logs
                    }
                    , Cmd.none
                    )
                Continuation { username } ->
                    ( { model
                        | username = Just username
                        , logs = "Auth: Continuation" :: model.logs
                    }
                    , Cmd.none
                    )

        Wnfs _ _ ->
            ( model, Cmd.none )


        Webnative (Webnative.NoArtifact SignedOut) ->
            ( { model
                | logs = "Signed out!" :: model.logs
                , username = Nothing
            }
            , Cmd.none
            )

        Webnative (Webnative.NoArtifact _) ->
            ( model
            , Cmd.none
            )

        WebnativeError err ->              
            ( { model
                | logs = Webnative.error err :: model.logs
            }
            , Cmd.none
            )

        WnfsError err ->
            ( { model
                | logs = Wnfs.error err :: model.logs
            }
            , Cmd.none
            )
Enter fullscreen mode Exit fullscreen mode

That is a lot of new code, and some of it is accompanied by our friendly neighborhood red squiggles. This is due to missing proper imports and a definition for tagFromString, which is a helper function. Let's resolve the problem starting with the imports.

Go to the import block at the top of Main.elm. Near the bottom of the import block at the top of the file, we have our Webnative import and it is currently exposing Artifact(..) and all its variants (represented by the (..)). Replace this import with:

import Webnative exposing (Artifact(..), NoArtifact(..), DecodedResponse(..), State(..))
Enter fullscreen mode Exit fullscreen mode

Adding these to the exposing list will allow us access to the DecodedResponse and State types from the Webnative-elm module (see DecodedResponse and State).

Now we will define tagFromString. In the HELPER FUNCTION section, add the following code:

tagFromString : String -> Result String Tag
tagFromString str =
    case str of
         _ ->
            Err <| "Invalid tag: " ++ str
Enter fullscreen mode Exit fullscreen mode

Webnative-elm utilizes tags to differentiate between different requests and connect the appropriate response. For example, if I sent a webnativeRequest tagged with “StudioGhibli”, the associated webnativeResponse will have the same tag. We can then create a branch within our GotWebnativeResponse branch’s case statement and dictate how to handle the response.

Now, both webnativeRequest and webnativeResponse require the tag to be a string not an Elm type. That is why we need such helper functions as tagFromString and its companion tagToString. Extra code on our part, yes. This is a byproduct of Elm, not the webnative-elm wrapper. In fact, this is great design by the people behind the wrapper because it couples the Webnative SDK with the strengths of Elm.

Speaking of an Elm type for our tags, we now have another red squiggle to take care of, this time on Tag. Add the following code between the type Msg and update function:

type Tag
    = ChangeMeLater
Enter fullscreen mode Exit fullscreen mode

This is where we define our different webnativeRequest tags. To reiterate, we will return to this later when it is time to utilize our Tag type. We need to define Tag now so we can proceed with the rest of what the GotWebnativeResponse has to offer.
We are now ready to officially sign in!

Signing in

This is where having a Fission account is crucial. If you have yet to do so, stop and take a moment to create a Fission account (see Appendix A at the end of this article for details).

The application is growing up so fast. To implement sign in, go to the SignIn branch of the update function (section: MODEL/UPDATE) and replace Cmd.none with the following code:

permissions
    |> Webnative.redirectToLobby Webnative.CurrentUrl
    |> webnativeRequest
Enter fullscreen mode Exit fullscreen mode

This will take our permissions (defined in part 2 of this series) and pass it to the Webnative function redirectToLobby.

Time to see it in action! Refresh the app in the browser and click "Login"! This will take us to a Fission authorization lobby. You will see something like this:

Fission Auth Lobby

This lobby utilizes the appPermissions and fsPermissions we defined at the beginning of the Main.elm file. If there are no defined fsPermissions (like with this application), the only permission request will be for the file path generated for the application. Once you grant permission, authentication will finish.

Rather than using traditional username and password authentication (such as OAuth), Fission uses cryptographic keys and stores them in a browser you authorize with your Fission account (you can authorize multiple browsers on multiple machines). See Webnative Authentication for more information.

Once you have logged in and granted permission for the app to access Trillian/fission-with-elm (aka creator/name as specified in appPermissions located in the beginning of section: FISSION SETUP) in your Fission Drive, you will see this simple web page:

Fission Demo App

Before we implement signing out, I want to give a quick walkthrough of this application.

The UI

Under the “Fission with Elm” title, there are two oval-esque elements. The one on the left should display the Fission logo and your Fission username, and the one on the right is a logout button.

On the far-left side of the screen, you will see “Auth: AuthSucceeded”. When you save a note, more logs will appear here, allowing you to see the process of writing to a Fission Drive.

The center of the screen has a textbox where you can write a note and a “Save Note” button below it that will write the note to your Fission Drive. This functionality will be implemented after sign out.

In the bottom right corner, there should be a little blue rectangle which displays the Elm logo and a number. This is a button that will open the Elm debugging window.

Anyways, on to the implementation of signing out!

Signing out

The webnativeRequest port supports two types methods of signing out: Webnative.leave and Webnative.signOut (see Webnative, in the section named "Authentication").

Webnative.leave will redirect the user to a Fission logout lobby, which looks like this:

Fission Logout Lobby

Webnative.signOut will not redirect the user to a Fission logout lobby.

To add sign out functionality, go to the SignOut branch in the update function and replace the current version of it with:

SignOut ->
    ( { model
        | username = Nothing
        , logs = "SignOut" :: model.logs
    }
    , Webnative.signOut
        |> webnativeRequest
    )
Enter fullscreen mode Exit fullscreen mode

Bah-duh bing, bah-duh boom! We can now sign in and sign out! Time to see it in action. Go ahead and test the app by logging in and logging out! Pretty cool!

Next up on the docket is writing to a Fission Drive, let us dive in!

Data Storage

Writing to the Wnfs is a two-step process:

  1. Write the data to a file and save it in the Wnfs in the local browser storage
  2. Once saved locally, replicate and encrypt the data and store it in the Wnfs on the IPFS network

The following diagram depicts the write process within the context of our application:

Save to Wnfs Process

The three circles in the elm app section represent Msg and Tag variants and are milestones in the writing process. We begin by calling the SaveNote Msg variant and end in handling a tagged webnativeResponse with our PublishNote Tag variant.
We need to do the following to replicate this process in our application:

  • type Tag
    • Finish defining the variants for the Tag type. Once this is done, we can tag requests and appropriately handle the associated responses.
  • Helper functions
    • Write several helper functions to help with encoding the data and using tags.
  • update function
    • Update various case statement branches to accommodate saving a note.

Once we get through that, our application will be complete. Let’s get started!

type Tag

This is our current state of our Tag type:

type Tag
    = ChangeMeLater
Enter fullscreen mode Exit fullscreen mode

We need to swap the current variant from ChangeMeLater with two new variants: WriteNote and PublishNote. This will allow us to handle responses to the write and publish requests.

Change the type Tag to look like this:

type Tag
    = WriteNote
    | PublishNote
Enter fullscreen mode Exit fullscreen mode

Helper functions

Two of these helper functions (tagFromString and tagToString) are needed simply because Elm is a strongly typed language and does not have a native way to convert type variants to strings. This gives us extra work to do before we can accomplish our goal. Fortunately for this application, our application is small and will not require much of this kind of code.

The other two helper functions (encode and save) are for encoding our note and preparing it as a Webnative.Request.

tagFromString

We can now properly finish the implementation of this function! This will allow us to convert our Type variants to strings. Replace the half-baked tagFromString with the following full-baked code:

tagFromString : String -> Result String Tag
tagFromString str =
    case str of
        "WriteNote" ->
            Ok WriteNote

        "PublishNote" ->
            Ok PublishNote
        _ ->
            Err <| "Invalid tag: " ++ str
Enter fullscreen mode Exit fullscreen mode

tagToString

As promised, here is the second helper function for the tags. This will convert a Tag into a String so we may add it to the webnativeRequest. Make sure to get the spelling right AND that it matches the spellings used in tagFromString function. Add this code to the HELPERS section of the file:

tagToString : Tag -> String
tagToString tag =
    case tag of 
        WriteNote ->
            "WriteNote"
        PublishNote ->
            "PublishNote"
Enter fullscreen mode Exit fullscreen mode

encode

This application will write our note to a JSON file. This next helper function will JSON encode our note. Add the following code to the HELPERS section of the file:

encode : String -> Json.Encode.Value
encode note =
    Json.Encode.object
        [ ("note", Json.Encode.string note)            
        ]
Enter fullscreen mode Exit fullscreen mode

save

This function will take the pieces we have made in this portion of the walkthrough and use them to initiate the writing process. It will first take our note and encode it to a JSON value, prettify it, and then write it to a specified file with an associated Tag. Add the following code to the HELPERS section of the file:

save : String -> Webnative.Request
save note =
    note    
        |> encode
        |> Json.Encode.encode 0
        |> Wnfs.writeUtf8
            Wnfs.Private
            { path = Path.file [ "Apps", "Trillian", "fission-with-elm", "notes", "note.json"]
            , tag = tagToString WriteNote
            }
Enter fullscreen mode Exit fullscreen mode

update Function

The prep work has been laid, now we need to establish some milestones. The writing process will begin when we click the “Save Note” button, which will send a webnativeRequest tagged with WriteNote. We will then handle the associated response and then send one more webnativeRequest tagged with PublishNote. The final step will be handling the associated response.

SaveNote msg

To begin, we need to change the Cmd.none in our SaveNote update branch to a Cmd that calls our webnativeRequest port. Replace Cmd.none with the following:

webnativeRequest <| save model.note
Enter fullscreen mode Exit fullscreen mode

This will begin our interaction with Webnative by writing our note to a file and then writing it to our Wnfs. The next step is to handle the response.

WriteNote handler

Our WriteNote response will be caught by our subscription to the webnativeResponse port and be redirected to the GotWebnativeResponse Msg variant. Add the following branch to GotWebnativeResponse, just before the Wnfs _ _ branch:

Wnfs WriteNote _ ->
    ( { model
        | logs = "Note Written..." :: model.logs
    }
    , ({ tag = tagToString PublishNote }
        |> Wnfs.publish
        |> webnativeRequest)
    )
Enter fullscreen mode Exit fullscreen mode

This code block will add an entry to our model’s list of logs and then chain a new request to publish our updated Wnfs.

PublishNote handler

Our PublishNote response will again be caught by our subscription to the webnativeResponse port and be redirected to the GotWebnativeResponse Msg variant. In GotWebnativeResponse, replace the Wnfs _ _ branch with the following:

Wnfs PublishNote _ ->
    ( {model
        | logs = "Note Published to Fission Drive!" :: model.logs
        }
    , Cmd.none
    )
Enter fullscreen mode Exit fullscreen mode

This code block will add an entry to our model’s list of logs and then do nothing because the process of writing our note to the Wnfs has reached its end. Your file will then be accessible from your Fission Drive! It may take a few seconds to propagate the data to the Drive, so be aware of that.

The Finished Project

Let’s do a quick walk through of writing to your Fission Drive. Begin by opening a new window or tab, and navigate to your Fission Drive. Once there, navigate to the fission-with-elm directory (Apps > Trillian > fission-with-elm). This directory should be empty (unless you saved a note between finishing the app and now).

Refresh the app page in the browser to make sure your browser has loaded the latest compiled version of the app from the server. Now, navigate to the “Fission with Elm” application and add some text to the input field in the app. It can be as quick as “asdf” or as creative as “test”, it is up to you. Once you have done so, click the “Save Note” button and then navigate back to your Fission Drive. As a reminder, it can take a few seconds for the new note to propagate to your Drive. Once a note has been written, there should appear a new directory within the fission-with-elm directory called notes. Enter that directory and you will see a JSON file entitled note. If you click on it, you will see the contents of the file in a preview window located on the right side of the screen. Like so:

Fission Note Preview

Voilà!

The End of a Trilogy

There is not much reading left now. And I can guarantee this ending is not as satisfying as the end of watching the Lord of the Rings trilogy for the umpteenth time… But hey, we made it. The “Fission with Elm” demonstration app is now fully functioning, and you now have a better grip on the basics of integrating Fission into your code.

In the first part of this series, I showed you how to set up Vite to manage Elm applications. This was the foundation of the application. In the second part, I showed you how to integrate Fission into an Elm project via the webnative-elm module. This highlighted the structure of Fission integration. This third part took what was established in the previous two parts and demonstrated some of the processes of Fission.

I hope you have found this series of articles helpful as you begin to build Fission-based applications in Elm!

Feel free to email any questions you may have to info@xeticode.com and my team and I will answer as promptly as we can :)


Appendix A

Creating a Fission account

  1. Go to https://fission.codes/
  2. Click on the "Sign up" button (located in the top portion of the screen)
  3. Click "Create account"
  4. Enter an email and desired username into the fields
  5. Click "Get started"
  6. Follow the instructions on your screen to link your account to another device!

Top comments (2)

Collapse
 
verge_729 profile image
Matt "Verge" Virgin

Thanks for pointing this out @improvement_addict !
It's been a while since I checked in on Fission related things

Collapse
 
improvement_addict profile image
Improvement Addict

Looks like this guide does not work anymore, as webnative-elm is abandoned :(