Skip to content

Commit 1d505d9

Browse files
committed
more fixes
1 parent 8f0e38f commit 1d505d9

12 files changed

Lines changed: 454 additions & 187 deletions

File tree

apps/sim/app/api/files/parse/route.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,8 +484,8 @@ describe('File Parse API Route', () => {
484484
const response = await POST(req)
485485
const data = await response.json()
486486

487-
expect(response.status).toBe(413)
488-
expect(data.success).toBe(false)
487+
expect(response.status).toBe(200)
488+
expect(data.success).toBe(true)
489489
expect(data.error).toContain('too large')
490490
expect(data.results).toHaveLength(1)
491491
expect(data.results[0].output.content).toBe('first file')

apps/sim/app/api/files/parse/route.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,17 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
112112
request,
113113
{},
114114
{
115-
validationErrorResponse: (error) =>
116-
NextResponse.json(
115+
validationErrorResponse: (error) => {
116+
const message = getValidationErrorMessage(error, 'Invalid request data')
117+
return NextResponse.json(
117118
{
118119
success: false,
119-
error: getValidationErrorMessage(error, 'Invalid request data'),
120+
error: message,
120121
filePath: '',
121122
},
122-
{ status: 400 }
123-
),
123+
{ status: message.includes('At most 10 files') ? 413 : 400 }
124+
)
125+
},
124126
}
125127
)
126128
if (!parsed.success) return parsed.response
@@ -397,15 +399,16 @@ function validateFileReferenceShape(filePath: string): { isValid: boolean; error
397399
}
398400

399401
function parsedOutputTooLargeResponse(results?: unknown[]): NextResponse {
402+
const hasPartialResults = Boolean(results && results.length > 0)
400403
return NextResponse.json(
401404
{
402-
success: false,
405+
success: hasPartialResults,
403406
error: `Parsed file output is too large to return safely. Maximum combined parsed output is ${prettySize(
404407
MAX_MULTI_FILE_PARSE_OUTPUT_BYTES
405408
)}.`,
406409
...(results && results.length > 0 ? { results } : {}),
407410
},
408-
{ status: 413 }
411+
{ status: hasPartialResults ? 200 : 413 }
409412
)
410413
}
411414

apps/sim/app/api/tools/docusign/route.ts

Lines changed: 112 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import { assertToolFileAccess } from '@/app/api/files/authorization'
2222

2323
const logger = createLogger('DocuSignAPI')
2424
const MAX_DOCUSIGN_DOCUMENT_BYTES = 25 * 1024 * 1024
25+
const MAX_LEGACY_INLINE_DOCUMENT_BYTES = 7 * 1024 * 1024
2526
const MAX_DOCUSIGN_JSON_BYTES = 2 * 1024 * 1024
27+
const DOCUSIGN_FETCH_TIMEOUT_MS = 30_000
2628

2729
interface DocuSignAccountInfo {
2830
accountId: string
@@ -47,14 +49,41 @@ function docusignError(data: Record<string, unknown>, fallback: string): string
4749
)
4850
}
4951

