You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

240 lines
6.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import type { Locator, Page } from "playwright";
import type { AxelSelectionBridge } from "@/workers/rpa/axel-selection-bridge";
import type { AddressLookupInput } from "@/modules/address/types";
import { getRpaPlaceFixMode } from "@/lib/rpa/env";
import {
clickMothershipStyledSuggestion,
reopenStyledSuggestionDropdown,
waitForStyledSuggestionRows,
} from "@/workers/rpa/mothership-styled-suggestion-click";
import { commitViaGooglePlaceEvent } from "./google-event-commit";
import { commitViaReactStateInjection } from "./react-state-commit";
import { commitViaBrowserPlaceFetch } from "./browser-place-commit";
import { commitViaCdpDomClick } from "./cdp-pac-commit";
import { commitViaDirectPlaceChangedListener } from "./direct-listener-commit";
import { loadReconVerdict } from "./recon-helpers";
import {
isPlaceDiagEnabled,
logPlaceDiagnostic,
setPlaceDiagMark,
writePlaceDiagnosticArtifact,
} from "./place-diagnostic-probe";
const STYLED_CLICK_RETRIES = 3;
async function diagPhase(
page: Page,
phase: string,
runId: string,
): Promise<void> {
if (!isPlaceDiagEnabled()) {
return;
}
const snapshot = await logPlaceDiagnostic(page, phase);
if (snapshot.ok) {
writePlaceDiagnosticArtifact(runId, phase, snapshot);
}
}
async function tryStyledClickCommit(
page: Page,
bridge: AxelSelectionBridge,
placeId: string,
description: string,
side: "pickup" | "delivery",
input: Locator,
query: AddressLookupInput,
): Promise<{ ok: boolean; method: string }> {
for (let attempt = 0; attempt < STYLED_CLICK_RETRIES; attempt += 1) {
if (attempt > 0) {
await reopenStyledSuggestionDropdown(page, input, query, description);
await waitForStyledSuggestionRows(page, [], query, 8_000);
}
const styled = await clickMothershipStyledSuggestion(
page,
description,
query,
{
input,
side,
bridge,
placeId,
skipReopen: attempt > 0,
maxStrategyAttempts: 3,
},
);
if (!styled.clicked) {
console.log(
`[rpa] fix-place: styled-click attempt=${attempt + 1} miss reason=${styled.reason ?? "unknown"}`,
);
continue;
}
const committed = await bridge.waitForPlaceCommit(placeId, 8_000);
const httpOk = bridge.hasSuccessfulPlaceNetworkRequest(placeId);
if (committed && httpOk) {
console.log(
`[rpa] fix-place: mothership-styled-click 成功 method=${styled.method ?? "styled"}`,
);
return { ok: true, method: styled.method ?? "mothership-styled-click" };
}
console.log(
`[rpa] fix-place: styled-click attempt=${attempt + 1} 无 place 200 committed=${committed} httpOk=${httpOk}`,
);
}
return { ok: false, method: "mothership-styled-click-failed" };
}
export async function commitPlaceWithStrategy(
page: Page,
bridge: AxelSelectionBridge,
placeId: string,
description: string,
side: "pickup" | "delivery",
pickupInfo?: {
placeId: string;
description: string;
location: Record<string, unknown>;
},
options?: {
input?: Locator;
query?: AddressLookupInput;
},
): Promise<{ ok: boolean; method: string }> {
const mode = getRpaPlaceFixMode();
const verdict = loadReconVerdict();
const diagRunId = `${side}-${placeId.slice(0, 12)}`;
const googleAvailable =
verdict.googleMapsLoaded &&
verdict.triggerAvailable &&
verdict.placeChangedTriggerOk;
const fiberAvailable = verdict.reactFiberKey !== "not-found";
if (options?.input && options?.query) {
await setPlaceDiagMark(page, `${diagRunId}-before-styled`);
const styled = await tryStyledClickCommit(
page,
bridge,
placeId,
description,
side,
options.input,
options.query,
);
if (styled.ok) {
await diagPhase(page, `${diagRunId}-after-styled`, diagRunId);
return styled;
}
await diagPhase(page, `${diagRunId}-styled-miss`, diagRunId);
}
if (mode === "legacy") {
return { ok: false, method: "legacy-skip" };
}
if (options?.input && options?.query) {
await setPlaceDiagMark(page, `${diagRunId}-before-cdp`);
const cdpResult = await commitViaCdpDomClick(
page,
bridge,
placeId,
description,
{
candidateIndex: bridge.getCandidateIndex(placeId),
query: options.query,
},
);
if (
cdpResult.ok &&
bridge.hasSuccessfulPlaceNetworkRequest(placeId)
) {
await diagPhase(page, `${diagRunId}-after-cdp`, diagRunId);
return cdpResult;
}
await diagPhase(page, `${diagRunId}-cdp-miss`, diagRunId);
}
await setPlaceDiagMark(page, `${diagRunId}-before-direct-listener`);
const directListener = await commitViaDirectPlaceChangedListener(
page,
bridge,
placeId,
description,
);
if (
directListener.ok &&
bridge.hasSuccessfulPlaceNetworkRequest(placeId)
) {
await diagPhase(page, `${diagRunId}-after-direct-listener`, diagRunId);
return directListener;
}
await diagPhase(page, `${diagRunId}-direct-listener-miss`, diagRunId);
await setPlaceDiagMark(page, `${diagRunId}-before-browser-fetch`);
const browserFetch = await commitViaBrowserPlaceFetch(
page,
bridge,
placeId,
description,
side,
);
if (
browserFetch.ok &&
bridge.hasSuccessfulPlaceNetworkRequest(placeId)
) {
await diagPhase(page, `${diagRunId}-after-browser-fetch`, diagRunId);
return browserFetch;
}
await diagPhase(page, `${diagRunId}-browser-fetch-miss`, diagRunId);
if (mode === "event" && googleAvailable) {
await setPlaceDiagMark(page, `${diagRunId}-before-google-event`);
const result = await commitViaGooglePlaceEvent(
page,
bridge,
placeId,
description,
);
if (
result.ok &&
bridge.hasSuccessfulPlaceNetworkRequest(placeId)
) {
await diagPhase(page, `${diagRunId}-after-google-event`, diagRunId);
console.log("[rpa] fix-place: google-event 成功");
return { ok: true, method: "google-event" };
}
await diagPhase(page, `${diagRunId}-google-event-miss`, diagRunId);
console.log("[rpa] fix-place: google-event 失败,降级到 react-state");
} else if (mode === "event" && !googleAvailable) {
console.log(
"[rpa] fix-place: verdict 无 Google Autocomplete跳过 google-event",
);
}
if (fiberAvailable) {
const stateResult = await commitViaReactStateInjection(
page,
bridge,
placeId,
description,
side,
pickupInfo,
);
if (
stateResult.ok &&
bridge.hasSuccessfulPlaceNetworkRequest(placeId)
) {
console.log("[rpa] fix-place: react-state 成功");
return { ok: true, method: "react-state" };
}
} else {
console.log("[rpa] fix-place: verdict 无 React Fiber跳过 react-state");
}
return { ok: false, method: "all-failed" };
}