Code Review on Promise Implementation


#1

Hey folks, I have this working code that I would love to get right. I feel like I could have done all this with a something much simpler.

The purpose is to get back a jwtToken and pass to another function. Here a auth client, obviously. Any feedback would be appreciated.

type idTokenT = {
  idToken,
} and idToken = {jwtToken: string};

module Decode = {
  type t = idTokenT;
  let decodeJwt = json => Json.Decode.{jwtToken: json |> field("jwtToken",string)}
  let decodeToken = json => Json.Decode.{
        idToken: json |> field("idToken", decodeJwt)
  };
  let decodeResponse = json => decodeToken(json);
  let response = (json: string) : Belt.Result.t(t, string) =>
    try (json |> Json.parseOrRaise |> decodeResponse |> (u => Belt.Result.Ok(u))) {
    | Json.Decode.DecodeError(decodeError) => Belt.Result.Error(decodeError)
    | e => Belt.Result.Error(e |> Js.String.make)
    };
};

type auth;
[@bs.module "@aws-amplify/auth/lib"] external auth: auth = "default";
[@bs.send] external getCurrentSession:(auth,unit)=> Js.Promise.t('a) = "currentSession";

[@genType]
let getJwtToken = () => {
    open PromiseMonad;
    getCurrentSession(auth,())
    >>= (response => {
        let jwtToken = Decode.response(response);
    Js.log(jwtToken);
    return(jwtToken);
  })
// >>| (err => {
//     let errMsg = err |> Js.Json.stringifyAny |> Js.Option.getWithDefault("");
//     Js.log2("Failure!!", err);
//     return(err)
//   });
};

#2

I would recommend starting simple. From what I can tell, you’re trying to do something like https://aws-amplify.github.io/docs/js/authentication#retrieve-current-session :

import { Auth } from 'aws-amplify';

Auth.currentSession()
  .then(data => console.log(data))
  .catch(err => console.log(err));

The Reason equivalent should look like this:

let _ = ()
  |> AwsAmplify.Auth.currentSession
  |> Js.Promise.then_(data =>
    data |> Js.log |> Js.Promise.resolve)
  |> Js.Promise.catch(error =>
    error |> Js.log |> Js.Promise.resolve);

The main binding you should write is currentSession. From the above documentation, that returns a CognitoUserSession object promise. The binding should look like this:

module CognitoUserSession = {
  type t = {.
    "accessToken": string, /* I'm guessing these are all strings */
    "idToken": string,
    "refreshToken": string,
  };
};

module AwsAmplify = {
  module Auth = {
    [@bs.module "aws-amplify"] [@bs.scope "Auth"]
    external currentSession: unit => Js.Promise.t(CognitoUserSession.t) = "";
  };
};

I don’t think you need to be dealing with JSON decoding here because you’re actually using an Amazon SDK which wraps all that and gives you JavaScript objects to work with.


#3

So many good things in here: code style, use of modules and type t. Will be back with feed back. Thank you for taking the time, brother.


#4

This is what I have come up with which works. I have two questions.

  1. I go this to work by passing Js.Json.t instead of the CognitoUserSession.t type. How do I get this work passing CognitoUserSession.t?

  2. In the AwsAppsyncClient, I have to change the type on the token to string to get it to go. I understand that I am not getting a proper string back but my toString function feels like a hack. Is there a different/better way to do this?

The CognitoUserSession type is:

module CognitoUserSession = {
  type t = {
    .
    "accessToken": string, /* I'm guessing these are all strings */
    "idToken": idToken,
    "refreshToken": token,
  }
  and idToken = {. "jwtToken": string}
  and token = {. "token": string};
};

This is the full response object gist

This is how I was able to get the value I needed:


module Decode = {
  let decodeJwt = json =>
    Json.Decode.{"jwtToken": json |> field("jwtToken", string)};
  let decodeToken = json =>
    Json.Decode.{"token": json |> field("token", string)};
  let decodeSession = json =>
    Json.Decode.{
      "idToken": json |> field("idToken", decodeJwt),
      "accessToken": json |> field("accessToken", decodeJwt),
      "refreshToken": json |> field("refreshToken", decodeToken),
    };
};
let getJwtToken = () => {
  PromiseMonad.(
    Auth.currentSession()
    >>= (
      json => {
        Js.log2("RESPONSE_VALUE_TOKEN_AMPLIFY", json);

        let session = Decode.decodeSession(json);
        let jwtToken = session##idToken##jwtToken;

        // Js.log2("RESPONSE_VALUE_jwtToken_AMPLIFY", jwtToken);

        return(jwtToken);
      }
    )
  );
};

In addition to the two questions, please feel free to get as didactic as you want without regard to my feelings or sensitivities! Your feedback/mentoring is greatly appreciated, my brother.

This is my non working attempt at trying to pass the CognitoUserSession.t:gist


#5

So this, using Belt.Option cuts the code size way down and works but why I am mad that I am not using the CognitoUserSession.t type? Is that a problem?


module Auth = {
  [@bs.module "aws-amplify"] [@bs.scope "Auth"]
  // external currentSession: unit => Js.Promise.t(CognitoUserSession.t) = "";
  external currentSession: unit => Js.Promise.t(Js.t('a)) = "";

  external currentCredentials: unit => Js.Promise.t(Js.Json.t) = "";
};

[@genType]
let getJwtToken = () => {
  PromiseMonad.(
    Auth.currentSession()
    >>= (
      json => {
        open Belt.Option;
        let jwtToken =
          json##idToken
          ->flatMap(idToken => idToken##jwtToken)
          ->getWithDefault("");

        return(jwtToken);
      }
    )
  );
};