Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
443 lines (417 sloc)
15.7 KB
This file contains 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 from 'react'; | |
import Container from 'react-bootstrap/Container'; | |
import Row from 'react-bootstrap/Row'; | |
import Col from 'react-bootstrap/Col'; | |
import Button from 'react-bootstrap/Button'; | |
import Form from 'react-bootstrap/Form'; | |
import Jumbotron from 'react-bootstrap/Jumbotron'; | |
import Navbar from 'react-bootstrap/Navbar'; | |
import Nav from 'react-bootstrap/Nav'; | |
import { ToastContainer, toast } from 'react-toastify'; | |
import OAuthImplicit from './OAuthImplicit'; | |
import DocuSign from './DocuSign'; | |
import './App.css'; | |
class App extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
accessToken: undefined, | |
expires: undefined, | |
name: undefined, | |
email: undefined, | |
externalAccountId: undefined, | |
accountName: undefined, | |
accountId: undefined, | |
baseUri: undefined, | |
page: 'welcome', // initial page. Pages: welcome|loggedIn | |
working: false, | |
workingMessage: '', | |
responseErrorMsg: undefined, | |
responseEnvelopeId: undefined, | |
responseAvailableApiRequests: undefined, | |
responseApiRequestsReset: undefined, | |
responseSuccess: undefined, | |
responseTraceId: undefined, | |
resultsEnvelopeJson: undefined, | |
formName: '', | |
formEmail: '', | |
}; | |
this.oAuthImplicit = new OAuthImplicit(this); | |
this.docusign = new DocuSign(this); | |
// bind <this> for methods called by React via buttons, etc | |
this.logout = this.logout.bind(this); | |
this.startAuthentication = this.startAuthentication.bind(this); | |
this.formNameChange = this.formNameChange.bind(this); | |
this.formEmailChange = this.formEmailChange.bind(this); | |
this.sendEnvelope = this.sendEnvelope.bind(this); | |
this.getEnvelope = this.getEnvelope.bind(this); | |
this.receiveMessage = this.receiveMessage.bind(this); | |
} | |
/** | |
* Starting up--if our URL includes a hash, check it to see if | |
* it's the OAuth response | |
*/ | |
async componentDidMount() { | |
const config = window.config; | |
// if the url has a query parameter of ?error=logout_request (from a logout operation) | |
// then remove it | |
if (window.location.search && window.location.search === '?error=logout_request') { | |
window.history.replaceState(null, '', config.DS_APP_URL); | |
} | |
if (config?.DS_REDIRECT_AUTHENTICATION) { | |
const hash = window.location.hash; | |
if (!hash) {return} | |
// possible OAuth response | |
this.setState({working: true, workingMessage: 'Logging in'}); | |
await this.oAuthImplicit.receiveHash(hash); | |
this.setState({working: false}); | |
} else { | |
// await authentication via the new tab | |
window.addEventListener("message", this.receiveMessage, false); | |
} | |
} | |
/** | |
* Receive message from a child . | |
* This method is only used if authentication is done | |
* in a new tab. See file public/oauthResponse.html | |
* @param {object} e | |
*/ | |
async receiveMessage(e) { | |
const rawSource = e && e.data && e.data.source | |
, ignore = {'react-devtools-inject-backend': true, | |
'react-devtools-content-script': true, | |
'react-devtools-detector': true, | |
'react-devtools-bridge': true} | |
, source = (rawSource && !ignore[rawSource]) ? rawSource : false | |
; | |
if (!source) {return}; // Ignore if no source field | |
if (source === 'oauthResponse') { | |
this.setState({working: true, workingMessage: 'Logging in'}); | |
const hash = e.data && e.data.hash; | |
await this.oAuthImplicit.receiveHash(hash); | |
this.setState ({working: false}); | |
} | |
} | |
startAuthentication() { | |
this.oAuthImplicit.startLogin(); | |
} | |
/** | |
* Is the accessToken ok to use? | |
* @returns boolean accessTokenIsGood | |
*/ | |
checkToken() { | |
if ( | |
!this.state.accessToken || | |
this.state.expires === undefined || | |
new Date() > this.state.expires | |
) { | |
// Need new login. Only clear auth, don't clear the state (leave form contents); | |
this.clearAuth(); | |
this.setState({ page: 'welcome', working: false }); | |
toast.error('Your login session has ended.\nPlease login again', { | |
autoClose: 8000, | |
}); | |
return false; | |
} | |
return true; | |
} | |
/** | |
* This method clears this app's authentication information. | |
* But there may still be an active login session cookie | |
* from the IdP. Your IdP may have an API method for clearing | |
* the login session. | |
*/ | |
logout() { | |
this.clearAuth(); | |
this.clearState(); | |
this.setState({ page: 'welcome' }); | |
toast.success('You have logged out.', { autoClose: 5000 }); | |
this.oAuthImplicit.logout(); | |
} | |
/** | |
* Clear authentication-related state | |
*/ | |
clearAuth() { | |
this.setState({ | |
accessToken: undefined, | |
expires: undefined, | |
accountId: undefined, | |
externalAccountId: undefined, | |
accountName: undefined, | |
baseUri: undefined, | |
name: undefined, | |
email: undefined, | |
}) | |
} | |
/** | |
* Clear the app's form and related state | |
*/ | |
clearState() { | |
this.setState({ | |
formName: '', | |
formEmail: '', | |
working: false, | |
responseErrorMsg: undefined, | |
responseEnvelopeId: undefined, | |
responseAvailableApiRequests: undefined, | |
responseApiRequestsReset: undefined, | |
responseSuccess: undefined, | |
responseTraceId: undefined, | |
resultsEnvelopeJson: undefined, | |
}); | |
} | |
/** | |
* Process the oauth results. | |
* This method is called by the OAuthImplicit class | |
* @param results | |
*/ | |
oAuthResults(results) { | |
this.setState({ | |
accessToken: results.accessToken, expires: results.expires, | |
name: results.name, externalAccountId: results.externalAccountId, | |
email: results.email, accountId: results.accountId, | |
accountName: results.accountName, baseUri: results.baseUri, | |
page: 'loggedIn', | |
formName: results.name, // default: set to logged in user | |
formEmail: results.email, | |
}); | |
toast.success(`Welcome ${results.name}, you are now logged in`); | |
} | |
formNameChange(event) { | |
this.setState({ formName: event.target.value }); | |
} | |
formEmailChange(event) { | |
this.setState({ formEmail: event.target.value }); | |
} | |
async sendEnvelope() { | |
this.setState({ | |
responseErrorMsg: undefined, | |
responseEnvelopeId: undefined, | |
responseAvailableApiRequests: undefined, | |
responseApiRequestsReset: undefined, | |
responseSuccess: undefined, | |
responseTraceId: undefined, | |
resultsEnvelopeJson: undefined, | |
}); | |
if (!this.checkToken()) { | |
return; // Problem! The user needs to login | |
} | |
if (!this.state.formEmail || this.state.formEmail.length < 5) { | |
toast.error("Problem: Enter the signer's email address"); | |
return; | |
} | |
if (!this.state.formName || this.state.formName.length < 5) { | |
toast.error("Problem: Enter the signer's name"); | |
return; | |
} | |
this.setState({ working: true, workingMessage: "Sending envelope" }); | |
const results = await this.docusign.sendEnvelope(); | |
const { apiRequestsReset } = results; | |
const responseApiRequestsReset = apiRequestsReset ? | |
new Date(apiRequestsReset) : undefined; | |
this.setState({ | |
working: false, | |
responseSuccess: results.success, | |
responseErrorMsg: results.errorMsg, | |
responseEnvelopeId: results.envelopeId, | |
responseAvailableApiRequests: results.availableApiRequests, | |
responseTraceId: results.traceId, | |
responseApiRequestsReset, | |
}); | |
} | |
async getEnvelope() { | |
this.setState({ | |
responseErrorMsg: undefined, | |
responseEnvelopeId: undefined, | |
responseAvailableApiRequests: undefined, | |
responseApiRequestsReset: undefined, | |
responseSuccess: undefined, | |
responseTraceId: undefined, | |
}); | |
if (!this.checkToken()) { | |
return; // Problem! The user needs to login | |
} | |
if (!this.state.responseEnvelopeId) { | |
toast.error("Problem: First send an envelope"); | |
return; | |
} | |
this.setState({ working: true, workingMessage: "Fetching the envelope's status" }); | |
const results = await this.docusign.getEnvelope(); | |
const { apiRequestsReset } = results; | |
const responseApiRequestsReset = apiRequestsReset | |
? new Date(apiRequestsReset) : undefined; | |
this.setState({ | |
working: false, | |
responseSuccess: results.success, | |
responseErrorMsg: results.errorMsg, | |
responseAvailableApiRequests: results.availableApiRequests, | |
responseTraceId: results.traceId, | |
resultsEnvelopeJson: results.resultsEnvelopeJson, | |
responseApiRequestsReset, | |
}); | |
} | |
/** | |
* Render this component | |
*/ | |
render() { | |
// Just two pages with a common header. | |
// Choose the body of the page: | |
let pagebody; | |
switch (this.state.page) { | |
case 'welcome': // not logged in | |
pagebody = this.Welcome(); | |
break; | |
case 'loggedIn': | |
pagebody = this.LoggedIn(); | |
break; | |
default: | |
pagebody = this.Welcome(); | |
}; | |
// Compute the name block for the top nav section | |
let nameBlock; | |
if (this.state.accessToken) { | |
nameBlock = ( | |
<Navbar.Text> | |
{this.state.name}<br/> | |
{this.state.accountName} ({this.state.externalAccountId}) | |
<Nav> | |
<Nav.Link href="#" onClick={()=> this.logout()}>Logout</Nav.Link> | |
</Nav> | |
</Navbar.Text> | |
) | |
} else { | |
nameBlock = null; | |
} | |
// The spinner | |
const spinner = ( | |
<Container fluid className='bodyMargin' | |
style={{ display: this.state.working ? 'block' : 'none' }}> | |
<Row className='justify-content-center'> | |
<div className="spinner" /> | |
</Row> | |
<Row className='justify-content-center'> | |
<h3>{this.state.workingMessage}…</h3> | |
</Row> | |
</Container> | |
) | |
// The complete page: | |
return ( | |
<> | |
<Navbar fixed="top" bg="primary" variant="dark" > | |
<Navbar.Brand>DocuSign Code Example</Navbar.Brand> | |
<Navbar.Toggle /> | |
<Navbar.Collapse className="justify-content-end">{nameBlock}</Navbar.Collapse> | |
</Navbar> | |
<ToastContainer /> | |
{spinner} | |
{pagebody} | |
</> | |
) | |
} | |
LoggedIn() { | |
const resetTime = this.state.responseApiRequestsReset; | |
const resetTimeString = resetTime | |
? new Intl.DateTimeFormat('en-US', { | |
dateStyle: 'medium', | |
timeStyle: 'full', | |
}).format(resetTime) | |
: undefined; | |
return ( | |
<Container className='bodyMargin'> | |
<Row> | |
<Col className="col-md-4"> | |
<h2>Send an Envelope</h2> | |
<Form> | |
<Form.Group controlId="formName"> | |
<Form.Label>Name</Form.Label> | |
<Form.Control type="text" placeholder="Name" | |
value={this.state.formName} | |
onChange={this.formNameChange} | |
/> | |
</Form.Group> | |
<Form.Group controlId="formEmail"> | |
<Form.Label>Email</Form.Label> | |
<Form.Control type="email" placeholder="Email" | |
value={this.state.formEmail} | |
onChange={this.formEmailChange} | |
/> | |
</Form.Group> | |
<Button variant="primary" onClick={this.sendEnvelope}> | |
Send Envelope | |
</Button> | |
<Button variant="primary" className='ml-4' onClick={this.getEnvelope}> | |
Get Envelope Status | |
</Button> | |
</Form> | |
</Col> | |
</Row> | |
<Row className='mt-4'> | |
<Col> | |
<h2>Results</h2> | |
<h2> | |
{this.state.responseSuccess !== undefined ? ( | |
this.state.responseSuccess ? ( | |
<>✅ Success!</> | |
) : ( | |
<>❌ Problem!</> | |
) | |
) : null} | |
</h2> | |
{this.state.responseErrorMsg ? ( | |
<p>Error message: {this.state.responseErrorMsg}</p> | |
) : null} | |
{this.state.responseEnvelopeId ? ( | |
<p>Envelope ID: {this.state.responseEnvelopeId}</p> | |
) : null} | |
{this.state.resultsEnvelopeJson ? ( | |
<p><pre>Response: {JSON.stringify(this.state.resultsEnvelopeJson, null, 4)}</pre></p> | |
) : null} | |
{this.state.responseAvailableApiRequests ? ( | |
<p> | |
Available API requests: {this.state.responseAvailableApiRequests} | |
</p> | |
) : null} | |
{resetTimeString ? ( | |
<p>API requests reset time: {resetTimeString}</p> | |
) : null} | |
{this.state.responseTraceId ? ( | |
<p> | |
Trace ID: {this.state.responseTraceId}. Please include with all | |
customer service questions. | |
</p> | |
) : null} | |
</Col> | |
</Row> | |
</Container> | |
)} | |
Welcome() { | |
return ( | |
<Container fluid className='welcomeMargin'> | |
<Row> | |
<Col> | |
<Jumbotron> | |
<h1>React Example with OAuth Authentication</h1> | |
<p> | |
In this example the user authenticates with DocuSign via the OAuth Implicit grant flow. | |
Since the app will then have an access token for the user, the app can call any | |
DocuSign eSignature REST API method. | |
</p> | |
<p> | |
Use this example for apps used by the staff of your organization who have | |
DocuSign accounts. For example, an application could pull data from multiple | |
sources and then send an envelope that includes the data. | |
</p> | |
<p> | |
Login with your DocuSign Developer (Demo) credentials. | |
</p> | |
<p> | |
<Button variant="primary" onClick={this.startAuthentication}>Login</Button> | |
</p> | |
</Jumbotron> | |
</Col> | |
</Row> | |
</Container> | |
) | |
} | |
} | |
export default App; |