Road to steemconnect-firebase-functions 2.0.0: what has been done so far?
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.
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:
- Github: https://github.com/jakipatryk/steemconnect-firebase-functions
- SteemProjects: https://steemprojects.com/projects/p/steemconnect-firebase-functions/
- NPM: https://www.npmjs.com/package/steemconnect-firebase-functions
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
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]
Do you want me to resteem your blog post to over 32,800 followers and many viewers to get more upvotes? For more info. go here: https://steemit.com/@a-0-0
Hey @jakipatryk I am @utopian-io. I have just upvoted you!
Achievements
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.