Files
xh-medical-sign-web/clinical-web/src/api/workbench/deliveries.ts
T

155 lines
4.8 KiB
TypeScript

import { unwrapApiResponse, unwrapNullableApiResponse } from '@/utils/api-response'
import { request } from '@/utils/request'
import type {
ApiResponseOf,
PadBindingRequest,
PadSessionUploadInput,
SendSmsDeliveryRequest,
SignDeliveryResponseDto,
SignatureUploadResponse,
SigningTokenUploadInput,
TokenConsumeRequest,
TokenConsumeResponse,
} from './types'
function createIdempotencyKey() {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return `clinical-web-${crypto.randomUUID()}`
}
return `clinical-web-${Date.now()}-${Math.random().toString(36).slice(2)}`
}
function idempotencyHeaders(value?: string) {
return { 'Idempotency-Key': value || createIdempotencyKey() }
}
export function sendSigningSms(
taskId: string,
payload: SendSmsDeliveryRequest,
idempotencyKey?: string,
): Promise<SignDeliveryResponseDto | null> {
return request
.post<ApiResponseOf<SignDeliveryResponseDto | null>>(
`/v1/sign-deliveries/${encodeURIComponent(taskId)}/sms/send`,
payload,
{ headers: idempotencyHeaders(idempotencyKey) },
)
.then(unwrapNullableApiResponse)
}
export function resendSigningSms(
taskId: string,
idempotencyKey?: string,
): Promise<SignDeliveryResponseDto | null> {
return request
.post<ApiResponseOf<SignDeliveryResponseDto | null>>(
`/v1/sign-deliveries/${encodeURIComponent(taskId)}/sms/resend`,
undefined,
{ headers: idempotencyHeaders(idempotencyKey) },
)
.then(unwrapNullableApiResponse)
}
export function createPadSigningSession(
taskId: string,
payload: PadBindingRequest = {},
idempotencyKey?: string,
): Promise<SignDeliveryResponseDto> {
return request
.post<ApiResponseOf<SignDeliveryResponseDto>>(
`/v1/sign-deliveries/${encodeURIComponent(taskId)}/pad/sessions`,
payload,
{ headers: idempotencyHeaders(idempotencyKey) },
)
.then(unwrapApiResponse)
}
export function consumeSigningToken(
payload: TokenConsumeRequest,
idempotencyKey?: string,
): Promise<TokenConsumeResponse> {
return request
.post<ApiResponseOf<TokenConsumeResponse>>('/v1/sign-deliveries/token/consume', payload, {
headers: idempotencyHeaders(idempotencyKey),
})
.then(unwrapApiResponse)
}
function createSignatureFormData(file: File | Blob, metadata: string) {
const formData = new FormData()
const fileName =
typeof File !== 'undefined' && file instanceof File && file.name ? file.name : 'signature.png'
formData.append('file', file, fileName)
formData.append('metadata', metadata)
return formData
}
export function uploadSigningSignature(
payload: SigningTokenUploadInput,
): Promise<SignatureUploadResponse> {
const formData = createSignatureFormData(payload.file, payload.metadata)
return request
.post<ApiResponseOf<SignatureUploadResponse>>(
`/v1/sign-deliveries/${encodeURIComponent(payload.taskId)}/signature`,
formData,
{
params: {
deliveryId: payload.deliveryId,
uploadToken: payload.uploadToken,
},
headers: idempotencyHeaders(payload.idempotencyKey),
},
)
.then(unwrapApiResponse)
}
/**
* PAD 专用兼容路径。服务端要求同时提交 PAD 元数据和真实 PNG,旧的 JSON-only 提交会返回 409。
* 具体的 challenge、文件摘要和 storageKey 由设备适配层生成,页面不应伪造这些值。
*/
export function uploadPadSigningSignature(
payload: PadSessionUploadInput,
): Promise<SignatureUploadResponse> {
if (!payload.pad) {
return Promise.reject(new Error('PAD 上传缺少会话元数据'))
}
const formData = createSignatureFormData(payload.file, payload.metadata)
formData.append('challenge', payload.pad.challenge)
formData.append('contentType', payload.pad.contentType)
formData.append('sizeBytes', String(payload.pad.sizeBytes ?? payload.file.size))
formData.append('sha256', payload.pad.sha256)
formData.append('storageKey', payload.pad.storageKey)
return request
.post<ApiResponseOf<SignatureUploadResponse>>(
`/v1/sign-deliveries/${encodeURIComponent(payload.taskId)}/pad/sessions/${encodeURIComponent(payload.deliveryId)}/upload`,
formData,
{
params: { uploadToken: payload.uploadToken },
headers: idempotencyHeaders(payload.idempotencyKey),
},
)
.then(unwrapApiResponse)
}
export function uploadSigningSignatureByProof(
payload: SigningTokenUploadInput,
): Promise<SignatureUploadResponse> {
const formData = createSignatureFormData(payload.file, payload.metadata)
return request
.post<ApiResponseOf<SignatureUploadResponse>>('/v1/sign-deliveries/token/signature', formData, {
params: {
taskId: payload.taskId,
deliveryId: payload.deliveryId,
uploadToken: payload.uploadToken,
},
headers: idempotencyHeaders(payload.idempotencyKey),
})
.then(unwrapApiResponse)
}