Road to steemconnect-firebase-functions 2.0.0: what has been done so far?

in utopian-io •  last year  (edited)

I wanna bring steemconnect-firebase-functions to a next level. To do so, I have to almost entirely remake it. It takes a lot of time, but I've already done quite a lot and I want to share the result of my work.

TOWARDS 2.0.0

Quick recap

steemconnect-firebase-functions is a library designed to help developers who want to create apps based on SteemConnect and Firebase. The library makes it easy to:

  • implement OAuth2 Authorization Code Grant (enables user to log in to your app using SteemConnect)
  • broadcast operations to the Steem blockchain (post, comment, upvote, etc.)
  • make operations on the Firebase products (Authentication, Firestore)

Links:

New features

The main idea behind version 2.0.0 is to make the library's functions composable. To do so, it was necessary to add some nice features, which added more abstraction to the code:

Operation creators

First of all, I created an operation-creators module. Role of this module is to take care of creating operations (which can be then broadcasted to the Steem) from given options.

The most fundamental creator is simply createOperation:

export const createOperation = (type: string, { ...parameters }): Operation => [
  type,
  { ...parameters }
];

Next, based on the createOperation, I've added several functions and interfaces for their configuration, for example createCustomJson and CustomJsonConfig:

export interface CustomJsonConfig {
  required_posting_auths: Array<string>;
  id: string;
  json: string;
  required_auths?: Array<string>;
}

export const createCustomJson = ({
  required_posting_auths: [...postingAuths],
  id,
  json,
  required_auths: [...auths] = []
}: CustomJsonConfig): Operation =>
  createOperation('custom_json', {
    required_posting_auths: [...postingAuths],
    id,
    json,
    required_auths: [...auths]
  });

For more code, click here.

Utilities

To compose functions, I needed two utilities:

  • pipe - this one is a classic functional programming utility:
export const pipe = <T>(...fns: Array<Function>) => (x: T) =>
  fns.reduce((v, f) => f(v), x);
  • combine - I had to create this one to have the ability to combine two creator functions (you can use it to combine more tho):
export const combine = <T, U>(...fns: Array<Function>) => (
  ...args: Array<T>
): Array<U> =>
  fns.reduce(
    (arr, f) => {
      arr.push(f(...args));
      return arr;
    },
    [] as Array<U>
  );

Helpers

Using the utilities I described above I created a few helper functions, which I could use later in the broadcasting module.

Currently there is only one function which uses combine, namely combineCommentWithOptions. I simply didn't need more:

export const combineCommentWithOptions = combine<
  CommentConfig & CommentOptionsConfig,
  Operation
>(createComment, createCommentOptions);

However, the pipe utility was used with every operation creator, for example createBroadcastableVote:

export const createBroadcastableVote = pipe<VoteConfig>(
  createVote,
  Array.of,
  broadcastOperations
);

For more, check this file.

Errors

For one of the features that hasn't been implemented yet, I'm gonna need functions that check if provided error fits error to check against.

To achieve this I created constants for a few errors, for example ACCESS_TOKEN_EXPIRED:

export const ACCESS_TOKEN_EXPIRED: OAuth2Error = Object.freeze({
  error: 'invalid_grant',
  error_description: 'The token has invalid role'
});

(more there)

I've also added error checkers themself, for example isAccessTokenExpiredError:

export const isAccessTokenExpiredError = ({
  error,
  error_description
}: OAuth2Error): boolean =>
  checkOAuth2Error({ error, error_description }, ACCESS_TOKEN_EXPIRED);

For more, check this module.

Modularity

I decided to split the code into modules, more precisely into:

  • broadcasting
  • firebase
  • oauth2
  • operation-creators
  • shared

New broadcasting system

In my opinion, this is the most important one. Even though it is not 100% ready yet.

Old system wasn't flexible. I didn't like it, so I decided to use some of the features you have already read about to make something better.

Flexibility was achieved also thanks to curried functions. This is crucial for a wrapper, which is not implemented yet.

The basic broadcastOperations function now looks like that:

export function broadcastOperations([...operations]: Operations) {
  return async function broadcast({
    access_token
  }: AccessTokenResponse): Promise<BroadcastResult> {
    return rp
      .post({
        uri: 'https://steemconnect.com/api/broadcast',
        headers: {
          Authorization: access_token,
          'Content-Type': 'application/json',
          Accept: 'application/json'
        },
        body: {
          operations: [...operations]
        },
        json: true
      })
      .catch(err => {
        throw err.error;
      });
  };
}

More specific broadcast functions use the createBroadcastableX helpers, for example broadcastComment:

export const broadcastComment = ({
  parentPermlink,
  commentAuthor,
  commentPermlink,
  commentBody,
  parentAuthor = '',
  commentTitle = '',
  commentMetadata
}: Comment) => ({ access_token }: AccessTokenResponse): BroadcastResult =>
  createBroadcastableComment({
    parent_permlink: parentPermlink,
    author: commentAuthor,
    permlink: commentPermlink,
    body: commentBody,
    parent_author: parentAuthor,
    title: commentTitle,
    json_metadata: JSON.stringify(commentMetadata)
  })({ access_token });

There are also interfaces associated with them, for example Comment:

export interface Comment {
  parentAuthor?: string;
  parentPermlink: string;
  commentAuthor: string;
  commentPermlink: string;
  commentBody: string;
  commentTitle?: string;
  commentMetadata?: object | string;
}

Roadmap

There is actually quite a lot to do before the beta release:

  • a wrapper function for broadcasting module functions, which is going to act like an HTTP Interceptor, in this case, it will be refreshing access token if necessary
  • change inputs of the functions from the oauth2 and firebase modules (make them unary)
  • a delete_comment operation (creator, helper and broadcast functions)
  • new in-code documentation which I will use to generate an external documentation from (I'm not sure what tools I will use yet, I was thinking about typedoc and Jekyll, what do you think?)
  • a migration guide

How to contribute?

For now, I'd rather want you to contact me on Discord (jakipatryk#1263) or on Steem.chat (jakipatryk) before any development contribution.

After the release of 2.0.0, I will automate this process, so the library will become community driven.

Pull requests:



Posted on Utopian.io - Rewarding Open Source Contributors

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Thanks for the contribution. It has been approved.


Need help? Write a ticket on https://support.utopian.io.
Chat with us on Discord.

[utopian-moderator]

Hey @jakipatryk I am @utopian-io. I have just upvoted you!

Achievements

  • Seems like you contribute quite often. AMAZING!

Utopian Witness!

Participate on Discord. Lets GROW TOGETHER!

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x

This post has been just added as new item to timeline of steemconnect-firebase-functions on Steem Projects.

If you want to be notified about new updates from this project, register on Steem Projects and add steemconnect-firebase-functions to your favorite projects.