Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 66 additions & 23 deletions src/background/filed-returns-download-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export async function triggerAndObserveFiledReturnDownload({
};
await persistArtifactAcquisitionIntent({ ...checkpointTarget, requestId });
let checkpointHasDownloadId = false;
let externallyVisibleActionMayHaveOccurred = false;
let retainCheckpointForRecovery = false;
try {
const delivery = await acquireFiledReturnJsonInMainWorld({
Expand All @@ -168,15 +169,18 @@ export async function triggerAndObserveFiledReturnDownload({
returnType: "GSTR-3B",
tabId,
onStarted: async (downloadId) => {
checkpointHasDownloadId = true;
externallyVisibleActionMayHaveOccurred = true;
await persistArtifactAcquisitionDownloadId({
...checkpointTarget,
downloadId,
requestId,
state: "download-observing",
});
checkpointHasDownloadId = true;
},
onStartCheckpointFailed: async (downloadId) => {
checkpointHasDownloadId = true;
externallyVisibleActionMayHaveOccurred = true;
await persistArtifactAcquisitionUnconfirmedDownload({
...checkpointTarget,
downloadId,
Expand All @@ -187,7 +191,10 @@ export async function triggerAndObserveFiledReturnDownload({
});
retainCheckpointForRecovery =
delivery.ok ||
shouldRetainArtifactAcquisitionCheckpoint(delivery, checkpointHasDownloadId);
shouldRetainArtifactAcquisitionCheckpoint(delivery, {
checkpointHasDownloadId,
externallyVisibleActionMayHaveOccurred,
});
return delivery.ok
? {
ok: true,
Expand Down Expand Up @@ -239,6 +246,7 @@ export async function triggerAndObserveFiledReturnDownload({
};
await persistArtifactAcquisitionIntent({ ...checkpointTarget, requestId });
let checkpointHasDownloadId = false;
const externallyVisibleActionMayHaveOccurred = true;
let retainCheckpointForRecovery = false;
try {
const acquired = await acquireGstr3bPdfAfterPreflight({
Expand All @@ -249,15 +257,16 @@ export async function triggerAndObserveFiledReturnDownload({
returnPeriod,
tabId,
onStarted: async (downloadId) => {
checkpointHasDownloadId = true;
await persistArtifactAcquisitionDownloadId({
...checkpointTarget,
downloadId,
requestId,
state: "download-observing",
});
checkpointHasDownloadId = true;
},
onStartCheckpointFailed: async (downloadId) => {
checkpointHasDownloadId = true;
await persistArtifactAcquisitionUnconfirmedDownload({
...checkpointTarget,
downloadId,
Expand All @@ -268,7 +277,10 @@ export async function triggerAndObserveFiledReturnDownload({
});
retainCheckpointForRecovery =
acquired.ok ||
shouldRetainArtifactAcquisitionCheckpoint(acquired, checkpointHasDownloadId);
shouldRetainArtifactAcquisitionCheckpoint(acquired, {
checkpointHasDownloadId,
externallyVisibleActionMayHaveOccurred,
});
return acquired.ok
? {
ok: true,
Expand Down Expand Up @@ -312,17 +324,29 @@ export async function triggerAndObserveFiledReturnDownload({

function shouldRetainArtifactAcquisitionCheckpoint(
delivery: { ok: false; reason: string },
checkpointHasDownloadId: boolean,
input: {
checkpointHasDownloadId: boolean;
externallyVisibleActionMayHaveOccurred: boolean;
},
): boolean {
if (!input.externallyVisibleActionMayHaveOccurred) return false;
// A portal generation timeout happens after Pack armed the target-bound
// control but before it can prove the browser action quiesced. Keep the
// intent so the next start routes it through recovery review instead of
// repeating the portal action.
if (["checkpoint-failed", "generation-timeout"].includes(delivery.reason)) return true;
if (
[
"checkpoint-failed",
"delivery-unconfirmed",
"generation-timeout",
"main-world-execution-failed",
].includes(delivery.reason)
)
return true;
// The browser already created an exact-ID item for these outcomes. It may
// settle as safe later, but it must not be forgotten and repeated first.
return (
checkpointHasDownloadId &&
input.checkpointHasDownloadId &&
[
"timeout",
"search-unavailable",
Expand Down Expand Up @@ -441,19 +465,24 @@ async function triggerPageGeneratedSinglePeriodArtifact(
await persistArtifactAcquisitionIntent({ ...checkpointTarget, requestId });
}
let checkpointHasDownloadId = false;
let externallyVisibleActionMayHaveOccurred =
artifact.state === "ready" && (artifactType === "PDF" || artifactType === "EXCEL");
let retainCheckpointForRecovery = false;
try {
const callbacks = {
onStarted: async (downloadId: number) => {
checkpointHasDownloadId = true;
externallyVisibleActionMayHaveOccurred = true;
await persistArtifactAcquisitionDownloadId({
...checkpointTarget,
downloadId,
requestId,
state: "download-observing",
});
checkpointHasDownloadId = true;
},
onStartCheckpointFailed: async (downloadId: number) => {
checkpointHasDownloadId = true;
externallyVisibleActionMayHaveOccurred = true;
await persistArtifactAcquisitionUnconfirmedDownload({
...checkpointTarget,
downloadId,
Expand Down Expand Up @@ -520,7 +549,11 @@ async function triggerPageGeneratedSinglePeriodArtifact(
}
retainCheckpointForRecovery =
tracksBrowserDownload &&
(acquired.ok || shouldRetainArtifactAcquisitionCheckpoint(acquired, checkpointHasDownloadId));
(acquired.ok ||
shouldRetainArtifactAcquisitionCheckpoint(acquired, {
checkpointHasDownloadId,
externallyVisibleActionMayHaveOccurred,
}));
return acquired.ok
? {
ok: true,
Expand Down Expand Up @@ -592,13 +625,18 @@ async function deliverValidatedArtifact({
> {
const staging = deps.stageCapturedDownloads;
if (staging) {
const result = await stageOffscreenFiledReturn({
artifactType,
dataUrl: `data:${mimeType};base64,${base64}`,
ledgerId: staging.ledgerId,
returnType,
zipPath: safeFiledReturnZipEntryPath(scope, artifactType),
});
let result;
try {
result = await stageOffscreenFiledReturn({
artifactType,
dataUrl: `data:${mimeType};base64,${base64}`,
ledgerId: staging.ledgerId,
returnType,
zipPath: safeFiledReturnZipEntryPath(scope, artifactType),
});
} catch {
return { ok: false, reason: "delivery-unconfirmed", safeSignals };
}
return result.status === "staged"
? {
ok: true,
Expand All @@ -611,13 +649,18 @@ async function deliverValidatedArtifact({
}
: { ok: false, reason: result.errorCategory ?? "stage-failed", safeSignals };
}
const delivery = await downloadAcquiredArtifact({
base64,
filename,
mimeType,
requestId,
...callbacks,
});
let delivery;
try {
delivery = await downloadAcquiredArtifact({
base64,
filename,
mimeType,
requestId,
...callbacks,
});
} catch {
return { ok: false, reason: "delivery-unconfirmed", safeSignals };
}
return delivery.ok
? {
ok: true,
Expand Down
51 changes: 33 additions & 18 deletions src/background/filed-returns-json-acquisition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type JsonAcquisitionResult =
safeSignals: string[];
}
| { ok: false; reason: string; safeSignals: string[] };
type MainWorldJsonCaptureResult = { ok: true; base64: string } | { ok: false; reason: string };

export async function acquireFiledReturnJsonInMainWorld(input: {
deliver?: (input: { base64: string; mimeType: string }) => Promise<JsonAcquisitionResult>;
Expand All @@ -23,6 +24,7 @@ export async function acquireFiledReturnJsonInMainWorld(input: {
returnType: JsonReturnType;
tabId: number;
}): Promise<JsonAcquisitionResult> {
let captured: MainWorldJsonCaptureResult | undefined;
try {
const [injection] = await browser.scripting.executeScript({
args: [
Expand All @@ -36,17 +38,30 @@ export async function acquireFiledReturnJsonInMainWorld(input: {
target: { tabId: input.tabId },
world: "MAIN",
});
const captured = injection?.result;
if (!captured?.ok) {
return { ok: false, reason: captured?.reason ?? "endpoint-unavailable", safeSignals: [] };
}
const bytes = Uint8Array.from(atob(captured.base64), (value) => value.charCodeAt(0));
const validation = validateArtifactBytes(bytes, "JSON", input.returnPeriod, input.returnType);
if (!validation.ok) return { ok: false, reason: validation.reason, safeSignals: [] };
if (input.deliver) {
return input.deliver({ base64: captured.base64, mimeType: validation.mimeType });
captured = injection?.result as MainWorldJsonCaptureResult | undefined;
} catch {
return { ok: false, reason: "main-world-execution-failed", safeSignals: [] };
}
if (!captured?.ok) {
return {
ok: false,
reason: captured?.reason ?? "main-world-execution-failed",
safeSignals: [],
};
}
const bytes = Uint8Array.from(atob(captured.base64), (value) => value.charCodeAt(0));
const validation = validateArtifactBytes(bytes, "JSON", input.returnPeriod, input.returnType);
if (!validation.ok) return { ok: false, reason: validation.reason, safeSignals: [] };
if (input.deliver) {
try {
return await input.deliver({ base64: captured.base64, mimeType: validation.mimeType });
} catch {
return { ok: false, reason: "delivery-unconfirmed", safeSignals: [] };
}
const delivery = await downloadAcquiredArtifact({
}
let delivery;
try {
delivery = await downloadAcquiredArtifact({
requestId: input.requestId,
base64: captured.base64,
filename: input.filename,
Expand All @@ -56,16 +71,16 @@ export async function acquireFiledReturnJsonInMainWorld(input: {
? { onStartCheckpointFailed: input.onStartCheckpointFailed }
: {}),
});
return delivery.ok
? {
ok: true,
safeSignals: [...delivery.safeSignals, "extension-download-complete"],
...(delivery.safeMessage ? { safeMessage: delivery.safeMessage } : {}),
}
: { ok: false, reason: delivery.reason, safeSignals: delivery.safeSignals };
} catch {
return { ok: false, reason: "endpoint-unavailable", safeSignals: [] };
return { ok: false, reason: "delivery-unconfirmed", safeSignals: [] };
}
return delivery.ok
? {
ok: true,
safeSignals: [...delivery.safeSignals, "extension-download-complete"],
...(delivery.safeMessage ? { safeMessage: delivery.safeMessage } : {}),
}
: { ok: false, reason: delivery.reason, safeSignals: delivery.safeSignals };
}

/**
Expand Down
16 changes: 16 additions & 0 deletions src/background/filed-returns-selected-artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,22 @@ export async function triggerSelectedArtifacts({
}
return response;
}
if (
singlePeriodBundleLedger &&
response.flowStep.safeSignals.includes("artifact-delivery-unconfirmed")
) {
const reviewLedger = await persistSinglePeriodBundleArtifactReview(
singlePeriodBundleLedger,
artifactType,
response.flowStep,
deps.now?.() ?? new Date(),
);
return persistAmbiguousSinglePeriodBundleResponse(
reviewLedger ?? singlePeriodBundleLedger,
deps,
response.flowStep,
);
}
if (response.flowStep.state !== "downloaded") {
if (singlePeriodBundleLedger) {
const unavailableLedger = await persistSinglePeriodBundleArtifactUnavailable(
Expand Down
46 changes: 28 additions & 18 deletions src/background/gstr2b-artifact-acquisition.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { browser } from "wxt/browser";
import { validateArtifactBytes } from "../connectors/gst/artifact-validation";
import { capturePortalPdfBlob } from "../connectors/gst/portal-blob-shim";
import {
capturePortalPdfBlob,
MAX_PORTAL_BLOB_BYTES,
type PortalBlobShimResult,
} from "../connectors/gst/portal-blob-shim";
import { installPortalBlobDownloadSafetyNet } from "./artifact-download";

const MIME_TYPES = {
Expand All @@ -22,27 +26,33 @@ export async function acquirePageGeneratedArtifact(input: {
> {
const safetyNet = installPortalBlobDownloadSafetyNet(input.tabId);
try {
const [injection] = await browser.scripting.executeScript({
args: [
{
controlSelector: `[data-pack-artifact-request="${input.requestId}"]`,
expectedMime: MIME_TYPES[input.artifactType],
expectedTarget: {
financialYear: input.financialYear,
period: input.period,
returnType: input.returnType,
let captured: PortalBlobShimResult | undefined;
try {
const [injection] = await browser.scripting.executeScript({
args: [
{
controlSelector: `[data-pack-artifact-request="${input.requestId}"]`,
expectedMime: MIME_TYPES[input.artifactType],
maxPortalBlobBytes: MAX_PORTAL_BLOB_BYTES,
expectedTarget: {
financialYear: input.financialYear,
period: input.period,
returnType: input.returnType,
},
},
},
],
func: capturePortalPdfBlob,
target: { tabId: input.tabId },
world: "MAIN",
});
const captured = injection?.result;
],
func: capturePortalPdfBlob,
target: { tabId: input.tabId },
world: "MAIN",
});
captured = injection?.result as PortalBlobShimResult | undefined;
} catch {
return { ok: false, reason: "main-world-execution-failed", safeSignals: [] };
}
if (!captured?.ok)
return {
ok: false,
reason: captured?.reason ?? "generation-timeout",
reason: captured?.reason ?? "main-world-execution-failed",
safeSignals: captured?.safeSignals ?? [],
};
await safetyNet.bind(captured.blobUrl);
Expand Down
Loading