Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.netlify/
.wrangler/
node_modules/
__pycache__/
*.zip
Expand Down
59 changes: 46 additions & 13 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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",
});
}

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
11 changes: 9 additions & 2 deletions cloudflare-worker/worker.js
Original file line number Diff line number Diff line change
@@ -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 = "") {
Expand Down Expand Up @@ -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") || "";
Expand Down Expand Up @@ -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, {
Expand Down