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/LICENSE +674 -0
- package/README.md +211 -0
- package/dist/index.d.mts +561 -0
- package/dist/index.d.ts +561 -0
- package/dist/index.js +864 -0
- package/dist/index.mjs +803 -0
- package/package.json +43 -0
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.
|