Skip to content

Commit 216b08e

Browse files
authored
Merge pull request #159 from docusign/feature/maestro-api-examples
Maestro API code examples
2 parents b131f5a + 4e421ba commit 216b08e

21 files changed

+1466
-11
lines changed

config/appsettings.example.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"clickAPIUrl": "https://demo.docusign.net/clickapi",
3030
"adminAPIUrl": "https://api-d.docusign.net/management",
3131
"monitorApiUrl": "https://lens-d.docusign.net",
32+
"maestroApiUrl": "https://demo.services.docusign.net",
3233
"webformsApiUrl": "https://apps-d.docusign.com/api/webforms/v1.1",
3334
"codeExamplesManifest": "https://raw.githubusercontent.com/docusign/code-examples-csharp/master/manifest/CodeExamplesManifest.json"
3435
}

index.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const {
5454
} = require('./lib/admin/controllers');
5555

5656
const { eg001connect } = require('./lib/connect/controllers');
57+
const { eg001maestro, eg002maestro, eg003maestro } = require('./lib/maestro/controllers');
5758
const { eg001webforms } = require('./lib/webforms/controllers');
5859

5960
const PORT = process.env.PORT || 3000;
@@ -273,6 +274,14 @@ app.get('/eg001', eg001.getController)
273274
app.get('/cneg001', eg001connect.getController)
274275
.post('/cneg001', eg001connect.createController);
275276

277+
app.get('/mseg001', eg001maestro.getController)
278+
.post('/mseg001', eg001maestro.createController)
279+
.post('/mseg001publish', eg001maestro.publishController)
280+
.get('/mseg002', eg002maestro.getController)
281+
.post('/mseg002', eg002maestro.createController)
282+
.get('/mseg003', eg003maestro.getController)
283+
.post('/mseg003', eg003maestro.createController);
284+
276285
app.get('/weg001', eg001webforms.getController)
277286
.get('/weg001webForm', eg001webforms.getWebFormCreateController)
278287
.post('/weg001', eg001webforms.createWebFormTemplate)
@@ -323,11 +332,12 @@ const ADMIN_SCOPES = [
323332
'user_data_redact', 'asset_group_account_read', 'asset_group_account_clone_write',
324333
'asset_group_account_clone_read'
325334
];
335+
const MAESTRO_SCOPES = ['signature', 'aow_manage'];
326336
const WEBFORMS_SCOPES = [
327337
'webforms_read', 'webforms_instance_read', 'webforms_instance_write'
328338
];
329339

330-
const scope = [...ROOM_SCOPES, ...CLICK_SCOPES, ...MONITOR_SCOPES, ...ADMIN_SCOPES, ...WEBFORMS_SCOPES, ...SCOPES];
340+
const scope = [...ROOM_SCOPES, ...CLICK_SCOPES, ...MONITOR_SCOPES, ...ADMIN_SCOPES, ...SCOPES, ...WEBFORMS_SCOPES, ...MAESTRO_SCOPES];
331341

