Skip to content

Commit 6b23b49

Browse files
authored
Merge pull request #23 from davidyaha/improve-flow
chore(flow): Added type declarations for accounts and made eslint and flow check happy
2 parents b3f8982 + 242d8db commit 6b23b49

File tree

8 files changed

+106
-43
lines changed

8 files changed

+106
-43
lines changed

.eslintrc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
],
1717

1818
"rules": {
19+
"indent": ["error", 2, { "CallExpression": {"arguments": "first"}, "SwitchCase": 1 }],
1920
"strict": 0,
2021
"import/no-extraneous-dependencies": 0,
22+
"no-duplicate-imports": 0,
2123
"no-underscore-dangle": 0,
2224
"class-methods-use-this": 0,
2325
"jsdoc/check-param-names": 1,
@@ -57,7 +59,8 @@
5759
2,
5860
"always",
5961
{
60-
"annotateUndefined": "never"
62+
"annotateUndefined": "never",
63+
"excludeArrowFunctions": true
6164
}
6265
],
6366
"flowtype/require-valid-file-annotation": 2,

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ node_modules/
33
coverage/
44
npm-debug.log
55
yarn.lock
6+
.idea

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"scripts": {
77
"start": "webpack -p --config --progress --watch",
88
"compile": "webpack -p --config --progress",
9+
"flow:check": "flow check",
910
"prepublish": "npm run compile",
1011
"pretest": "npm run lint",
1112
"test": "npm run testonly",

src/client/AccountsClient.js

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,65 @@
1+
// @flow
2+
13
import { isFunction, isString, has } from 'lodash';
4+
import type { Map } from 'immutable';
5+
26
import { defaultClientConfig } from '../common/defaultConfigs';
37
import { AccountsError } from '../common/errors';
48
import store from './store';
59
import AccountsCommon from '../common/AccountsCommon';
610
import { loggingIn } from './module';
711
import ui from './ui';
12+
import type { AccountsOptionsType } from '../common/AccountsCommon';
13+
14+
export type UserObjectType = {username: ?string, email: ?string, id: ?string};
15+
const isValidUserObject = (user: UserObjectType) => has(user, 'user') || has(user, 'email') || has(user, 'id');
16+
17+
export type UserCreationInputType = {
18+
username: ?string,
19+
password: ?string,
20+
email: ?string
21+
};
822

9-
const isValidUserObject = user => has(user, 'user') || has(user, 'email') || has(user, 'id');
23+
export type AccountTokenType = {
24+
token: string,
25+
tokenExpiration: Date,
26+
userId: ?string
27+
};
28+
29+
export interface AccountsTransportClient {
30+
createUser(user: UserCreationInputType): AccountTokenType,
31+
loginWithPassword(user: UserObjectType, password: string): AccountTokenType
32+
}
1033

1134
class Accounts extends AccountsCommon {
12-
constructor(options, client) {
35+
36+
constructor(options: AccountsOptionsType, client: AccountsTransportClient) {
1337
super(options);
38+
1439
if (!client) {
1540
throw new AccountsError({
1641
message: 'A REST or GraphQL client is required',
1742
});
1843
}
19-
this.store = store;
44+
2045
this.client = client;
2146
}
22-
getState() {
23-
return this.getState().get('accounts');
47+
48+
getState(): Map<string, any> {
49+
return store.getState().get('accounts');
2450
}
51+
2552
// TODO Accept 'profile' in the options
26-
async createUser({ password, username, email }, callback) {
27-
this.validatePassword(password);
53+
async createUser(user: UserCreationInputType, callback: Function): Promise<void> {
54+
this.validatePassword(user.password);
2855
// TODO Throw error if client user creation is disabled
2956

30-
if (!this.validateUsername(username, false) && !this.validateEmail(email, false)) {
57+
if (!this.validateUsername(user.username, false) && !this.validateEmail(user.email, false)) {
3158
throw new AccountsError({ message: 'Username or Email is required' });
3259
}
3360
try {
34-
const res = await this.client.createUser({
35-
password,
36-
username,
37-
email,
38-
});
61+
await this.client.createUser(user);
62+
3963
if (isFunction(callback)) {
4064
callback();
4165
}
@@ -47,17 +71,20 @@ class Accounts extends AccountsCommon {
4771
}
4872
}
4973
}
50-
async loginWithPassword(user, password, callback) {
74+
75+
async loginWithPassword(user: UserObjectType,
76+
password: string,
77+
callback: Function): Promise<void> {
5178
if (!password || !user) {
5279
throw new AccountsError({ message: 'Unrecognized options for login request [400]' });
5380
}
5481
if ((!isString(user) && !isValidUserObject(user)) || !isString(password)) {
5582
throw new AccountsError({ message: 'Match failed [400]' });
5683
}
5784

58-
this.store.dispatch(loggingIn(true));
85+
store.dispatch(loggingIn(true));
5986
try {
60-
const res = await this.client.loginWithPassword(user, password);
87+
await this.client.loginWithPassword(user, password);
6188
// TODO Update redux store with user info
6289
if (isFunction(callback)) {
6390
callback();
@@ -68,14 +95,16 @@ class Accounts extends AccountsCommon {
6895
throw new AccountsError({ message: err });
6996
}
7097
}
71-
this.store.dispatch(loggingIn(false));
98+
store.dispatch(loggingIn(false));
7299
}
100+
73101
// loginWith(service, options, callback) {
74102
//
75103
// }
76-
loggingIn() {
77-
return this.getState().get('loggingIn');
104+
loggingIn(): boolean {
105+
return (this.getState().get('loggingIn'): boolean);
78106
}
107+
79108
// logout(callback) {
80109
//
81110
// }
@@ -97,21 +126,23 @@ class Accounts extends AccountsCommon {
97126
// onEmailVerificationLink(callback) {
98127
//
99128
// }
129+
130+
client: AccountsTransportClient;
100131
}
101132

102133

103134
const AccountsClient = {
104135
ui,
105-
config(options, client) {
136+
config(options: AccountsOptionsType, client: AccountsTransportClient) {
106137
this.instance = new Accounts({
107138
...defaultClientConfig,
108139
...options,
109140
}, client);
110141
},
111-
createUser(...args) {
142+
createUser(...args: Array<mixed>): void {
112143
return this.instance.createUser(...args);
113144
},
114-
loginWithPassword(...args) {
145+
loginWithPassword(...args: Array<mixed>): void {
115146
return this.instance.loginWithPassword(...args);
116147
},
117148
};

src/client/store.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
// @flow
2+
3+
import type { Map } from 'immutable';
4+
import type { Store } from 'redux';
5+
16
import createStore from './createStore';
27
import reducer from './module';
38

4-
const store = createStore({
9+
const store: Store<Map<string, any>> = createStore({
510
reducers: {
611
accounts: reducer,
712
},

src/common/AccountsCommon.js

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,70 @@
1-
/* @flow */
1+
// @flow
2+
23
import { trim, isEmpty } from 'lodash';
34
import { AccountsError } from './errors';
45

56
// TODO Check that password complexity satisfies the config
67
// TODO Check that email domain satisifes the config
78

8-
class AccountsCommon {
9+
export type AccountsOptionsType = {};
10+
11+
export default class AccountsCommon {
12+
913
// TODO Handle options
10-
constructor(options: object) {
14+
constructor(options: AccountsOptionsType) {
1115
this.options = options;
1216
}
13-
validateEmail(email: string, throwError: boolean = true): boolean {
17+
18+
validateEmail(email: ?string, throwError: boolean = true): boolean {
1419
const hasError = isEmpty(trim(email));
1520
if (hasError && throwError) {
1621
throw new AccountsError({ message: 'Email is required' });
1722
}
1823
return hasError;
1924
}
20-
validatePassword(password: string, throwError: boolean = true) {
25+
26+
validatePassword(password: ?string, throwError: boolean = true): boolean {
2127
const hasError = isEmpty(trim(password));
2228
if (hasError && throwError) {
2329
throw new AccountsError({ message: 'Password is required' });
2430
}
2531
return hasError;
2632
}
27-
validateUsername(username: string, throwError = true) {
33+
34+
validateUsername(username: ?string, throwError: boolean = true): boolean {
2835
const hasError = isEmpty(trim(username));
2936
if (hasError && throwError) {
3037
throw new AccountsError({ message: 'Username is required' });
3138
}
3239
return hasError;
3340
}
34-
userId() {
41+
42+
userId(): ?string {
3543

3644
}
45+
3746
user() {
3847

3948
}
40-
config(config) {
41-
this.config = { ...this.config, config };
49+
50+
config(options: AccountsOptionsType) {
51+
this.options = { ...this.options, options };
4252
}
43-
onLogin(func) {
44-
this.onLogin = func;
53+
54+
onLogin(cb: Function) {
55+
this.onLogin = cb;
4556
}
46-
onLoginFailure(func) {
47-
this.onLoginFailure = func;
57+
58+
onLoginFailure(cb: Function) {
59+
this.onLoginFailure = cb;
4860
}
49-
onLogout(func) {
50-
this.onLogout = func;
61+
62+
onLogout(cb: Function) {
63+
this.onLogout = cb;
5164
}
52-
}
5365

54-
export default AccountsCommon;
66+
options: AccountsOptionsType;
67+
onLogin: Function;
68+
onLoginFailure: Function;
69+
onLogout: Function;
70+
}

src/common/toUsernameAndEmail.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
// Thank you http://stackoverflow.com/a/46181
1+
/**
2+
* Checks if a string is actualy an email.
3+
* Thank you http://stackoverflow.com/a/46181.
4+
*
5+
* @param {string} email - presumably an email
6+
* @returns {boolean} whether or not it is an email
7+
*/
28
function isEmail(email) {
39
// eslint-disable-next-line
410
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,}))$/;

src/server/AccountsServer.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable no-unused-expressions */
22
import 'regenerator-runtime/runtime'; // For async / await syntax
33
import chai, { expect } from 'chai';
4-
import sinon from 'sinon';
4+
// import sinon from 'sinon';
55
import sinonChai from 'sinon-chai';
66
import chaiAsPromised from 'chai-as-promised';
77
// import 'localstorage-polyfill';

0 commit comments

Comments
 (0)