155 lines
4.8 KiB
TypeScript
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)
|
|
}
|