332342
// Configure passport for DocusignStrategy
333343
let docusignStrategy = new DocusignStrategy({

lib/DSJwtAuth.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ let DsJwtAuth = function _DsJwtAuth(req) {
1818
this.accountName = req.user && req.user.accountName;
1919
this.basePath = req.user && req.user.basePath;
2020
this._tokenExpiration = req.user && req.user.tokenExpirationTimestamp;
21-
this.scopes = 'signature dtr.rooms.read dtr.rooms.write dtr.documents.read dtr.documents.write dtr.profile.read dtr.profile.write dtr.company.read dtr.company.write room_forms click.manage click.send organization_read group_read permission_read user_read user_write account_read domain_read identity_provider_read user_data_redact asset_group_account_read asset_group_account_clone_write asset_group_account_clone_read webforms_read webforms_instance_read webforms_instance_write';
21+
this.scopes = 'signature dtr.rooms.read dtr.rooms.write dtr.documents.read dtr.documents.write dtr.profile.read dtr.profile.write dtr.company.read dtr.company.write room_forms click.manage click.send organization_read group_read permission_read user_read user_write account_read domain_read identity_provider_read user_data_redact asset_group_account_read asset_group_account_clone_write asset_group_account_clone_read webforms_read webforms_instance_read webforms_instance_write aow_manage';
2222

2323
// For production use, you'd want to store the refresh token in non-volatile storage since it is
2424
// good for 30 days. You'd probably want to encrypt it too.

lib/common/DSJwtAuth.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
this.accountName = req.user && req.user.accountName;
1919
this.basePath = req.user && req.user.basePath;
2020
this._tokenExpiration = req.user && req.user.tokenExpirationTimestamp;
21-
this.scopes = 'signature dtr.rooms.read dtr.rooms.write dtr.documents.read dtr.documents.write dtr.profile.read dtr.profile.write dtr.company.read dtr.company.write room_forms click.manage click.send webforms_read webforms_instance_read webforms_instance_write';
21+
this.scopes = 'signature dtr.rooms.read dtr.rooms.write dtr.documents.read dtr.documents.write dtr.profile.read dtr.profile.write dtr.company.read dtr.company.write room_forms click.manage click.send webforms_read webforms_instance_read webforms_instance_write aow_manage';
2222

2323
// For production use, you'd want to store the refresh token in non-volatile storage since it is
2424
// good for 30 days. You'd probably want to encrypt it too.
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/**
2+
* @file
3+
* Example 001: How to trigger a Maestro workflow
4+
* @author DocuSign
5+
*/
6+
7+
const path = require('path');
8+
const validator = require('validator');
9+
const { formatString, API_TYPES } = require('../../utils.js');
10+
const { getExampleByNumber } = require('../../manifestService');
11+
const dsConfig = require('../../../config/index.js').config;
12+
const { getWorkflowDefinitions, getWorkflowDefinition, triggerWorkflow } = require('../examples/triggerWorkflow');
13+
const { createWorkflow, publishWorkflow } = require('../workflowUtils.js');
14+
15+
const eg001TriggerWorkflow = exports;
16+
const exampleNumber = 1;
17+
const eg = `mseg00${exampleNumber}`; // This example reference.
18+
const api = API_TYPES.MAESTRO;
19+
const mustAuthenticate = '/ds/mustAuthenticate';
20+
const minimumBufferMin = 3;
21+
const workflowName = 'Example workflow - send invite to signer';
22+
23+
24+
/**
25+
* Trigger workflow
26+
* @param {object} req Request obj
27+
* @param {object} res Response obj
28+
*/
29+
eg001TriggerWorkflow.createController = async (req, res) => {
30+
// Step 1. Check the token
31+
// At this point we should have a good token. But we
32+
// double-check here to enable a better UX to the user.
33+
const isTokenOK = req.dsAuth.checkToken(minimumBufferMin);
34+
if (!isTokenOK) {
35+
req.flash('info', 'Sorry, you need to re-authenticate.');
36+
// Save the current operation so it will be resumed after authentication
37+
req.dsAuth.setEg(req, eg);
38+
return res.redirect(mustAuthenticate);
39+
}
40+
41+
// Step 2. Call the worker method
42+
const { body } = req;
43+
const args = {
44+
instanceName: validator.escape(body.instanceName),
45+
signerEmail: validator.escape(body.signerEmail),
46+
signerName: validator.escape(body.signerName),
47+
ccEmail: validator.escape(body.ccEmail),
48+
ccName: validator.escape(body.ccName),
49+
workflowId: req.session.workflowId,
50+
accessToken: req.user.accessToken,
51+
basePath: dsConfig.maestroApiUrl,
52+
accountId: req.session.accountId,
53+
};
54+
let results = null;
55+
56+
try {
57+
const workflow = await getWorkflowDefinition(args);
58+
results = await triggerWorkflow(workflow, args);
59+
} catch (error) {
60+
const errorCode = error?.response?.statusCode;
61+
const errorMessage = error?.response?.body?.message;
62+
let errorInfo;
63+
64+
// use custom error message if Maestro is not enabled for the account
65+
if (errorCode === 403) {
66+
errorInfo = formatString(res.locals.manifest.SupportingTexts.ContactSupportToEnableFeature, 'Maestro');
67+
}
68+
69+
return res.render('pages/error', { err: error, errorCode, errorMessage, errorInfo });
70+
}
71+
if (results) {
72+
req.session.instanceId = results.instanceId; // Save for use by other examples
73+
// which need an envelopeId
74+
const example = getExampleByNumber(res.locals.manifest, exampleNumber, api);
75+
res.render('pages/example_done', {
76+
title: example.ExampleName,
77+
message: formatString(example.ResultsPageText, JSON.stringify(results.envelopeId)),
78+
json: JSON.stringify(results),
79+
});
80+
}
81+
};
82+
83+
/**
84+
* Form page for this application
85+
*/
86+
eg001TriggerWorkflow.getController = async (req, res) => {
87+
// Check that the authentication token is ok with a long buffer time.
88+
// If needed, now is the best time to ask the user to authenticate
89+
// since they have not yet entered any information into the form.
90+
const isTokenOK = req.dsAuth.checkToken();
91+
if (!isTokenOK) {
92+
// Save the current operation so it will be resumed after authentication
93+
req.dsAuth.setEg(req, eg);
94+
return res.redirect(mustAuthenticate);
95+
}
96+
97+
const example = getExampleByNumber(res.locals.manifest, exampleNumber, api);
98+
const additionalPageData = example.AdditionalPage.filter(p => p.Name === 'publish_workflow')[0];
99+
const sourceFile =
100+
path.basename(__filename)[5].toLowerCase() +
101+
path.basename(__filename).substr(6);
102+
103+
const args = {
104+
templateId: req.session.templateId,
105+
accessToken: req.user.accessToken,
106+
basePath: dsConfig.maestroApiUrl,
107+
accountId: req.session.accountId,
108+
};
109+
110+
try {
111+
const workflows = await getWorkflowDefinitions(args);
112+
113+
if (workflows.count > 0) {
114+
const workflow = workflows.value
115+
.filter(workflow => workflow.name === workflowName)
116+
.sort((wf1, wf2) => wf2.lastUpdatedDate - wf1.lastUpdatedDate)[0];
117+
if (workflow) {
118+
req.session.workflowId = workflow.id;
119+
}
120+
}
121+
122+
// if there is no workflow, then create one
123+
if (!req.session.workflowId) {
124+
if (!req.session.templateId) {
125+
return res.render('pages/maestro-examples/eg001TriggerWorkflow', {
126+
eg: eg,
127+
csrfToken: req.csrfToken(),
128+
example: example,
129+
templateOk: false,
130+
sourceFile: sourceFile,
131+
sourceUrl: dsConfig.githubExampleUrl + 'maestro/examples/' + sourceFile,
132+
documentation: dsConfig.documentation + eg,
133+
showDoc: dsConfig.documentation,
134+
});
135+
}
136+
137+
req.session.workflowId = await createWorkflow(args);
138+
139+
const consentUrl = await publishWorkflow(args, req.session.workflowId);
140+
if (consentUrl) {
141+
return res.render('pages/maestro-examples/eg001PublishWorkflow', {
142+
example,
143+
consentUrl,
144+
message: additionalPageData.ResultsPageText,
145+
csrfToken: req.csrfToken(),
146+
});
147+
}
148+
}
149+
} catch (error) {
150+
const errorCode = error?.response?.statusCode;
151+
const errorMessage = error?.response?.body?.message;
152+
let errorInfo;
153+
154+
// use custom error message if Maestro is not enabled for the account
155+
if (errorCode === 403) {
156+
errorInfo = formatString(res.locals.manifest.SupportingTexts.ContactSupportToEnableFeature, 'Maestro');
157+
}
158+
159+
return res.render('pages/error', { err: error, errorCode, errorMessage, errorInfo });
160+
}
161+
162+
res.render('pages/maestro-examples/eg001TriggerWorkflow', {
163+
eg: eg,
164+
csrfToken: req.csrfToken(),
165+
example: example,
166+
sourceFile: sourceFile,
167+
templateOk: true,
168+
sourceUrl: dsConfig.githubExampleUrl + 'maestro/examples/' + sourceFile,
169+
documentation: dsConfig.documentation + eg,
170+
showDoc: dsConfig.documentation,
171+
});
172+
};
173+
174+
/**
175+
* Publish workflow page
176+
*/
177+
eg001TriggerWorkflow.publishController = async (req, res) => {
178+
// Check that the authentication token is ok with a long buffer time.
179+
// If needed, now is the best time to ask the user to authenticate
180+
// since they have not yet entered any information into the form.
181+
const isTokenOK = req.dsAuth.checkToken();
182+
if (!isTokenOK) {
183+
// Save the current operation so it will be resumed after authentication
184+
req.dsAuth.setEg(req, eg);
185+
return res.redirect(mustAuthenticate);
186+
}
187+
188+
const example = getExampleByNumber(res.locals.manifest, exampleNumber, api);
189+
const additionalPageData = example.AdditionalPage.filter(p => p.Name === 'publish_workflow')[0];
190+
191+
try {
192+
const args = {
193+
accessToken: req.user.accessToken,
194+
basePath: dsConfig.maestroApiUrl,
195+
accountId: req.session.accountId,
196+
};
197+
const consentUrl = await publishWorkflow(args, req.session.workflowId);
198+
if (consentUrl) {
199+
return res.render('pages/maestro-examples/eg001PublishWorkflow', {
200+
example,
201+
consentUrl,
202+
message: additionalPageData.ResultsPageText,
203+
csrfToken: req.csrfToken(),
204+
});
205+
}
206+
} catch (error) {
207+
const errorCode = error?.response?.statusCode;
208+
const errorMessage = error?.response?.body?.message;
209+
let errorInfo;
210+
211+
// use custom error message if Maestro is not enabled for the account
212+
if (errorCode === 403) {
213+
errorInfo = formatString(res.locals.manifest.SupportingTexts.ContactSupportToEnableFeature, 'Maestro');
214+
}
215+
216+
return res.render('pages/error', { err: error, errorCode, errorMessage, errorInfo });
217+
}
218+
219+
const sourceFile =
220+
path.basename(__filename)[5].toLowerCase() +
221+
path.basename(__filename).substr(6);
222+
res.render('pages/maestro-examples/eg001TriggerWorkflow', {
223+
eg: eg,
224+
csrfToken: req.csrfToken(),
225+
example: example,
226+
sourceFile: sourceFile,
227+
templateOk: true,
228+
sourceUrl: dsConfig.githubExampleUrl + 'maestro/examples/' + sourceFile,
229+
documentation: dsConfig.documentation + eg,
230+
showDoc: dsConfig.documentation,
231+
});
232+
};

0 commit comments

Comments
 (0)