diff --git a/.gitignore b/.gitignore index d554fde..3b4147d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .netlify/ +.wrangler/ node_modules/ __pycache__/ *.zip diff --git a/app.js b/app.js index 85b2e4a..aa1c6cf 100644 --- a/app.js +++ b/app.js @@ -87,6 +87,17 @@ function getPhoneCookie() { return match ? decodeURIComponent(match[1]) : ""; } +function setLastToJerusalemStation(stationId) { + const expires = new Date(); + expires.setFullYear(expires.getFullYear() + 1); + document.cookie = `lastToJerusalemStation=${encodeURIComponent(stationId)};expires=${expires.toUTCString()};path=/`; +} + +function getLastToJerusalemStation() { + const match = document.cookie.match(/(?:^|; )lastToJerusalemStation=([^;]*)/); + return match ? decodeURIComponent(match[1]) : ""; +} + // ── Date helpers ───────────────────────────────────────────────────────────── function setDefaultDate() { @@ -141,9 +152,15 @@ function renderStationOptions() { ), ].join(""); - if (stations.some((station) => String(station.stationId) === previousValue)) { + const remembered = + state.direction === "to-jerusalem" ? getLastToJerusalemStation() : ""; + const inList = (id) => stations.some((station) => String(station.stationId) === id); + + if (remembered && inList(remembered)) { + elements.otherStation.value = remembered; + } else if (inList(previousValue)) { elements.otherStation.value = previousValue; - } else if (stations.some((station) => String(station.stationId) === DEFAULT_OTHER_STATION)) { + } else if (inList(DEFAULT_OTHER_STATION)) { elements.otherStation.value = DEFAULT_OTHER_STATION; } else if (stations[0]) { elements.otherStation.value = String(stations[0].stationId); @@ -233,22 +250,32 @@ async function apiPost(path, body) { } async function sendOtp(phone) { - return apiPost("SendOtp", { userContact: phone, type: "phone" }); + return apiPost("Otp/Send", { + userContact: phone, + type: "phone", + languageId: "Hebrew", + }); } async function verifyOtp(phone, otp) { - return apiPost("VerifyOtp", { userContact: phone, type: "phone", otp }); + return apiPost("Otp/Verify", { + userContact: phone, + type: "phone", + otp, + languageId: "Hebrew", + }); } async function orderSeat(params) { - return apiPost("OrderSeatForTrip", { + return apiPost("TripReservation/OrderSeatForTrip", { fromStation: params.fromStation, toStation: params.toStation, departureDate: params.date, numberSeats: 1, - sourceChannel: "web", + systemTypeId: "2", trainNumber: Number(params.trainNumber), - type: "", + type: "phone", + languageId: "Hebrew", }); } @@ -305,10 +332,11 @@ async function handleSubmit(event) { // Try ordering with existing authToken first try { const data = await orderSeat(state.tripParams); - if (data.statusCode === 200 && data.result?.result) { + const confirmationCode = data.result?.data?.confirmationCode; + if (data.statusCode === 200 && data.result?.success && confirmationCode) { submitBtn.disabled = false; elements.statusText.textContent = ""; - showResult(data.result.result); + showResult(confirmationCode); return; } throw new Error(JSON.stringify(data.errorMessages || data)); @@ -384,13 +412,13 @@ async function handleOtpConfirm() { try { const data = await orderSeat(state.tripParams); + const confirmationCode = data.result?.data?.confirmationCode; - if (data.statusCode !== 200 || !data.result?.result) { + if (data.statusCode !== 200 || !data.result?.success || !confirmationCode) { throw new Error(JSON.stringify(data.errorMessages || data)); } - const resultId = data.result.result; - showResult(resultId); + showResult(confirmationCode); } catch (error) { console.error(error); @@ -464,7 +492,12 @@ async function loadData() { function registerEvents() { elements.directionGroup.addEventListener("click", handleDirectionClick); - elements.otherStation.addEventListener("change", updateStatus); + elements.otherStation.addEventListener("change", () => { + if (state.direction === "to-jerusalem" && elements.otherStation.value) { + setLastToJerusalemStation(elements.otherStation.value); + } + updateStatus(); + }); elements.tripDate.addEventListener("change", updateStatus); elements.tripTime.addEventListener("change", syncTrainNumberToTime); elements.voucherForm.addEventListener("submit", handleSubmit); diff --git a/cloudflare-worker/worker.js b/cloudflare-worker/worker.js index 000c2ff..6e9e662 100644 --- a/cloudflare-worker/worker.js +++ b/cloudflare-worker/worker.js @@ -1,7 +1,7 @@ import helpers from "./worker-helpers.cjs"; const { shouldServeStatusPage, buildStatusPayload } = helpers; -const UPSTREAM = "https://rail-api.rail.co.il/common/api/v1/TripReservation"; +const UPSTREAM = "https://rail-api.rail.co.il/common/api/v1"; const ALLOWED_ORIGIN = "https://train-ticket-idshklein.netlify.app"; function buildUpstreamUrl(pathname = "") { @@ -40,6 +40,13 @@ function corsHeaders(origin) { }; } +// Upstream sets cookies with Domain=rail.co.il. Browsers reject those for +// the proxy host. Strip the Domain attribute so the cookie defaults to the +// proxy origin, which is what the FE needs to read it back via credentials. +function rewriteSetCookie(cookie) { + return cookie.replace(/;\s*Domain=[^;]+/i, ""); +} + export default { async fetch(request) { const origin = request.headers.get("Origin") || ""; @@ -78,7 +85,7 @@ export default { const setCookies = response.headers.getAll?.("set-cookie") ?? []; for (const cookie of setCookies) { - responseHeaders.append("Set-Cookie", cookie); + responseHeaders.append("Set-Cookie", rewriteSetCookie(cookie)); } return new Response(responseBody, {