Skip to content

Commit 3227e61

Browse files
refactor(sign): extract submit orchestration service
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent 678f2e3 commit 3227e61

1 file changed

Lines changed: 297 additions & 0 deletions

File tree

src/services/signSubmit.ts

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import type {
7+
FileUuidReferenceRecord,
8+
SignActionResponseRecord,
9+
SignerDetailRecord,
10+
SigningJobRecord,
11+
UserElementRecord,
12+
VisibleElementRecord,
13+
} from '../types/index'
14+
15+
export type SignResultData = Omit<Partial<SignActionResponseRecord>, 'file' | 'job'> & {
16+
file?: Partial<FileUuidReferenceRecord>
17+
job?: Partial<SigningJobRecord> & {
18+
file?: Partial<FileUuidReferenceRecord>
19+
}
20+
} & Record<string, unknown>
21+
22+
export type SignResult = {
23+
status: 'signingInProgress' | 'signed' | 'unknown'
24+
data: SignResultData
25+
}
26+
27+
export type SubmitSignaturePayload = {
28+
method?: string
29+
token?: string
30+
elements?: Array<{
31+
documentElementId: number
32+
profileNodeId?: number
33+
}>
34+
}
35+
36+
export type SignatureMethodConfig = {
37+
method?: string
38+
modalCode?: string
39+
token?: string
40+
}
41+
42+
export type VisibleSignatureElement = Partial<Pick<VisibleElementRecord, 'elementId' | 'signRequestId' | 'type'>>
43+
44+
export type SignatureProfileMap = Record<string, {
45+
file?: Partial<Pick<UserElementRecord['file'], 'nodeId' | 'url'>>
46+
} | undefined>
47+
48+
export type EnvelopeSigner = Omit<Partial<Pick<SignerDetailRecord, 'me' | 'signRequestId' | 'sign_request_uuid'>>, 'sign_request_uuid'> & {
49+
sign_request_uuid?: string | null
50+
}
51+
52+
export type EnvelopeFileForSubmission = {
53+
signers?: EnvelopeSigner[]
54+
}
55+
56+
export type SignDocumentForSubmission = {
57+
nodeType?: string
58+
signers?: EnvelopeSigner[]
59+
files?: EnvelopeFileForSubmission[]
60+
}
61+
62+
export type SignSubmissionAttempt = {
63+
result: SignResult
64+
signRequestUuid: string
65+
}
66+
67+
export type SignSubmissionOutcome =
68+
| {
69+
type: 'signed'
70+
payload: Record<string, unknown> & {
71+
signRequestUuid: string
72+
}
73+
}
74+
| {
75+
type: 'signing-started'
76+
payload: {
77+
signRequestUuid: string
78+
async: true
79+
}
80+
}
81+
| null
82+
83+
export type EnvelopeSubmitRequest = {
84+
signRequestUuid: string
85+
payload: SubmitSignaturePayload
86+
}
87+
88+
export function createBaseSubmitSignaturePayload(methodConfig: SignatureMethodConfig = {}): SubmitSignaturePayload {
89+
const payload: SubmitSignaturePayload = {
90+
method: methodConfig.method,
91+
}
92+
93+
if (methodConfig.token) {
94+
payload.token = methodConfig.token
95+
}
96+
97+
return payload
98+
}
99+
100+
export function buildSubmitSignaturePayload({
101+
basePayload,
102+
elements,
103+
canCreateSignature,
104+
signatures,
105+
}: {
106+
basePayload: SubmitSignaturePayload
107+
elements: VisibleSignatureElement[]
108+
canCreateSignature: boolean
109+
signatures: SignatureProfileMap
110+
}): SubmitSignaturePayload {
111+
const payload: SubmitSignaturePayload = { ...basePayload }
112+
const mappedElements = mapSubmitSignatureElements(elements, canCreateSignature, signatures)
113+
114+
if (mappedElements.length > 0) {
115+
payload.elements = mappedElements
116+
}
117+
118+
return payload
119+
}
120+
121+
export function getEnvelopeSubmitRequests({
122+
document,
123+
basePayload,
124+
elements,
125+
canCreateSignature,
126+
signatures,
127+
}: {
128+
document: SignDocumentForSubmission | null | undefined
129+
basePayload: SubmitSignaturePayload
130+
elements: VisibleSignatureElement[]
131+
canCreateSignature: boolean
132+
signatures: SignatureProfileMap
133+
}): EnvelopeSubmitRequest[] {
134+
if (document?.nodeType !== 'envelope') {
135+
return []
136+
}
137+
138+
const requests: EnvelopeSubmitRequest[] = []
139+
140+
for (const signer of getEnvelopeOwnSigners(document)) {
141+
if (!isOwnEnvelopeSigner(signer)) {
142+
continue
143+
}
144+
145+
const signerElements = elements.filter((element) => element.signRequestId === signer.signRequestId)
146+
requests.push({
147+
signRequestUuid: signer.sign_request_uuid,
148+
payload: buildSubmitSignaturePayload({
149+
basePayload,
150+
elements: signerElements,
151+
canCreateSignature,
152+
signatures,
153+
}),
154+
})
155+
}
156+
157+
return requests
158+
}
159+
160+
function getEnvelopeOwnSigners(document: SignDocumentForSubmission): Array<EnvelopeSigner & {
161+
me: true
162+
signRequestId: number
163+
sign_request_uuid: string
164+
}> {
165+
const ownSigners: Array<EnvelopeSigner & {
166+
me: true
167+
signRequestId: number
168+
sign_request_uuid: string
169+
}> = []
170+
const seen = new Set<string>()
171+
172+
const addSigner = (signer: EnvelopeSigner) => {
173+
if (!isOwnEnvelopeSigner(signer)) {
174+
return
175+
}
176+
177+
if (seen.has(signer.sign_request_uuid)) {
178+
return
179+
}
180+
181+
seen.add(signer.sign_request_uuid)
182+
ownSigners.push(signer)
183+
}
184+
185+
for (const signer of document.signers ?? []) {
186+
addSigner(signer)
187+
}
188+
189+
for (const file of document.files ?? []) {
190+
for (const signer of file.signers ?? []) {
191+
addSigner(signer)
192+
}
193+
}
194+
195+
return ownSigners
196+
}
197+
198+
export function resolveSignSubmissionOutcome(attempts: SignSubmissionAttempt[]): SignSubmissionOutcome {
199+
let signedAttempt: SignSubmissionAttempt | null = null
200+
let signingInProgressAttempt: SignSubmissionAttempt | null = null
201+
202+
for (const attempt of attempts) {
203+
if (attempt.result.status === 'signed') {
204+
signedAttempt = attempt
205+
continue
206+
}
207+
208+
if (attempt.result.status === 'signingInProgress' && !signingInProgressAttempt) {
209+
signingInProgressAttempt = attempt
210+
}
211+
}
212+
213+
if (signedAttempt) {
214+
return {
215+
type: 'signed',
216+
payload: {
217+
...signedAttempt.result.data,
218+
signRequestUuid: resolveNavigationUuid(signedAttempt.result.data, signedAttempt.signRequestUuid),
219+
},
220+
}
221+
}
222+
223+
if (signingInProgressAttempt) {
224+
return {
225+
type: 'signing-started',
226+
payload: {
227+
signRequestUuid: resolveNavigationUuid(
228+
signingInProgressAttempt.result.data,
229+
signingInProgressAttempt.signRequestUuid,
230+
),
231+
async: true,
232+
},
233+
}
234+
}
235+
236+
return null
237+
}
238+
239+
export function resolveNavigationUuid(
240+
data: SignResultData | null | undefined,
241+
fallbackUuid: string,
242+
): string {
243+
if (typeof data?.file?.uuid === 'string' && data.file.uuid.length > 0) {
244+
return data.file.uuid
245+
}
246+
247+
if (typeof data?.job?.file?.uuid === 'string' && data.job.file.uuid.length > 0) {
248+
return data.job.file.uuid
249+
}
250+
251+
return fallbackUuid
252+
}
253+
254+
function mapSubmitSignatureElements(
255+
elements: VisibleSignatureElement[],
256+
canCreateSignature: boolean,
257+
signatures: SignatureProfileMap,
258+
): NonNullable<SubmitSignaturePayload['elements']> {
259+
const payloadElements: NonNullable<SubmitSignaturePayload['elements']> = []
260+
261+
for (const element of elements) {
262+
if (typeof element.elementId !== 'number') {
263+
continue
264+
}
265+
266+
const payloadElement: NonNullable<SubmitSignaturePayload['elements']>[number] = {
267+
documentElementId: element.elementId,
268+
}
269+
270+
if (canCreateSignature && element.type) {
271+
const profileNodeId = signatures[element.type]?.file?.nodeId
272+
if (typeof profileNodeId === 'number') {
273+
payloadElement.profileNodeId = profileNodeId
274+
}
275+
}
276+
277+
payloadElements.push(payloadElement)
278+
}
279+
280+
return payloadElements
281+
}
282+
283+
function isOwnEnvelopeSigner(signer: EnvelopeSigner): signer is EnvelopeSigner & {
284+
me: true
285+
signRequestId: number
286+
sign_request_uuid: string
287+
} {
288+
if (signer.me !== true) {
289+
return false
290+
}
291+
292+
if (typeof signer.signRequestId !== 'number') {
293+
return false
294+
}
295+
296+
return typeof signer.sign_request_uuid === 'string' && signer.sign_request_uuid.length > 0
297+
}

0 commit comments

Comments
 (0)