diff --git a/README.md b/README.md index a2249ed..a6fb2c9 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ A implementation of Meteor Accounts only in GraphQL with Apollo. -This package uses the Meteor Accounts methods in GraphQL, it's compatible with the accounts you have saved in your database and you may use apollo-accounts and Meteor's DPP accounts at the same time. +This package uses the Meteor Accounts methods in GraphQL, it's compatible with the accounts you have saved in your database and you may use apollo-accounts and Meteor's DPP accounts at the same time. It is also compatible with React Native using AsyncStorage (see guide at end of ReadMe). > Sorry for all the api changes, now we will use [graphql-loader](https://github.com/orionsoft/graphql-loader) for a long term solution. ## Installing -### Install on Meteor server +## Install on Meteor server ```sh meteor add nicolaslopezj:apollo-accounts @@ -37,7 +37,7 @@ const executableSchema = makeExecutableSchema(schema) ``` -### Install on your apollo app +## Install on your apollo app May or may not be the same app. @@ -45,26 +45,125 @@ May or may not be the same app. npm install meteor-apollo-accounts ``` -## Examples +### Examples - [janikvonrotz/meteor-apollo-accounts-example](https://github.com/janikvonrotz/meteor-apollo-accounts-example): Meteor client and server side. - [orionsoft/server-boilerplate](https://github.com/orionsoft/server-boilerplate): Large Meteor server side only starter app. -## Tutorials +### Tutorials - [Using Meteor With Apollo and React](https://blog.orionsoft.io/using-meteor-accounts-with-apollo-and-react-df3c89b46b17#.znozw2zbd) -## Methods +### Example Usage + +```js +// client.js +import ApolloClient, { createNetworkInterface } from 'apollo-client'; +import Accounts from 'meteor-apollo-accounts'; + +const networkInterface = createNetworkInterface({ uri: 'localhost:3000/graphql' }); + +networkInterface.use([{ + applyMiddleware(req, next) { + if (!req.options.headers) { + req.options.headers = {}; + } + req.options.headers.authorization = Accounts.getLoginToken() || null; + next(); + } +}]); + +const client = new ApolloClient({ + networkInterface, + dataIdFromObject: (result) => result._id +}); + +export default client; +``` + +```js +// Login.jsx +import React, { Component } from 'react'; +import Accounts from 'meteor-apollo-accounts'; +import client from './client'; // instance of apollo-client + +Accounts.initWithClient(client); //do this only once + +Accounts.onLogin(() => { + console.log(Accounts.userId()); +}); + +export default class Login extends Component { + constructor() { + this.state = { + username: '', + password: '' + }; + this.login = this.login.bind(this); + this.onUsernameChange = this.onUsernameChange.bind(this); + this.onPwdChange = this.onPwdChange.bind(this); + } + + login(e) { + const { username, password } = this.state; + e.preventDefault(); + Accounts.loginWithPassword({ username, password }) + .catch(function(err) { + console.log("Error logging in", err); + }); + } + + onUsernameChange(event) { + this.setState({username: event.target.value}); + } + + onPwdChange(event) { + this.setState({password: event.target.value}); + } + + render() { + const { username, password } = this.state; + return ( +
+
+
+ + +
+ +
+
+ ) + } +} + +``` + +### Methods Meteor accounts methods, client side only. All methods are promises. +#### initWithClient + +Initialize the accounts system with an instance of Apollo Client. You only need to do this once in your app, typically upon startup. + +```js +import ApolloClient from 'apollo-client'; +import Accounts from 'meteor-apollo-accounts'; + +const client = new ApolloClient(); + +Accounts.initWithClient(client); +``` + +- ```client```: Your instance of Apollo Client + + #### loginWithPassword Log the user in with a password. ```js -import { loginWithPassword } from 'meteor-apollo-accounts' - -loginWithPassword({username, email, password, plainPassword}, apollo) +Accounts.loginWithPassword({username, email, password, plainPassword}) ``` - ```username```: Optional. The user's username. @@ -75,159 +174,150 @@ loginWithPassword({username, email, password, plainPassword}, apollo) - ```plainPassword```: Optional. The plain user's password. Recommended only for use in testing tools, like GraphiQL. -- ```apollo```: Apollo client instance. -#### changePassword +#### logout -Change the current user's password. Must be logged in. +Log the user out. ```js -import { changePassword } from 'meteor-apollo-accounts' +Accounts.logout() +``` + +#### onLogin -changePassword({oldPassword, newPassword}, apollo) +Register a function to be called when a user logged in + +```js +Accounts.onLogin(() => { + console.log('Current User: ', Accounts.userId()) + ... + // Fetch data, change routes, etc +}) ``` -- ```oldPassword```: The user's current password. This is not sent in plain text over the wire. +#### onLoginFailure -- ```newPassword```: A new password for the user. This is not sent in plain text over the wire. +Register a function to be called when a login attempt is failed -- ```apollo```: Apollo client instance. +```js +Accounts.onLoginFailure(() => { + console.log('Login Failed') + ... + // Set route to login page, reset store, etc +}) +``` -#### logout +#### onLogout -Log the user out. +Register a function to be called when a user logs out + +```js +Accounts.onLogout(() => { + console.log('User Logged Out') + ... + // Set route to login page, reset store, etc +}) +``` + +#### loggingIn + +Returns true if a login method (such as Accounts.loginWithPassword, Accounts.loginWithFacebook, or Accounts.createUser) is currently in progress. +```js +console.log('Currently logging in? : ', Accounts.loggingIn()) +``` + +#### userId + +Returns the id of the logged in user. ```js -import { logout } from 'meteor-apollo-accounts' +console.log('The user id is:', Accounts.userId()) +``` -logout(apollo) +#### changePassword + +Change the current user's password. Must be logged in. + +```js +Accounts.changePassword({oldPassword, newPassword}) ``` -- ```apollo```: Apollo client instance. +- `oldPassword`: The user's current password. This is not sent in plain text over the wire. + +- `newPassword`: A new password for the user. This is not sent in plain text over the wire. + #### createUser Create a new user. ```js -import { createUser } from 'meteor-apollo-accounts' - -createUser({username, email, password, profile}, apollo) +Accounts.createUser({username, email, password, profile}) ``` -- ```username```: A unique name for this user. +- `username`: A unique name for this user. -- ```email```: The user's email address. +- `email`: The user's email address. -- ```password```: The user's password. This is not sent in plain text over the wire. +- `password`: The user's password. This is not sent in plain text over the wire. -- ```profile```: The profile object based on the ```UserProfileInput``` input type. +- `profile`: The profile object based on the ```UserProfileInput``` input type. -- ```apollo```: Apollo client instance. #### verifyEmail Marks the user's email address as verified. Logs the user in afterwards. ```js -import { verifyEmail } from 'meteor-apollo-accounts' - -verifyEmail({token}, apollo) +Accounts.verifyEmail({token}) ``` - ```token```: The token retrieved from the verification URL. -- ```apollo```: Apollo client instance. - - #### forgotPassword Request a forgot password email. ```js -import { forgotPassword } from 'meteor-apollo-accounts' - -forgotPassword({email}, apollo) +Accounts.forgotPassword({email}) ``` - ```email```: The email address to send a password reset link. -- ```apollo```: Apollo client instance. - #### resetPassword Reset the password for a user using a token received in email. Logs the user in afterwards. ```js -import { resetPassword } from 'meteor-apollo-accounts' - -resetPassword({newPassword, token}, apollo) +Accounts.resetPassword({newPassword, token}) ``` - ```newPassword```: A new password for the user. This is not sent in plain text over the wire. - ```token```: The token retrieved from the reset password URL. -- ```apollo```: Apollo client instance. - - #### loginWithFacebook Logins the user with a facebook accessToken ```js -import { loginWithFacebook } from 'meteor-apollo-accounts' - -loginWithFacebook({accessToken}, apollo) +Accounts.loginWithFacebook({accessToken}) ``` - ```accessToken```: A Facebook accessToken. It's recommended to use https://github.com/keppelen/react-facebook-login to fetch the accessToken. -- ```apollo```: Apollo client instance. - #### loginWithGoogle Logins the user with a google accessToken ```js -import { loginWithGoogle } from 'meteor-apollo-accounts' - -loginWithGoogle({accessToken}, apollo) +Accounts.loginWithGoogle({accessToken}) ``` - ```accessToken```: A Google accessToken. It's recommended to use https://github.com/anthonyjgrove/react-google-login to fetch the accessToken. -- ```apollo```: Apollo client instance. - - -#### onTokenChange - -Register a function to be called when a user is logged in or out. - -```js -import { onTokenChange } from 'meteor-apollo-accounts' - -onTokenChange(function () { - console.log('token did change') - apollo.resetStore() -}) -``` - -#### userId - -Returns the id of the logged in user. - -```js -import { userId } from 'meteor-apollo-accounts' - -async function () { - console.log('The user id is:', await userId()) -} - -``` - ### React-Native usage @@ -241,35 +331,58 @@ import { AsyncStorage } from 'react-native'; -import { loginWithPassword, userId, setTokenStore} from 'meteor-apollo-accounts' - -// Then you'll have to define a TokenStore for your user data using setTokenStore -// (for instance when your component is mounted): -setTokenStore({ - set: async function ({userId, token, tokenExpires}) { - await AsyncStorage.setItem('Meteor.userId', userId) - await AsyncStorage.setItem('Meteor.loginToken', token) - // AsyncStorage doesn't support Date type so we'll store it as a String - await AsyncStorage.setItem('Meteor.loginTokenExpires', tokenExpires.toString()) +import Accounts, { USER_ID_KEY, TOKEN_KEY, TOKEN_EXPIRES_KEY } from 'meteor-apollo-accounts'; +import client from './ApolloClient'; // Your instance of apollo client + +// Then you'll have to define a TokenStore for your user data using setTokenStore. +// This should be done before calling Accounts.initWithClient: +Accounts.setTokenStore({ + async set({ userId, token, tokenExpires }) { + return AsyncStorage.multiSet([ + [USER_ID_KEY, userId], + [TOKEN_KEY, token], + [TOKEN_EXPIRES_KEY, tokenExpires.toString()] + ]); }, - get: async function () { - return { - userId: await AsyncStorage.getItem('Meteor.userId'), - token: await AsyncStorage.getItem('Meteor.loginToken'), - tokenExpires: await AsyncStorage.getItem('Meteor.loginTokenExpires') - } + async get() { + const stores = await AsyncStorage.multiGet([ + USER_ID_KEY, + TOKEN_KEY, + TOKEN_EXPIRES_KEY + ]); + + const userId = stores[0][1]; + const token = stores[1][1]; + const tokenExpires = stores[2][1]; + + return { userId, token, tokenExpires }; + }, + async remove() { + return AsyncStorage.multiRemove([ + USER_ID_KEY, + TOKEN_KEY, + TOKEN_EXPIRES_KEY + ]); } -}) +}); + +// Make sure to initialize before calling anything else in Accounts: +Accounts.initWithClient(client); + // Finally, you'll be able to use asynchronously any method from the library: + +Accounts.onLogin(() => { + console.log(Accounts.userId()); +}); + async login (event) { event.preventDefault(); try { - const id_ = await loginWithPassword({ "email", "password" }, this.client) - this.client.resetStore() + const id = await Accounts.loginWithPassword({ "email", "password" }) } catch (error) { - + console.log("Error logging in: ", error); } } diff --git a/client/CHANGELOG.md b/client/CHANGELOG.md index b4ce074..369128d 100644 --- a/client/CHANGELOG.md +++ b/client/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +### vNEXT + +- Add `initWithClient(apolloClientInstance)` function to remove need for passing apollo with every function call +- Calling `userId()` is now synchronous and more performant since it's cached in memory +- Added the following hooks/callbacks: `onLogin`, `onLoginFailure`, `onLogout` so the API is similar to Meteor Accounts +- Added `loggingIn` function, similar to Meteor Accounts +- Added support for persisting login automatically. As soon as you call `initWithClient` in your app, it will check for a stored token and attempt to login, calling the `onLogin` callback if it succeeds. + ### v2.0.0 - React Native support. diff --git a/meteor-server/CHANGELOG.md b/meteor-server/CHANGELOG.md index 2a5ab71..daa5621 100644 --- a/meteor-server/CHANGELOG.md +++ b/meteor-server/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### vNEXT + +- Added `loginWithToken` mutation + ### v3.0.1 - Fix bug with ```tmeasday:check-npm-versions```.