Last active
March 11, 2019 22:33
-
-
Save burdiuz/0256a2436e814f315f36713088efb49e to your computer and use it in GitHub Desktop.
React Native component built to login users and obtain accessToken via callback
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React, { Component } from 'react'; | |
| import PropTypes from 'prop-types'; | |
| import { WebView } from 'react-native'; | |
| const WEBVIEW_URL = 'https://github.com/login/oauth/authorize'; | |
| const TOKEN_URL = 'https://github.com/login/oauth/access_token'; | |
| /* | |
| URL that will be displayed after successful login, it should be different to track URL change, so component can grab code | |
| */ | |
| const STATE = `${Date.now()}${Math.random(Math.random())}`; | |
| const getParam = (url, name) => { | |
| const matches = url.match(new RegExp(`(?:&|\\?)${name}=([^&]+)`)); | |
| return (matches && decodeURIComponent(matches[1])) || ''; | |
| }; | |
| class GithubSignin extends Component { | |
| static propTypes = { | |
| /* | |
| Client Id of your GitHub application | |
| */ | |
| clientId: PropTypes.string.isRequired, | |
| /* | |
| Client Secret of your GitHub application | |
| */ | |
| clientSecret: PropTypes.string.isRequired, | |
| /* | |
| Permissions | |
| */ | |
| scope: PropTypes.string.isRequired, | |
| /* | |
| Callback for successful login, passes object with accessToken as argument | |
| */ | |
| onSignInComplete: PropTypes.func.isRequired, | |
| /* | |
| Callback for failed login, passes error object as argument | |
| */ | |
| onSignInError: PropTypes.func.isRequired, | |
| redirectUri: PropTypes.string, | |
| state: PropTypes.string, | |
| /* | |
| Optional callback called when user logged in and redirect to "redirectUri" was requested | |
| */ | |
| onCodeReceived: PropTypes.func, | |
| }; | |
| static defaultProps = { | |
| state: STATE, | |
| redirectUri: '', | |
| onCodeReceived: undefined, | |
| }; | |
| state = { | |
| codeReceived: false, | |
| }; | |
| get signInUri() { | |
| const { clientId, redirectUri, scope, state } = this.props; | |
| const params = `?client_id=${encodeURIComponent(clientId)}&scope=${encodeURIComponent( | |
| scope, | |
| )}&state=${encodeURIComponent(state)}${ | |
| redirectUri ? `&redirect_uri=${encodeURIComponent(redirectUri)}` : '' | |
| }`; | |
| return `${WEBVIEW_URL}${params}`; | |
| } | |
| handleWebViewReference = (webView) => { | |
| this.webView = webView; | |
| }; | |
| handleNavigationStateChange = (params) => { | |
| const { url, loading } = params; | |
| const { redirectUri, state, onCodeReceived } = this.props; | |
| if (!loading || this.state.codeReceived) { | |
| return; | |
| } | |
| const code = getParam(url, 'code'); | |
| const remoteState = getParam(url, 'state'); | |
| const errorCode = getParam(url, 'error'); | |
| const errorDescription = getParam(url, 'error_description'); | |
| if ((!redirectUri || url.indexOf(redirectUri) === 0) && code && state === remoteState) { | |
| this.setState({ codeReceived: true }); | |
| if (onCodeReceived) { | |
| onCodeReceived(code); | |
| } | |
| this.sendTokenRequest(code); | |
| return; | |
| } | |
| if (errorCode) { | |
| const error = new Error(errorCode); | |
| error.description = errorDescription; | |
| this.props.onSignInError(error); | |
| } | |
| }; | |
| sendTokenRequest(code) { | |
| const { clientId, clientSecret, redirectUri, state } = this.props; | |
| const headers = new Headers({ | |
| Accept: 'application/json', | |
| 'Content-Type': 'application/json', | |
| }); | |
| return fetch(TOKEN_URL, { | |
| method: 'POST', | |
| headers, | |
| body: JSON.stringify({ | |
| code, | |
| state, | |
| client_id: clientId, | |
| client_secret: clientSecret, | |
| redirect_uri: encodeURIComponent(redirectUri), | |
| }), | |
| }) | |
| .catch((error) => { | |
| this.props.onSignInError(error); | |
| return Promise.reject(error); | |
| }) | |
| .then((response) => response.json()) | |
| .then((data) => { | |
| const { onSignInError, onSignInComplete } = this.props; | |
| const { error } = data || {}; | |
| const handler = error ? onSignInError : onSignInComplete; | |
| handler(data); | |
| return data; | |
| }); | |
| } | |
| render() { | |
| const { scope, style, state, ...props } = this.props; | |
| return ( | |
| <WebView | |
| {...props} | |
| ref={this.handleWebViewReference} | |
| source={{ uri: this.signInUri }} | |
| onNavigationStateChange={this.handleNavigationStateChange} | |
| style={style} | |
| /> | |
| ); | |
| } | |
| } | |
| export default GithubSignin; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "name": "GithubSignIn", | |
| "description": "React Native component built to login users and obtain accessToken via callback", | |
| "version": "0.0.2", | |
| "main": "GithubSignIn.js", | |
| "dependencies": { | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment