Skip to content

Commit

Permalink
OAuth Implicit grant + DocuSign eSignature API example
Browse files Browse the repository at this point in the history
  • Loading branch information
LarryKlugerDS committed Sep 5, 2021
1 parent e993252 commit baef838
Show file tree
Hide file tree
Showing 19 changed files with 2,953 additions and 1,346 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
src/config.js
# Logs
logs
*.log
Expand Down
Binary file added assets/World_Wide_Corp_lorem.docx
Binary file not shown.
Binary file added assets/World_Wide_Corp_lorem.pdf
Binary file not shown.
17 changes: 17 additions & 0 deletions assets/info.mac.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>com.example.electron</string>
</array>
<key>CFBundleURLName</key>
<string>com.example.electron</string>
</dict>
</array>
</dict>
</plist>
17 changes: 17 additions & 0 deletions assets/info.mac_example.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>Your private scheme, from schemeName in src/config.js</string>
</array>
<key>CFBundleURLName</key>
<string>Your private scheme, from schemeName in src/config.js</string>
</dict>
</array>
</dict>
</plist>
61 changes: 61 additions & 0 deletions assets/public_thank_you/thankyou.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>Please close this tab</title>
<meta name="description" content="Please close this tab" />

<style>
body {
position: relative;
color: darkblue;
height: 100vh;
background: lightsteelblue;
font-family: 'Noto Sans JP', sans-serif;
overflow-y: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.newline {
display: flex;
justify-content: center;
align-items: center;
margin: 20px 0;
}
.tm {
margin-top: 3em;
}
#edge {
display: none;
}
</style>
</head>
<body>
<div>
<h1 class="newline">Thank you for logging in.</h1>
<h1 class="newline">Please close this tab.</h1>
<!-- Unfortunately Microsoft Edge sometimes wants user input but doesn't ask for it!! -->
<h2 id="edge" class="tm">
If the application is not opening: click within this tab, then type
Ctrl+Shift+R
</h2>
</div>

<script>
const electronUrl = 'com.example.electron:/implicit-result';
//console.log (`##### redirecting to ${electronUrl + window.hash}`)
window.location = electronUrl + window.location.hash;

// For Edge and IE browsers: show the Edge info
const agent = window.navigator.userAgent.toLowerCase();
const edge =
agent.indexOf('edg/') > -1 ||
agent.indexOf('edge/') > -1 ||
agent.indexOf('trident/') > -1;
if (edge) {
document.getElementById('edge').style.display = 'block';
}
</script>
</body>
</html>
12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"hardenedRuntime": true,
"entitlements": "assets/entitlements.mac.plist",
"entitlementsInherit": "assets/entitlements.mac.plist",
"gatekeeperAssess": false
"gatekeeperAssess": false,
"extendInfo": "assets/info.mac.plist"
},
"dmg": {
"contents": [
Expand Down Expand Up @@ -177,8 +178,10 @@
"@types/history": "4.7.8",
"@types/jest": "^26.0.24",
"@types/node": "15.0.2",
"@types/node-fetch": "^3.0.3",
"@types/react": "^17.0.9",
"@types/react-dom": "^17.0.9",
"@types/react-phone-number-input": "^3.0.10",
"@types/react-router": "^5.1.14",
"@types/react-router-dom": "^5.1.6",
"@types/react-test-renderer": "^17.0.1",
Expand Down Expand Up @@ -247,12 +250,15 @@
},
"dependencies": {
"electron-debug": "^3.2.0",
"electron-log": "^4.3.5",
"electron-log": "^4.4.1",
"electron-updater": "^4.3.8",
"history": "4.x.x",
"node-fetch": "^2.6.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-phone-number-input": "^3.1.25",
"react-router-dom": "^5.2.0",
"react-toastify": "^6.2.0",
"regenerator-runtime": "^0.13.5"
},
"devEngines": {
Expand All @@ -279,7 +285,7 @@
],
"singleQuote": true
},
"husky": {
"husky_not_used": {
"hooks": {
"pre-commit": "lint-staged"
}
Expand Down
17 changes: 17 additions & 0 deletions src/config_example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copy this file to config.js and update the settings.
// Don't store config.js in your repo!
// See the README_OAuth.md file for more information

// Example settings
const config = {
schemeName: 'com.example.electron',
schemeSlashCount: 1, // 1 or 2 (not a string!)
idpUrl: 'https://account-d.docusign.com',
implicitClientId: 'b2b5xxxx-xxxx-xxxx-xxxx-xxxxxxxxxx6b',
implicitReturnPath: 'implicit-result',
implicitScopes: 'signature',
implicitRedirectUrl: 'http://example.com/thank-you-page.html', // see assets/public_thank_you_page
tempAccessToken: "eyJ0e...." // Only used for debugging on a Mac. See Readme debugging section
};

export default config;
202 changes: 202 additions & 0 deletions src/main/docusign/DocuSign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/* eslint-disable prefer-destructuring */
/**
* DocuSign and related operations.
*/
import { app as electronApp } from 'electron';
import path from 'path';
import fs from 'fs/promises';
import fetch from 'node-fetch';

type SendEnvelopeResults = {
success: boolean;
errorMsg: string | undefined;
envelopeId: string | undefined;
availableApiRequests: number | undefined;
apiRequestsReset: Date | undefined;
traceId: string | undefined;
};

const sdkString = 'electron1';
const urlFrag = '/restapi/v2.1'; // DocuSign specific

/**
* Asset files
* Add assets (eg PDF files) to the top level assets directory.
* (NOT under /src.) They will be included with the packaged app.
*
* The assets directory relative to the appPath variable varies:
* Windows development
* appPath: Z:\www\windows\docusign-electron-react\src\main
* asset files: APP_PATH/../../assets
*
* Windows packaged
* appPath: C:\Users\your.name\AppData\Local\Programs\electron-react-boilerplate\resources\app.asar
* asset files: APP_PATH/../assets
*
* Mac development
* appPath: /Users/your.name/www/docusign-electron-react/src/main
* asset files: APP_PATH/../../assets
*
* Mac packaged
* appPath: App path: /Applications/ElectronReact.app/Contents/Resources/app.asar
* asset files: APP_PATH/../assets
*
*/

/**
* Send an envelope, return results or error
*/
const sendEnvelope = async (
baseUri: string,
accountId: string,
accessToken: string,
documentFileName: string,
documentName: string,
documentExtension: string,
emailSubject: string,
signer1Email: string,
signer1Name: string,
signer1SmsCountryCode: string,
signer1SmsNumber: string
): Promise<SendEnvelopeResults> => {
const appPath = electronApp.getAppPath();

let result: SendEnvelopeResults;

const pathOptions = ['..', '../..'];
let docPath = '.';
let foundFile;
// eslint-disable-next-line no-restricted-syntax
for (const pathOption of pathOptions) {
docPath = path.join(appPath, pathOption, 'assets', documentFileName);
try {
// eslint-disable-next-line no-await-in-loop
foundFile = (await fs.access(docPath)) === undefined;
if (foundFile) {
break;
}
} catch {
foundFile = false;
}
}
if (!foundFile) {
result = {
success: false,
errorMsg: `Could not locate document file. [appPath: ${appPath}]`,
envelopeId: undefined,
availableApiRequests: undefined,
apiRequestsReset: undefined,
traceId: undefined,
};
return result; // EARLY return
}

const docContents: string = await fs.readFile(docPath, {
encoding: 'base64',
});
const envelopeRequest = {
emailSubject,
status: 'sent',
recipients: {
signers: [
{
email: signer1Email,
name: signer1Name,
additionalNotifications: [
{
secondaryDeliveryMethod: 'SMS',
phoneNumber: {
countryCode: signer1SmsCountryCode,
number: signer1SmsNumber,
},
},
],
recipientId: '1',
tabs: {
signHereTabs: [
{
anchorString: '/sn1/',
anchorXOffset: '20',
anchorUnits: 'pixels',
},
],
},
},
],
},
documents: [
{
name: documentName,
fileExtension: documentExtension,
documentId: '1',
documentBase64: docContents,
htmlDefinition: {
source: 'document',
},
},
],
};

try {
const url = `${baseUri}${urlFrag}/accounts/${accountId}/envelopes`;
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(envelopeRequest),
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: `application/json`,
'Content-Type': 'application/json',
'X-DocuSign-SDK': sdkString,
},
});
const data = response && response.ok && (await response.json());
const headers = response.headers;
const availableApiReqHeader = headers.get('X-RateLimit-Remaining');
const availableApiRequests = availableApiReqHeader
? parseInt(availableApiReqHeader, 10)
: undefined;
const apiResetHeader = headers.get('X-RateLimit-Reset');
const apiRequestsReset = apiResetHeader
? new Date(parseInt(apiResetHeader, 10) * 1000)
: undefined;
const traceId = headers.get('X-DocuSign-TraceToken') || undefined;
if (response.ok) {
result = {
success: true,
errorMsg: undefined,
envelopeId: data.envelopeId,
availableApiRequests,
apiRequestsReset,
traceId,
};
} else {
result = {
success: false,
errorMsg: response && (await response.text()),
envelopeId: undefined,
availableApiRequests,
apiRequestsReset,
traceId,
};
}
return result;
} catch (e) {
// Unfortunately we don't have access to the real
// networking problem!
// See https://medium.com/to-err-is-aaron/detect-network-failures-when-using-fetch-40a53d56e36
const errorMsg =
e.message === 'Failed to fetch'
? 'Networking error—check your Internet and DNS connections'
: e.message;
return {
success: false,
errorMsg,
envelopeId: undefined,
availableApiRequests: undefined,
apiRequestsReset: undefined,
traceId: undefined,
};
}
};

export default sendEnvelope;
Loading

0 comments on commit baef838

Please # to comment.