6.7 KiB
TimeToLeave
TimeToLeave is a departure planner for calendar-driven travel. It imports events with locations, resolves the closest public-transport station, checks live ÖBB HAFAS and Wiener Linien data, and shows when to leave by train or bike.
What It Does
- Imports events from ICS URLs, ICS files, Google Calendar on web, or native device calendars on mobile.
- Stores events locally in browser
localStorageor mobileAsyncStorage. - Uses browser or device geolocation, a saved station, or the default Mödling origin.
- Geocodes event destinations and resolves nearby stations through HAFAS
LocMatch. - Searches ÖBB HAFAS journeys, enriches train data with the ÖBB GTFS fallback when available, and shows real-time delays and cancellations.
- Calculates bike and final walking routes through OSRM.
- Shows a live leave-by countdown and optional browser/mobile notifications.
Repository Layout
| Path | Purpose |
|---|---|
apps/web/ |
Next.js 16 App Router web UI plus backend proxy routes for HAFAS, geocoding, routing, calendar parsing, Google Calendar, and Wiener Linien. |
apps/mobile/ |
Expo 54 / React Native 0.81 mobile app with native calendar, location, notification, and local storage integrations. |
packages/core/ |
Shared types, defaults, HAFAS time parsing, journey parsing/scoring, countdown, formatting, and status utilities. |
packages/api-client/ |
Shared client for calling the web app's /api/* backend routes from web hooks and the mobile app. |
docs/ |
Architecture, development, API, user, and codebase reference documentation. |
Prerequisites
- Node.js 20 or newer
- npm 9 or newer
- For mobile native builds: Expo/EAS prerequisites plus Android Studio or Xcode as needed
Setup
npm install
cp .env.example .env
The web app reads environment variables from the workspace process. For deployment, configure the same values in the hosting environment.
Important variables:
| Variable | Purpose |
|---|---|
HAFAS_URL |
ÖBB HAFAS endpoint. Defaults to https://fahrplan.oebb.at/bin/mgate.exe. |
NOMINATIM_URL and NOMINATIM_USER_AGENT |
Geocoding endpoint and required user agent. |
OSRM_URL |
Routing endpoint used for bike and foot profiles. |
WIENER_LINIEN_API_URL |
Wiener Linien live data base URL. |
OEBB_GTFS_URL |
Optional ÖBB GTFS ZIP used to enrich HAFAS train metadata. |
CORS_ALLOWED_ORIGINS |
Comma-separated origins allowed to call /api/*. |
DEPLOYMENT_URL |
Public base URL used by Google OAuth redirects. |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI |
Required for Google Calendar sync on web. |
EXPO_PUBLIC_API_BASE_URL |
Mobile backend URL. Set this for device builds so the app can reach the deployed web backend. |
Development
| Command | Description |
|---|---|
npm run dev |
Start the Next.js web app on http://localhost:3000. |
npm run dev:mobile |
Start the Expo development server. |
npm run build |
Build the web app. |
npm run start |
Start the built web app. |
npm run test |
Run web Vitest and mobile Jest suites. |
npm run lint |
Run ESLint across web, mobile, core, and api-client workspaces. |
npm run typecheck |
Run TypeScript checks across all workspaces. |
Web App
Current user-facing routes:
| Route | Description |
|---|---|
/ |
Departure desk. Shows the next upcoming event, leave-by status, transport mode selector, train journeys, bike route, final walk, and nearby Wiener Linien departures. |
/calendar |
Calendar import and management view with URL, file, and Google Calendar tabs plus batch destination editing. |
The add/edit event UI is a modal component, not a standalone page route.
Backend Proxy Routes
All backend routes live under apps/web/src/app/api/ and are protected by strict CORS plus per-IP rate limiting in apps/web/src/proxy.ts.
| Endpoint | Methods | Purpose |
|---|---|---|
/api/health |
GET |
Returns { ok, ts, version }. |
/api/hafas |
GET, POST |
Convenience journey search or validated HAFAS relay for TripSearch and LocMatch. |
/api/geocode |
GET |
Forward geocoding through Nominatim. |
/api/bike-route |
GET |
OSRM bicycle route between two coordinates. |
/api/walk-route |
GET |
OSRM foot route between two coordinates. |
/api/calendar |
GET |
Fetch and parse an allowed remote ICS URL. |
/api/calendar/parse |
POST |
Parse uploaded/raw ICS text. |
/api/calendar/google |
GET |
Fetch Google Calendar events using OAuth cookies. |
/api/auth/google |
GET |
Start Google OAuth. |
/api/auth/google/callback |
GET |
Complete Google OAuth and store token cookie. |
/api/auth/google/status |
GET |
Report Google configuration and connection state. |
/api/auth/google/disconnect |
POST |
Delete the Google token cookie. |
/api/wienerlinien/stops |
GET |
Find nearby Wiener Linien stops. |
/api/wienerlinien/monitor |
GET |
Fetch and flatten live stop departures. |
Mobile App
The mobile app includes event list, add/edit event, event detail, calendar import, and settings screens. It supports:
- Native calendar sync for the next 30 days.
- Calendar-source selection, including CalDAV/DAVx, Apple, Google, Exchange, subscribed, and local calendars when exposed by the device.
- Saved origin station with current-location lookup.
- Train, bike, walking, and Wiener Linien live sections on event detail.
- Local notifications scheduled from stored event/settings data.
- Dark/light theme toggle.
Documentation
Start with docs/README.md, then use:
Important Implementation Notes
- HAFAS date/time values are Vienna-local strings. Use
parseHafasTime()andhafasDateTime()from@timetoleave/core; avoid ad hocDateparsing for HAFAS payloads. - Remote calendar URLs are restricted to known calendar providers and private/reserved hosts are blocked.
- Mobile devices must use a reachable
EXPO_PUBLIC_API_BASE_URL; same-origin empty base URLs only work in the web app. - This repo uses Next.js 16. Before changing Next.js routing, middleware/proxy, or framework conventions, read the relevant guide in
node_modules/next/dist/docs/.
