|
1 | 1 | import { truncate } from '@sim/utils/string' |
2 | 2 | import { readResponseTextWithLimit } from '@/lib/core/utils/stream-limits' |
| 3 | +import { normalizeQuickBooksAccessToken } from '@/lib/oauth/quickbooks-token' |
3 | 4 | import type { |
4 | 5 | QuickBooksApiEnvelope, |
5 | 6 | QuickBooksAttachmentEntityName, |
@@ -80,20 +81,8 @@ const QUICKBOOKS_RESOURCE_NAMES: Record<QuickBooksEntityName, string> = { |
80 | 81 | } |
81 | 82 |
|
82 | 83 | export function buildQuickBooksHeaders(accessToken: string): Record<string, string> { |
83 | | - if (/[\r\n]/.test(accessToken)) { |
84 | | - throw new Error('QuickBooks access token contains invalid characters') |
85 | | - } |
86 | | - if (accessToken.length > 4096) { |
87 | | - throw new Error('QuickBooks access token must be 4096 characters or less') |
88 | | - } |
89 | | - |
90 | | - const normalizedAccessToken = accessToken.trim() |
91 | | - if (!normalizedAccessToken) { |
92 | | - throw new Error('QuickBooks access token is required') |
93 | | - } |
94 | | - |
95 | 84 | return { |
96 | | - Authorization: `Bearer ${normalizedAccessToken}`, |
| 85 | + Authorization: `Bearer ${normalizeQuickBooksAccessToken(accessToken)}`, |
97 | 86 | Accept: 'application/json', |
98 | 87 | 'Content-Type': 'application/json', |
99 | 88 | } |
@@ -500,7 +489,7 @@ export async function parseQuickBooksJson(response: Response): Promise<QuickBook |
500 | 489 | export function assertQuickBooksAttachmentUploadResponse( |
501 | 490 | data: QuickBooksApiEnvelope |
502 | 491 | ): QuickBooksApiEnvelope { |
503 | | - const uploaded = data.AttachableResponse?.some((item) => isQuickBooksRecord(item.Attachable)) |
| 492 | + const uploaded = data.AttachableResponse?.some((item) => hasQuickBooksRecordId(item.Attachable)) |
504 | 493 | if (!uploaded) { |
505 | 494 | throw new Error('QuickBooks attachment upload returned no attachment') |
506 | 495 | } |
@@ -555,7 +544,8 @@ export function extractQuickBooksRecord( |
555 | 544 | entity: string |
556 | 545 | ): QuickBooksRecord { |
557 | 546 | const record = data[entity] |
558 | | - if (!isQuickBooksRecord(record)) { |
| 547 | + const requiresId = entity !== 'Preferences' && entity !== 'ExchangeRate' |
| 548 | + if (!isNonEmptyQuickBooksRecord(record) || (requiresId && !hasQuickBooksRecordId(record))) { |
559 | 549 | throw new Error(`QuickBooks API response did not include ${entity}`) |
560 | 550 | } |
561 | 551 | return record |
@@ -763,6 +753,14 @@ function isQuickBooksRecord(value: unknown): value is QuickBooksRecord { |
763 | 753 | return value != null && typeof value === 'object' && !Array.isArray(value) |
764 | 754 | } |
765 | 755 |
|
| 756 | +function isNonEmptyQuickBooksRecord(value: unknown): value is QuickBooksRecord { |
| 757 | + return isQuickBooksRecord(value) && Object.keys(value).length > 0 |
| 758 | +} |
| 759 | + |
| 760 | +function hasQuickBooksRecordId(value: unknown): value is QuickBooksRecord { |
| 761 | + return isQuickBooksRecord(value) && typeof value.Id === 'string' && value.Id.trim().length > 0 |
| 762 | +} |
| 763 | + |
766 | 764 | function extractQuickBooksError(data: QuickBooksApiEnvelope): string | null { |
767 | 765 | const errors = [ |
768 | 766 | ...extractFaultErrors(data.Fault), |
|
0 commit comments