zet-api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,211 @@
1
+ # zet-api
2
+
3
+ Unofficial TypeScript client for the [ZET](https://www.zet.hr) (Zagreb Electric Tram) API.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install zet-api
9
+ yarn add zet-api
10
+ ```
11
+
12
+ ## Quick Start
13
+
14
+ ```ts
15
+ import { ZETClient } from "zet-api";
16
+
17
+ const zet = new ZETClient({
18
+ appKeys: {
19
+ ticket: "YOUR_TICKET_APP_KEY",
20
+ order: "YOUR_ORDER_APP_KEY",
21
+ },
22
+ });
23
+
24
+ await zet.login({
25
+ username: "your@email.com",
26
+ password: "your-password",
27
+ });
28
+
29
+ const account = await zet.getAccount();
30
+ console.log(account.ePurseAmount);
31
+ ```
32
+
33
+ ## Expo Token Storage Example
34
+
35
+ ```ts
36
+ import * as SecureStore from "expo-secure-store";
37
+ import { ZETClient } from "zet-api";
38
+
39
+ const zet = new ZETClient({
40
+ appKeys: {
41
+ ticket: "YOUR_TICKET_APP_KEY",
42
+ order: "YOUR_ORDER_APP_KEY",
43
+ },
44
+ storage: {
45
+ get: (key) => SecureStore.getItemAsync(key),
46
+ set: (key, value) => SecureStore.setItemAsync(key, value),
47
+ delete: (key) => SecureStore.deleteItemAsync(key),
48
+ },
49
+ onTokenRefresh: (tokens) => {
50
+ console.log("Refreshed", tokens.accessToken);
51
+ },
52
+ onAuthFailure: () => {
53
+ console.log("Session expired");
54
+ },
55
+ cacheTTL: -1,
56
+ });
57
+ ```
58
+
59
+ ## Constructor Options
60
+
61
+ ```ts
62
+ type ZETClientOptions = {
63
+ appKeys: {
64
+ ticket: string;
65
+ order: string;
66
+ };
67
+ storage?: ZETTokenStorage;
68
+ onTokenRefresh?: (tokens: ZETAuthTokens) => void;
69
+ onAuthFailure?: () => void;
70
+ timeout?: number;
71
+ baseUrl?: string;
72
+ cacheTTL?: number; // -1: never expire, 0: always refetch
73
+ };
74
+ ```
75
+
76
+ ## API Usage
77
+
78
+ ### Auth
79
+
80
+ ```ts
81
+ await zet.login({ username, password });
82
+ await zet.setTokens({ accessToken, refreshToken });
83
+ await zet.clearTokens();
84
+ const token = await zet.getAccessToken();
85
+ ```
86
+
87
+ ### Account
88
+
89
+ ```ts
90
+ const account = await zet.getAccount();
91
+ ```
92
+
93
+ ### Routes, Stops, Search
94
+
95
+ ```ts
96
+ const routes = await zet.getRoutes();
97
+ const tramRoutes = await zet.getRoutes(0);
98
+ const route = await zet.getRouteById(1);
99
+ const foundRoutes = await zet.searchRoutes("savski", { limit: 10 });
100
+
101
+ const stops = await zet.getStops();
102
+ const stop = await zet.getStopById("1234");
103
+ const foundStops = await zet.searchStops("glavni", { routeType: 0, limit: 10 });
104
+ ```
105
+
106
+ ### Shapes
107
+
108
+ ```ts
109
+ const shapes = await zet.getShapes();
110
+ const routeShapes = await zet.getShapesByRouteId(1);
111
+ ```
112
+
113
+ ### Timetable
114
+
115
+ ```ts
116
+ const trips = await zet.getRouteTrips(1, { daysFromToday: 0 });
117
+ const stopTimes = await zet.getTripStopTimes(trips[0].id, { daysFromToday: 0 });
118
+ const incoming = await zet.getStopIncomingTrips("1234");
119
+ ```
120
+
121
+ ### News
122
+
123
+ ```ts
124
+ const news = await zet.getNewsFeed();
125
+ const newsAlias = await zet.getNewsfeed();
126
+ const lineNews = await zet.getNewsByRoute(6);
127
+ ```
128
+
129
+ ### Tickets
130
+
131
+ ```ts
132
+ const history = await zet.getTicketHistory({ pageSize: 20, pageNumber: 1 });
133
+ const active = await zet.getActiveTickets({ validOnly: true });
134
+ const articles = await zet.getArticles({ lat: 45.815, lng: 15.982 });
135
+
136
+ const bought = await zet.buyTicket({
137
+ trafficZone: 1,
138
+ articleId: 703,
139
+ vehicleNumber: "T2113",
140
+ });
141
+
142
+ const changed = await zet.changeTicketVehicle({
143
+ accountId: 12345,
144
+ vehicleNumber: "T2217",
145
+ });
146
+ ```
147
+
148
+ ### Orders and Payment Flow
149
+
150
+ ```ts
151
+ const orders = await zet.getOrders({
152
+ completedOnly: true,
153
+ pageSize: 20,
154
+ pageNumber: 1,
155
+ });
156
+
157
+ const payment = await zet.getTopUpPaymentInfo({ mPurse: 1 });
158
+ // payment.url -> Corvus URL
159
+ // payment.orderNumber -> use for cancel
160
+
161
+ const cancelHtml = await zet.cancelTopUpPayment({
162
+ orderNumber: payment.orderNumber ?? "",
163
+ language: "hr",
164
+ });
165
+ ```
166
+
167
+ ### GTFS-RT
168
+
169
+ ```ts
170
+ import { fetchGtfsRtVehicles, getNearbyVehicles } from "zet-api";
171
+
172
+ const vehicles = await fetchGtfsRtVehicles();
173
+ const nearby = getNearbyVehicles(vehicles, 45.81, 15.98, 500);
174
+ ```
175
+
176
+ ## Auth Behavior
177
+
178
+ - Attaches `Authorization: Bearer <token>` when token exists
179
+ - On `401`, refreshes token once and retries request
180
+ - Queues concurrent requests while refresh is in progress
181
+ - Calls `onAuthFailure()` and throws `ZETAuthError` if refresh fails
182
+
183
+ ## Cache Behavior
184
+
185
+ - Internal/private in-memory cache
186
+ - No manual refresh/clear required
187
+ - `routes`, `stops`, `shapes` use `cacheTTL`
188
+ - `news` refreshes more frequently with an internal shorter TTL
189
+ - In-flight dedupe prevents duplicate concurrent fetches for same resource
190
+
191
+ ## Error Handling
192
+
193
+ ```ts
194
+ import { ZETApiError, ZETAuthError } from "zet-api";
195
+
196
+ try {
197
+ await zet.getAccount();
198
+ } catch (error) {
199
+ if (error instanceof ZETAuthError) {
200
+ console.log("Authentication failed!");
201
+ } else if (error instanceof ZETApiError) {
202
+ console.log(error.status, error.endpoint);
203
+ }
204
+ }
205
+ ```
206
+
207
+ ## Disclaimer & License
208
+
209
+ Unofficial client reverse-engineered from observed app traffic. Use responsibly.
210
+
211
+ This project is licensed under the GNU General Public License v3.0. See [LICENSE](./LICENSE) for details.