From 242d8dbc3864cc7fefbbed087e05ab73309b0119 Mon Sep 17 00:00:00 2001 From: David Yahalomi Date: Sun, 18 Dec 2016 15:35:48 +0200 Subject: [PATCH] chore(flow): Added type declarations for accounts and made eslint and flow check happy --- .eslintrc | 5 ++- .gitignore | 1 + package.json | 1 + src/client/AccountsClient.js | 75 ++++++++++++++++++++++--------- src/client/store.js | 7 ++- src/common/AccountsCommon.js | 50 ++++++++++++++------- src/common/toUsernameAndEmail.js | 8 +++- src/server/AccountsServer.spec.js | 2 +- 8 files changed, 106 insertions(+), 43 deletions(-) diff --git a/.eslintrc b/.eslintrc index b6df8cd90..7e9c71675 100644 --- a/.eslintrc +++ b/.eslintrc @@ -16,8 +16,10 @@ ], "rules": { + "indent": ["error", 2, { "CallExpression": {"arguments": "first"}, "SwitchCase": 1 }], "strict": 0, "import/no-extraneous-dependencies": 0, + "no-duplicate-imports": 0, "no-underscore-dangle": 0, "class-methods-use-this": 0, "jsdoc/check-param-names": 1, @@ -57,7 +59,8 @@ 2, "always", { - "annotateUndefined": "never" + "annotateUndefined": "never", + "excludeArrowFunctions": true } ], "flowtype/require-valid-file-annotation": 2, diff --git a/.gitignore b/.gitignore index 60483a3b3..bece0becc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules/ coverage/ npm-debug.log yarn.lock +.idea diff --git a/package.json b/package.json index 399434b2d..106cd7add 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "start": "webpack -p --config --progress --watch", "compile": "webpack -p --config --progress", + "flow:check": "flow check", "prepublish": "npm run compile", "pretest": "npm run lint", "test": "npm run testonly", diff --git a/src/client/AccountsClient.js b/src/client/AccountsClient.js index f4d73b9f2..1380ba491 100644 --- a/src/client/AccountsClient.js +++ b/src/client/AccountsClient.js @@ -1,41 +1,65 @@ +// @flow + import { isFunction, isString, has } from 'lodash'; +import type { Map } from 'immutable'; + import { defaultClientConfig } from '../common/defaultConfigs'; import { AccountsError } from '../common/errors'; import store from './store'; import AccountsCommon from '../common/AccountsCommon'; import { loggingIn } from './module'; import ui from './ui'; +import type { AccountsOptionsType } from '../common/AccountsCommon'; + +export type UserObjectType = {username: ?string, email: ?string, id: ?string}; +const isValidUserObject = (user: UserObjectType) => has(user, 'user') || has(user, 'email') || has(user, 'id'); + +export type UserCreationInputType = { + username: ?string, + password: ?string, + email: ?string +}; -const isValidUserObject = user => has(user, 'user') || has(user, 'email') || has(user, 'id'); +export type AccountTokenType = { + token: string, + tokenExpiration: Date, + userId: ?string +}; + +export interface AccountsTransportClient { + createUser(user: UserCreationInputType): AccountTokenType, + loginWithPassword(user: UserObjectType, password: string): AccountTokenType +} class Accounts extends AccountsCommon { - constructor(options, client) { + + constructor(options: AccountsOptionsType, client: AccountsTransportClient) { super(options); + if (!client) { throw new AccountsError({ message: 'A REST or GraphQL client is required', }); } - this.store = store; + this.client = client; } - getState() { - return this.getState().get('accounts'); + + getState(): Map { + return store.getState().get('accounts'); } + // TODO Accept 'profile' in the options - async createUser({ password, username, email }, callback) { - this.validatePassword(password); + async createUser(user: UserCreationInputType, callback: Function): Promise { + this.validatePassword(user.password); // TODO Throw error if client user creation is disabled - if (!this.validateUsername(username, false) && !this.validateEmail(email, false)) { + if (!this.validateUsername(user.username, false) && !this.validateEmail(user.email, false)) { throw new AccountsError({ message: 'Username or Email is required' }); } try { - const res = await this.client.createUser({ - password, - username, - email, - }); + await this.client.createUser(user); + if (isFunction(callback)) { callback(); } @@ -47,7 +71,10 @@ class Accounts extends AccountsCommon { } } } - async loginWithPassword(user, password, callback) { + + async loginWithPassword(user: UserObjectType, + password: string, + callback: Function): Promise { if (!password || !user) { throw new AccountsError({ message: 'Unrecognized options for login request [400]' }); } @@ -55,9 +82,9 @@ class Accounts extends AccountsCommon { throw new AccountsError({ message: 'Match failed [400]' }); } - this.store.dispatch(loggingIn(true)); + store.dispatch(loggingIn(true)); try { - const res = await this.client.loginWithPassword(user, password); + await this.client.loginWithPassword(user, password); // TODO Update redux store with user info if (isFunction(callback)) { callback(); @@ -68,14 +95,16 @@ class Accounts extends AccountsCommon { throw new AccountsError({ message: err }); } } - this.store.dispatch(loggingIn(false)); + store.dispatch(loggingIn(false)); } + // loginWith(service, options, callback) { // // } - loggingIn() { - return this.getState().get('loggingIn'); + loggingIn(): boolean { + return (this.getState().get('loggingIn'): boolean); } + // logout(callback) { // // } @@ -97,21 +126,23 @@ class Accounts extends AccountsCommon { // onEmailVerificationLink(callback) { // // } + + client: AccountsTransportClient; } const AccountsClient = { ui, - config(options, client) { + config(options: AccountsOptionsType, client: AccountsTransportClient) { this.instance = new Accounts({ ...defaultClientConfig, ...options, }, client); }, - createUser(...args) { + createUser(...args: Array): void { return this.instance.createUser(...args); }, - loginWithPassword(...args) { + loginWithPassword(...args: Array): void { return this.instance.loginWithPassword(...args); }, }; diff --git a/src/client/store.js b/src/client/store.js index 2db9ba236..72aa2f9a6 100644 --- a/src/client/store.js +++ b/src/client/store.js @@ -1,7 +1,12 @@ +// @flow + +import type { Map } from 'immutable'; +import type { Store } from 'redux'; + import createStore from './createStore'; import reducer from './module'; -const store = createStore({ +const store: Store> = createStore({ reducers: { accounts: reducer, }, diff --git a/src/common/AccountsCommon.js b/src/common/AccountsCommon.js index 074d6d890..0d25afaec 100644 --- a/src/common/AccountsCommon.js +++ b/src/common/AccountsCommon.js @@ -1,54 +1,70 @@ -/* @flow */ +// @flow + import { trim, isEmpty } from 'lodash'; import { AccountsError } from './errors'; // TODO Check that password complexity satisfies the config // TODO Check that email domain satisifes the config -class AccountsCommon { +export type AccountsOptionsType = {}; + +export default class AccountsCommon { + // TODO Handle options - constructor(options: object) { + constructor(options: AccountsOptionsType) { this.options = options; } - validateEmail(email: string, throwError: boolean = true): boolean { + + validateEmail(email: ?string, throwError: boolean = true): boolean { const hasError = isEmpty(trim(email)); if (hasError && throwError) { throw new AccountsError({ message: 'Email is required' }); } return hasError; } - validatePassword(password: string, throwError: boolean = true) { + + validatePassword(password: ?string, throwError: boolean = true): boolean { const hasError = isEmpty(trim(password)); if (hasError && throwError) { throw new AccountsError({ message: 'Password is required' }); } return hasError; } - validateUsername(username: string, throwError = true) { + + validateUsername(username: ?string, throwError: boolean = true): boolean { const hasError = isEmpty(trim(username)); if (hasError && throwError) { throw new AccountsError({ message: 'Username is required' }); } return hasError; } - userId() { + + userId(): ?string { } + user() { } - config(config) { - this.config = { ...this.config, config }; + + config(options: AccountsOptionsType) { + this.options = { ...this.options, options }; } - onLogin(func) { - this.onLogin = func; + + onLogin(cb: Function) { + this.onLogin = cb; } - onLoginFailure(func) { - this.onLoginFailure = func; + + onLoginFailure(cb: Function) { + this.onLoginFailure = cb; } - onLogout(func) { - this.onLogout = func; + + onLogout(cb: Function) { + this.onLogout = cb; } -} -export default AccountsCommon; + options: AccountsOptionsType; + onLogin: Function; + onLoginFailure: Function; + onLogout: Function; +} diff --git a/src/common/toUsernameAndEmail.js b/src/common/toUsernameAndEmail.js index 18c326cb5..959040276 100644 --- a/src/common/toUsernameAndEmail.js +++ b/src/common/toUsernameAndEmail.js @@ -1,4 +1,10 @@ -// Thank you http://stackoverflow.com/a/46181 +/** + * Checks if a string is actualy an email. + * Thank you http://stackoverflow.com/a/46181. + * + * @param {string} email - presumably an email + * @returns {boolean} whether or not it is an email + */ function isEmail(email) { // eslint-disable-next-line const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; diff --git a/src/server/AccountsServer.spec.js b/src/server/AccountsServer.spec.js index f456b7d7f..7f906ecf9 100644 --- a/src/server/AccountsServer.spec.js +++ b/src/server/AccountsServer.spec.js @@ -1,7 +1,7 @@ /* eslint-disable no-unused-expressions */ import 'regenerator-runtime/runtime'; // For async / await syntax import chai, { expect } from 'chai'; -import sinon from 'sinon'; +// import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import chaiAsPromised from 'chai-as-promised'; // import 'localstorage-polyfill';