mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 01:53:21 +08:00
177 lines
5.1 KiB
JavaScript
177 lines
5.1 KiB
JavaScript
import {
|
|
materializeOutboundArtifact,
|
|
releaseOutboundArtifact,
|
|
} from './artifact.mjs';
|
|
import {
|
|
createArtifactFailureReceipt,
|
|
createDeliveryReceipt,
|
|
mergeDeliveryReceipts,
|
|
providerMessageIdsFor,
|
|
} from './delivery.mjs';
|
|
|
|
function unavailableError() {
|
|
const error = new Error('Native file delivery is unavailable');
|
|
error.code = 'artifact-provider-unavailable';
|
|
return error;
|
|
}
|
|
|
|
function isAbort(error, signal) {
|
|
return signal?.aborted
|
|
|| error?.name === 'AbortError'
|
|
|| error?.code === 'ABORT_ERR';
|
|
}
|
|
|
|
function providerIds(value) {
|
|
if (Array.isArray(value)) {
|
|
return [...new Set(value
|
|
.filter((candidate) => (
|
|
(typeof candidate === 'string' && candidate.trim())
|
|
|| Number.isSafeInteger(candidate)
|
|
))
|
|
.map(String))];
|
|
}
|
|
return providerMessageIdsFor(value);
|
|
}
|
|
|
|
async function sendMaterializedArtifact(file, {
|
|
sendFile,
|
|
sendImage,
|
|
signal,
|
|
}) {
|
|
if (file.mediaType?.startsWith('image/') && typeof sendImage === 'function') {
|
|
try {
|
|
return {
|
|
presentation: 'image',
|
|
result: await sendImage(file),
|
|
};
|
|
} catch (error) {
|
|
if (isAbort(error, signal) || error?.code === 'artifact-delivery-uncertain') {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
signal?.throwIfAborted();
|
|
if (typeof sendFile !== 'function') throw unavailableError();
|
|
return {
|
|
presentation: 'file',
|
|
result: await sendFile(file),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Deliver registered artifacts with one shared image-first policy while keeping
|
|
* provider protocol details inside the channel-supplied send closures.
|
|
*/
|
|
export async function deliverOutboundArtifacts({
|
|
artifacts = [],
|
|
baseReceipt = null,
|
|
deliveryId,
|
|
aggregatePresentation,
|
|
alwaysMerge = false,
|
|
channelKey,
|
|
signal,
|
|
sendFile,
|
|
sendImage,
|
|
sendFailureNotice,
|
|
onFailure,
|
|
logger,
|
|
}) {
|
|
const receipts = baseReceipt ? [baseReceipt] : [];
|
|
let userVisible = Boolean(baseReceipt) && baseReceipt.deliveryOutcome !== 'failed';
|
|
let failureNoticeVisible = false;
|
|
let artifactsSent = 0;
|
|
let artifactSendErrors = 0;
|
|
|
|
let artifactIndex = 0;
|
|
try {
|
|
while (artifactIndex < artifacts.length) {
|
|
const artifact = artifacts[artifactIndex];
|
|
artifactIndex += 1;
|
|
try {
|
|
signal?.throwIfAborted();
|
|
const file = await materializeOutboundArtifact(artifact, { signal });
|
|
signal?.throwIfAborted();
|
|
const sent = await sendMaterializedArtifact(file, {
|
|
sendFile,
|
|
sendImage,
|
|
signal,
|
|
});
|
|
signal?.throwIfAborted();
|
|
receipts.push(createDeliveryReceipt({
|
|
deliveryId: file.deliveryKey,
|
|
presentation: `${channelKey}-${sent.presentation}`,
|
|
providerMessageIds: providerIds(sent.result),
|
|
artifacts: [{ artifactId: file.artifactId, outcome: 'sent' }],
|
|
}));
|
|
artifactsSent += 1;
|
|
userVisible = true;
|
|
} catch (error) {
|
|
if (isAbort(error, signal)) throw error;
|
|
artifactSendErrors += 1;
|
|
const failure = typeof onFailure === 'function'
|
|
? await onFailure(artifact, error)
|
|
: null;
|
|
const reference = typeof failure?.referenceId === 'string'
|
|
? ` [${failure.referenceId}]`
|
|
: '';
|
|
logger?.warn?.(
|
|
`[dsh-im:${channelKey}] result artifact delivery failed${reference} (${error?.code ?? 'unknown'})`,
|
|
);
|
|
let messageIds = [];
|
|
if (typeof sendFailureNotice === 'function') {
|
|
try {
|
|
signal?.throwIfAborted();
|
|
const notice = await sendFailureNotice(artifact, error, failure);
|
|
signal?.throwIfAborted();
|
|
messageIds = providerIds(notice);
|
|
failureNoticeVisible = true;
|
|
} catch (noticeError) {
|
|
if (isAbort(noticeError, signal)) throw noticeError;
|
|
logger?.warn?.(
|
|
`[dsh-im:${channelKey}] unable to send the safe artifact failure notice`,
|
|
);
|
|
}
|
|
}
|
|
const failureReceipt = createArtifactFailureReceipt({
|
|
artifactId: artifact?.artifactId ?? 'unknown',
|
|
deliveryId: artifact?.deliveryKey ?? artifact?.artifactId ?? 'unknown',
|
|
error,
|
|
providerMessageIds: messageIds,
|
|
});
|
|
receipts.push(failureReceipt);
|
|
if (failureNoticeVisible || failureReceipt.artifacts[0]?.outcome === 'unknown') {
|
|
userVisible = true;
|
|
}
|
|
} finally {
|
|
releaseOutboundArtifact(artifact);
|
|
}
|
|
}
|
|
} finally {
|
|
while (artifactIndex < artifacts.length) {
|
|
releaseOutboundArtifact(artifacts[artifactIndex]);
|
|
artifactIndex += 1;
|
|
}
|
|
}
|
|
|
|
let receipt = null;
|
|
if (receipts.length === 1 && !alwaysMerge) {
|
|
[receipt] = receipts;
|
|
} else if (receipts.length > 0) {
|
|
receipt = mergeDeliveryReceipts({
|
|
deliveryId: deliveryId
|
|
?? baseReceipt?.deliveryId
|
|
?? artifacts[0]?.deliveryKey,
|
|
presentation: aggregatePresentation
|
|
?? `${channelKey}-${baseReceipt ? 'text-and-files' : 'files'}`,
|
|
receipts,
|
|
});
|
|
}
|
|
|
|
return {
|
|
receipt,
|
|
userVisible,
|
|
failureNoticeVisible,
|
|
artifactsSent,
|
|
artifactSendErrors,
|
|
};
|
|
}
|