52+
async function fetchDocusign(
53+
input: string,
54+
init: RequestInit = {},
55+
parentSignal?: AbortSignal
56+
): Promise<Response> {
57+
const controller = new AbortController()
58+
const timeout = setTimeout(() => {
59+
controller.abort(new Error('DocuSign request timed out'))
60+
}, DOCUSIGN_FETCH_TIMEOUT_MS)
61+
const abort = () => controller.abort(parentSignal?.reason ?? new Error('Request aborted'))
62+
parentSignal?.addEventListener('abort', abort, { once: true })
63+
64+
try {
65+
return await fetch(input, { ...init, signal: controller.signal })
66+
} finally {
67+
clearTimeout(timeout)
68+
parentSignal?.removeEventListener('abort', abort)
69+
}
70+
}
71+
5072
/**
5173
* Resolves the user's DocuSign account info from their access token
5274
* by calling the DocuSign userinfo endpoint.
5375
*/
54-
async function resolveAccount(accessToken: string): Promise<DocuSignAccountInfo> {
55-
const response = await fetch('https://account-d.docusign.com/oauth/userinfo', {
56-
headers: { Authorization: `Bearer ${accessToken}` },
57-
})
76+
async function resolveAccount(
77+
accessToken: string,
78+
signal?: AbortSignal
79+
): Promise<DocuSignAccountInfo> {
80+
const response = await fetchDocusign(
81+
'https://account-d.docusign.com/oauth/userinfo',
82+
{
83+
headers: { Authorization: `Bearer ${accessToken}` },
84+
},
85+
signal
86+
)
5887

5988
if (!response.ok) {
6089
const errorText = await readResponseTextWithLimit(response, {
@@ -120,7 +149,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
120149
const { accessToken, operation, ...params } = parsed.data.body
121150

122151
try {
123-
const account = await resolveAccount(accessToken)
152+
const account = await resolveAccount(accessToken, request.signal)
124153
const apiBase = `${account.baseUri}/restapi/v2.1/accounts/${account.accountId}`
125154
const headers: Record<string, string> = {
126155
Authorization: `Bearer ${accessToken}`,
@@ -129,21 +158,27 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
129158

130159
switch (operation) {
131160
case 'send_envelope':
132-
return await handleSendEnvelope(apiBase, headers, params, authResult.userId)
161+
return await handleSendEnvelope(apiBase, headers, params, authResult.userId, request.signal)
133162
case 'create_from_template':
134-
return await handleCreateFromTemplate(apiBase, headers, params)
163+
return await handleCreateFromTemplate(apiBase, headers, params, request.signal)
135164
case 'get_envelope':
136-
return await handleGetEnvelope(apiBase, headers, params)
165+
return await handleGetEnvelope(apiBase, headers, params, request.signal)
137166
case 'list_envelopes':
138-
return await handleListEnvelopes(apiBase, headers, params)
167+
return await handleListEnvelopes(apiBase, headers, params, request.signal)
139168
case 'void_envelope':
140-
return await handleVoidEnvelope(apiBase, headers, params)
169+
return await handleVoidEnvelope(apiBase, headers, params, request.signal)
141170
case 'download_document':
142-
return await handleDownloadDocument(apiBase, headers, params, authResult.userId)
171+
return await handleDownloadDocument(
172+
apiBase,
173+
headers,
174+
params,
175+
authResult.userId,
176+
request.signal
177+
)
143178
case 'list_templates':
144-
return await handleListTemplates(apiBase, headers, params)
179+
return await handleListTemplates(apiBase, headers, params, request.signal)
145180
case 'list_recipients':
146-
return await handleListRecipients(apiBase, headers, params)
181+
return await handleListRecipients(apiBase, headers, params, request.signal)
147182
default:
148183
return NextResponse.json(
149184
{ success: false, error: `Unknown operation: ${operation}` },
@@ -164,7 +199,8 @@ async function handleSendEnvelope(
164199
apiBase: string,
165200
headers: Record<string, string>,
166201
params: Record<string, unknown>,
167-
userId: string
202+
userId: string,
203+
signal?: AbortSignal
168204
) {
169205
const { signerEmail, signerName, emailSubject, emailBody, ccEmail, ccName, file, status } = params
170206

@@ -276,11 +312,15 @@ async function handleSendEnvelope(
276312
)
277313
}
278314

279-
const response = await fetch(`${apiBase}/envelopes`, {
280-
method: 'POST',
281-
headers,
282-
body: JSON.stringify(envelopeBody),
283-
})
315+
const response = await fetchDocusign(
316+
`${apiBase}/envelopes`,
317+
{
318+
method: 'POST',
319+
headers,
320+
body: JSON.stringify(envelopeBody),
321+
},
322+
signal
323+
)
284324

285325
const data = await readDocusignJson(response, 'DocuSign send envelope response')
286326
if (!response.ok) {
@@ -297,7 +337,8 @@ async function handleSendEnvelope(
297337
async function handleCreateFromTemplate(
298338
apiBase: string,
299339
headers: Record<string, string>,
300-
params: Record<string, unknown>
340+
params: Record<string, unknown>,
341+
signal?: AbortSignal
301342
) {
302343
const { templateId, emailSubject, emailBody, templateRoles, status } = params
303344

@@ -330,11 +371,15 @@ async function handleCreateFromTemplate(
330371
if (emailSubject) envelopeBody.emailSubject = emailSubject
331372
if (emailBody) envelopeBody.emailBlurb = emailBody
332373

333-
const response = await fetch(`${apiBase}/envelopes`, {
334-
method: 'POST',
335-
headers,
336-
body: JSON.stringify(envelopeBody),
337-
})
374+
const response = await fetchDocusign(
375+
`${apiBase}/envelopes`,
376+
{
377+
method: 'POST',
378+
headers,
379+
body: JSON.stringify(envelopeBody),
380+
},
381+
signal
382+
)
338383

339384
const data = await readDocusignJson(response, 'DocuSign create from template response')
340385
if (!response.ok) {
@@ -354,16 +399,18 @@ async function handleCreateFromTemplate(
354399
async function handleGetEnvelope(
355400
apiBase: string,
356401
headers: Record<string, string>,
357-
params: Record<string, unknown>
402+
params: Record<string, unknown>,
403+
signal?: AbortSignal
358404
) {
359405
const { envelopeId } = params
360406
if (!envelopeId) {
361407
return NextResponse.json({ success: false, error: 'envelopeId is required' }, { status: 400 })
362408
}
363409

364-
const response = await fetch(
410+
const response = await fetchDocusign(
365411
`${apiBase}/envelopes/${(envelopeId as string).trim()}?include=recipients,documents`,
366-
{ headers }
412+
{ headers },
413+
signal
367414
)
368415
const data = await readDocusignJson(response, 'DocuSign envelope response')
369416

@@ -380,7 +427,8 @@ async function handleGetEnvelope(
380427
async function handleListEnvelopes(
381428
apiBase: string,
382429
headers: Record<string, string>,
383-
params: Record<string, unknown>
430+
params: Record<string, unknown>,
431+
signal?: AbortSignal
384432
) {
385433
const queryParams = new URLSearchParams()
386434

@@ -398,7 +446,7 @@ async function handleListEnvelopes(
398446
if (params.searchText) queryParams.append('search_text', params.searchText as string)
399447
if (params.count) queryParams.append('count', params.count as string)
400448

401-
const response = await fetch(`${apiBase}/envelopes?${queryParams}`, { headers })
449+
const response = await fetchDocusign(`${apiBase}/envelopes?${queryParams}`, { headers }, signal)
402450
const data = await readDocusignJson(response, 'DocuSign envelope list response')
403451

404452
if (!response.ok) {
@@ -414,7 +462,8 @@ async function handleListEnvelopes(
414462
async function handleVoidEnvelope(
415463
apiBase: string,
416464
headers: Record<string, string>,
417-
params: Record<string, unknown>
465+
params: Record<string, unknown>,
466+
signal?: AbortSignal
418467
) {
419468
const { envelopeId, voidedReason } = params
420469
if (!envelopeId) {
@@ -424,11 +473,15 @@ async function handleVoidEnvelope(
424473
return NextResponse.json({ success: false, error: 'voidedReason is required' }, { status: 400 })
425474
}
426475

427-
const response = await fetch(`${apiBase}/envelopes/${(envelopeId as string).trim()}`, {
428-
method: 'PUT',
429-
headers,
430-
body: JSON.stringify({ status: 'voided', voidedReason }),
431-
})
476+
const response = await fetchDocusign(
477+
`${apiBase}/envelopes/${(envelopeId as string).trim()}`,
478+
{
479+
method: 'PUT',
480+
headers,
481+
body: JSON.stringify({ status: 'voided', voidedReason }),
482+
},
483+
signal
484+
)
432485

433486
const data = await readDocusignJson(response, 'DocuSign void envelope response')
434487
if (!response.ok) {
@@ -445,7 +498,8 @@ async function handleDownloadDocument(
445498
apiBase: string,
446499
headers: Record<string, string>,
447500
params: Record<string, unknown>,
448-
userId: string
501+
userId: string,
502+
signal?: AbortSignal
449503
) {
450504
const { envelopeId, documentId } = params
451505
if (!envelopeId) {
@@ -454,11 +508,12 @@ async function handleDownloadDocument(
454508

455509
const docId = (documentId as string) || 'combined'
456510

457-
const response = await fetch(
511+
const response = await fetchDocusign(
458512
`${apiBase}/envelopes/${(envelopeId as string).trim()}/documents/${docId}`,
459513
{
460514
headers: { Authorization: headers.Authorization },
461-
}
515+
},
516+
signal
462517
)
463518

464519
if (!response.ok) {
@@ -494,6 +549,10 @@ async function handleDownloadDocument(
494549
const workspaceId = typeof params.workspaceId === 'string' ? params.workspaceId : undefined
495550
const workflowId = typeof params.workflowId === 'string' ? params.workflowId : undefined
496551
const executionId = typeof params.executionId === 'string' ? params.executionId : undefined
552+
const legacyInlineContent =
553+
buffer.length <= MAX_LEGACY_INLINE_DOCUMENT_BYTES
554+
? { base64Content: buffer.toString('base64') }
555+
: {}
497556

498557
if (workspaceId && workflowId && executionId) {
499558
const file = await uploadExecutionFile(
@@ -506,6 +565,7 @@ async function handleDownloadDocument(
506565
file,
507566
mimeType: contentType,
508567
fileName,
568+
...legacyInlineContent,
509569
})
510570
}
511571

@@ -516,13 +576,14 @@ async function handleDownloadDocument(
516576
userId,
517577
})
518578

519-
return NextResponse.json({ file, mimeType: contentType, fileName })
579+
return NextResponse.json({ file, mimeType: contentType, fileName, ...legacyInlineContent })
520580
}
521581

522582
async function handleListTemplates(
523583
apiBase: string,
524584
headers: Record<string, string>,
525-
params: Record<string, unknown>
585+
params: Record<string, unknown>,
586+
signal?: AbortSignal
526587
) {
527588
const queryParams = new URLSearchParams()
528589
if (params.searchText) queryParams.append('search_text', params.searchText as string)
@@ -531,7 +592,7 @@ async function handleListTemplates(
531592
const queryString = queryParams.toString()
532593
const url = queryString ? `${apiBase}/templates?${queryString}` : `${apiBase}/templates`
533594

534-
const response = await fetch(url, { headers })
595+
const response = await fetchDocusign(url, { headers }, signal)
535596
const data = await readDocusignJson(response, 'DocuSign template list response')
536597

537598
if (!response.ok) {
@@ -547,16 +608,21 @@ async function handleListTemplates(
547608
async function handleListRecipients(
548609
apiBase: string,
549610
headers: Record<string, string>,
550-
params: Record<string, unknown>
611+
params: Record<string, unknown>,
612+
signal?: AbortSignal
551613
) {
552614
const { envelopeId } = params
553615
if (!envelopeId) {
554616
return NextResponse.json({ success: false, error: 'envelopeId is required' }, { status: 400 })
555617
}
556618

557-
const response = await fetch(`${apiBase}/envelopes/${(envelopeId as string).trim()}/recipients`, {
558-
headers,
559-
})
619+
const response = await fetchDocusign(
620+
`${apiBase}/envelopes/${(envelopeId as string).trim()}/recipients`,
621+
{
622+
headers,
623+
},
624+
signal
625+
)
560626
const data = await readDocusignJson(response, 'DocuSign recipients response')
561627

562628
if (!response.ok) {

0 commit comments

Comments
 (0)