iobroker.webuntis2 0.0.1
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 +21 -0
- package/README.de.md +79 -0
- package/README.md +74 -0
- package/admin/build/index.css +2 -0
- package/admin/build/index.css.map +7 -0
- package/admin/build/index.js +171 -0
- package/admin/build/index.js.map +7 -0
- package/admin/index.html +24 -0
- package/admin/index_m.html +24 -0
- package/admin/style.css +32 -0
- package/admin/webuntis2.png +0 -0
- package/build/lib/LegacyNamespaceCleanup.js +33 -0
- package/build/lib/LegacyNamespaceCleanup.js.map +7 -0
- package/build/lib/webuntis/CurrentNextLesson.js +98 -0
- package/build/lib/webuntis/CurrentNextLesson.js.map +7 -0
- package/build/lib/webuntis/Holidays.js +138 -0
- package/build/lib/webuntis/Holidays.js.map +7 -0
- package/build/lib/webuntis/SchoolDiscovery.js +145 -0
- package/build/lib/webuntis/SchoolDiscovery.js.map +7 -0
- package/build/lib/webuntis/Timetable.js +210 -0
- package/build/lib/webuntis/Timetable.js.map +7 -0
- package/build/lib/webuntis/WebUntisClient.js +593 -0
- package/build/lib/webuntis/WebUntisClient.js.map +7 -0
- package/build/lib/webuntis/WebUntisErrors.js +53 -0
- package/build/lib/webuntis/WebUntisErrors.js.map +7 -0
- package/build/lib/webuntis/WebUntisService.js +252 -0
- package/build/lib/webuntis/WebUntisService.js.map +7 -0
- package/build/lib/webuntis/WebUntisTypes.js +17 -0
- package/build/lib/webuntis/WebUntisTypes.js.map +7 -0
- package/build/main.js +514 -0
- package/build/main.js.map +7 -0
- package/io-package.json +151 -0
- package/package.json +111 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var WebUntisClient_exports = {};
|
|
20
|
+
__export(WebUntisClient_exports, {
|
|
21
|
+
WebUntisClient: () => WebUntisClient
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(WebUntisClient_exports);
|
|
24
|
+
var import_WebUntisErrors = require("./WebUntisErrors");
|
|
25
|
+
const REQUEST_TIMEOUT_MS = 1e4;
|
|
26
|
+
function isRecord(value) {
|
|
27
|
+
return typeof value === "object" && value !== null;
|
|
28
|
+
}
|
|
29
|
+
function isAuthenticationResponse(value) {
|
|
30
|
+
return isRecord(value) && typeof value.sessionId === "string" && typeof value.personType === "number" && typeof value.personId === "number" && typeof value.klasseId === "number";
|
|
31
|
+
}
|
|
32
|
+
function isPeriodList(value) {
|
|
33
|
+
return Array.isArray(value) && value.every(isRecord);
|
|
34
|
+
}
|
|
35
|
+
function getClassIdFromConfig(value) {
|
|
36
|
+
var _a;
|
|
37
|
+
if (!isRecord(value) || !isRecord(value.data)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const classId = value.data.klasseId;
|
|
41
|
+
if (typeof classId === "number" && Number.isInteger(classId) && classId > 0) {
|
|
42
|
+
return classId;
|
|
43
|
+
}
|
|
44
|
+
if (typeof classId === "string" && /^\d+$/.test(classId) && Number(classId) > 0) {
|
|
45
|
+
return Number(classId);
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(value.data.klassen)) {
|
|
48
|
+
const firstClass = value.data.klassen.find(isRecord);
|
|
49
|
+
const id = (_a = firstClass == null ? void 0 : firstClass.id) != null ? _a : firstClass == null ? void 0 : firstClass.klasseId;
|
|
50
|
+
if (typeof id === "number" && Number.isInteger(id)) {
|
|
51
|
+
return id;
|
|
52
|
+
}
|
|
53
|
+
if (typeof id === "string" && /^\d+$/.test(id)) {
|
|
54
|
+
return Number(id);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
function publicPeriods(value, personId) {
|
|
60
|
+
if (!isRecord(value) || !isRecord(value.data) || !isRecord(value.data.result)) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
const data = value.data.result.data;
|
|
64
|
+
if (!isRecord(data) || !isRecord(data.elementPeriods)) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const periods = data.elementPeriods[String(personId)];
|
|
68
|
+
if (!Array.isArray(periods)) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
return periods.filter(isRecord).map((period) => {
|
|
72
|
+
const elements = Array.isArray(period.elements) ? period.elements.filter(isRecord) : [];
|
|
73
|
+
const refs = (type) => elements.filter((element) => element.type === type).map((element) => isRecord(element.element) ? element.element : element);
|
|
74
|
+
return { ...period, kl: refs(1), te: refs(2), su: refs(3), ro: refs(4) };
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function getSupportedPerson(value) {
|
|
78
|
+
if (!isRecord(value) || !isRecord(value.data) || !isRecord(value.data.loginServiceConfig)) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const user = value.data.loginServiceConfig.user;
|
|
82
|
+
if (!isRecord(user) || !Array.isArray(user.persons)) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
for (const person of user.persons) {
|
|
86
|
+
if (!isRecord(person) || typeof person.id !== "number" || typeof person.type !== "number") {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (person.type >= 1 && person.type <= 5) {
|
|
90
|
+
return { id: person.id, type: person.type };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
function isLoginPagePayload(value) {
|
|
96
|
+
return isRecord(value) && Boolean(value.loginError);
|
|
97
|
+
}
|
|
98
|
+
function getEndpoint(server, schoolName) {
|
|
99
|
+
var _a;
|
|
100
|
+
const url = new URL(server);
|
|
101
|
+
const webUntisPath = ((_a = url.pathname.match(/^(.*\/WebUntis)(?:\/|$)/i)) == null ? void 0 : _a[1]) || "/WebUntis";
|
|
102
|
+
return `${url.origin}${webUntisPath}/jsonrpc.do?school=${encodeURIComponent(schoolName)}`;
|
|
103
|
+
}
|
|
104
|
+
function getResponseType(body, contentType) {
|
|
105
|
+
if (!body) {
|
|
106
|
+
return "empty";
|
|
107
|
+
}
|
|
108
|
+
if (contentType.toLocaleLowerCase().includes("html") || /^\s*</.test(body)) {
|
|
109
|
+
return "html";
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
JSON.parse(body);
|
|
113
|
+
return "json";
|
|
114
|
+
} catch {
|
|
115
|
+
return "text";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function redactUrl(url) {
|
|
119
|
+
return url.replace(/;jsessionid=[^?;]+/gi, ";jsessionid=<redacted>");
|
|
120
|
+
}
|
|
121
|
+
function getCookieHeader(headers) {
|
|
122
|
+
var _a, _b;
|
|
123
|
+
const cookieHeaders = headers;
|
|
124
|
+
const values = (_b = (_a = cookieHeaders.getSetCookie) == null ? void 0 : _a.call(cookieHeaders)) != null ? _b : [headers.get("set-cookie") || ""];
|
|
125
|
+
const cookies = values.flatMap((value) => value.split(/,(?=[^;,]+=)/)).map((value) => value.split(";", 1)[0].trim()).filter(Boolean);
|
|
126
|
+
return cookies.length ? cookies.join("; ") : void 0;
|
|
127
|
+
}
|
|
128
|
+
function cookieValue(header, name) {
|
|
129
|
+
var _a;
|
|
130
|
+
return (_a = header == null ? void 0 : header.split(/;\s*/).map((cookie) => cookie.split("=", 2)).find(([key]) => key === name)) == null ? void 0 : _a[1];
|
|
131
|
+
}
|
|
132
|
+
function mergeCookieHeader(previous, headers) {
|
|
133
|
+
const values = /* @__PURE__ */ new Map();
|
|
134
|
+
for (const cookie of (previous || "").split(/;\s*/)) {
|
|
135
|
+
const separator = cookie.indexOf("=");
|
|
136
|
+
if (separator > 0) {
|
|
137
|
+
values.set(cookie.slice(0, separator), cookie);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const next = getCookieHeader(headers);
|
|
141
|
+
if (next) {
|
|
142
|
+
for (const cookie of next.split(/;\s*/)) {
|
|
143
|
+
const separator = cookie.indexOf("=");
|
|
144
|
+
if (separator > 0) {
|
|
145
|
+
values.set(cookie.slice(0, separator), cookie);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const value = values.size ? [...values.values()].join("; ") : void 0;
|
|
150
|
+
return {
|
|
151
|
+
value,
|
|
152
|
+
changed: value !== previous,
|
|
153
|
+
sessionCookieChanged: cookieValue(previous, "JSESSIONID") !== cookieValue(value, "JSESSIONID")
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
class WebUntisClient {
|
|
157
|
+
/**
|
|
158
|
+
*
|
|
159
|
+
*/
|
|
160
|
+
constructor(config, fetchImpl = fetch, requestTimeoutMs = REQUEST_TIMEOUT_MS, onDiagnostic) {
|
|
161
|
+
this.fetchImpl = fetchImpl;
|
|
162
|
+
this.requestTimeoutMs = requestTimeoutMs;
|
|
163
|
+
this.onDiagnostic = onDiagnostic;
|
|
164
|
+
this.endpoint = getEndpoint(config.server, config.schoolName);
|
|
165
|
+
}
|
|
166
|
+
endpoint;
|
|
167
|
+
sessionCookie;
|
|
168
|
+
authenticatedAt;
|
|
169
|
+
requestSequence = 0;
|
|
170
|
+
/**
|
|
171
|
+
*
|
|
172
|
+
*/
|
|
173
|
+
async authenticate(username, password) {
|
|
174
|
+
var _a;
|
|
175
|
+
const controller = new AbortController();
|
|
176
|
+
const timer = setTimeout(() => controller.abort(), this.requestTimeoutMs);
|
|
177
|
+
try {
|
|
178
|
+
const response = await this.fetchImpl(this.endpoint, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
181
|
+
body: JSON.stringify({
|
|
182
|
+
id: Date.now().toString(),
|
|
183
|
+
method: "authenticate",
|
|
184
|
+
params: { user: username, password, client: "ioBroker.webuntis2" },
|
|
185
|
+
jsonrpc: "2.0"
|
|
186
|
+
}),
|
|
187
|
+
signal: controller.signal
|
|
188
|
+
});
|
|
189
|
+
this.sessionCookie = getCookieHeader(response.headers);
|
|
190
|
+
this.authenticatedAt = Date.now();
|
|
191
|
+
const contentType = response.headers.get("content-type") || "";
|
|
192
|
+
const body = await response.text();
|
|
193
|
+
const type = getResponseType(body, contentType);
|
|
194
|
+
let payload;
|
|
195
|
+
if (type === "json") {
|
|
196
|
+
try {
|
|
197
|
+
payload = JSON.parse(body);
|
|
198
|
+
} catch {
|
|
199
|
+
throw new import_WebUntisErrors.WebUntisError("INVALID_RESPONSE", "Invalid WebUntis JSON response");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const diagnostic = {
|
|
203
|
+
method: "authenticate",
|
|
204
|
+
status: response.status,
|
|
205
|
+
contentType,
|
|
206
|
+
url: redactUrl(response.url),
|
|
207
|
+
redirected: response.redirected,
|
|
208
|
+
length: body.length,
|
|
209
|
+
type,
|
|
210
|
+
cookiePresent: Boolean(this.sessionCookie),
|
|
211
|
+
responseHeaders: Array.from(response.headers.keys()).sort()
|
|
212
|
+
};
|
|
213
|
+
if (isRecord(payload)) {
|
|
214
|
+
diagnostic.keys = Object.keys(payload);
|
|
215
|
+
if (isRecord(payload.error)) {
|
|
216
|
+
diagnostic.errorKeys = Object.keys(payload.error);
|
|
217
|
+
if (typeof payload.error.code === "number") {
|
|
218
|
+
diagnostic.errorCode = payload.error.code;
|
|
219
|
+
}
|
|
220
|
+
if (typeof payload.error.message === "string") {
|
|
221
|
+
diagnostic.errorMessage = payload.error.message.slice(0, 120);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (isRecord(payload.result)) {
|
|
225
|
+
diagnostic.resultKeys = Object.keys(payload.result);
|
|
226
|
+
if (typeof payload.result.personType === "number") {
|
|
227
|
+
diagnostic.personType = payload.result.personType;
|
|
228
|
+
}
|
|
229
|
+
diagnostic.personIdPresent = typeof payload.result.personId === "number";
|
|
230
|
+
diagnostic.klasseIdPresent = typeof payload.result.klasseId === "number";
|
|
231
|
+
}
|
|
232
|
+
diagnostic.resultType = Array.isArray(payload.result) ? "array" : payload.result === void 0 ? "missing" : isRecord(payload.result) ? "object" : "primitive";
|
|
233
|
+
if (Array.isArray(payload.result)) {
|
|
234
|
+
diagnostic.resultLength = payload.result.length;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
(_a = this.onDiagnostic) == null ? void 0 : _a.call(this, diagnostic);
|
|
238
|
+
if (!response.ok) {
|
|
239
|
+
throw new import_WebUntisErrors.WebUntisError("SERVER_UNREACHABLE", `WebUntis HTTP ${response.status}`);
|
|
240
|
+
}
|
|
241
|
+
if (response.redirected) {
|
|
242
|
+
throw new import_WebUntisErrors.WebUntisError("REDIRECTED", "WebUntis redirected the authentication request");
|
|
243
|
+
}
|
|
244
|
+
if (type === "html") {
|
|
245
|
+
throw new import_WebUntisErrors.WebUntisError("HTML_RESPONSE", "WebUntis returned HTML instead of JSON");
|
|
246
|
+
}
|
|
247
|
+
if (!isRecord(payload)) {
|
|
248
|
+
throw new import_WebUntisErrors.WebUntisError("INVALID_RESPONSE", "Invalid WebUntis response");
|
|
249
|
+
}
|
|
250
|
+
if (isRecord(payload.error)) {
|
|
251
|
+
const code = payload.error.code;
|
|
252
|
+
if (code === -8504 || code === -8520 || code === -8521) {
|
|
253
|
+
throw new import_WebUntisErrors.WebUntisError("INVALID_CREDENTIALS", "WebUntis rejected the credentials");
|
|
254
|
+
}
|
|
255
|
+
if (code === -8522) {
|
|
256
|
+
throw new import_WebUntisErrors.WebUntisError("USER_BLOCKED", "WebUntis user is blocked");
|
|
257
|
+
}
|
|
258
|
+
if (code === -8500) {
|
|
259
|
+
throw new import_WebUntisErrors.WebUntisError("INVALID_SCHOOL", "WebUntis rejected the school");
|
|
260
|
+
}
|
|
261
|
+
throw new import_WebUntisErrors.WebUntisError("UNEXPECTED_RESPONSE", "WebUntis authentication failed");
|
|
262
|
+
}
|
|
263
|
+
if (!isAuthenticationResponse(payload.result)) {
|
|
264
|
+
throw new import_WebUntisErrors.WebUntisError("INVALID_RESPONSE", "Invalid authentication response");
|
|
265
|
+
}
|
|
266
|
+
return payload.result;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (error instanceof import_WebUntisErrors.WebUntisError) {
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
272
|
+
throw new import_WebUntisErrors.WebUntisError("TIMEOUT", "WebUntis authentication timed out");
|
|
273
|
+
}
|
|
274
|
+
throw new import_WebUntisErrors.WebUntisError("SERVER_UNREACHABLE", "WebUntis authentication failed", { cause: error });
|
|
275
|
+
} finally {
|
|
276
|
+
clearTimeout(timer);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
*
|
|
281
|
+
*/
|
|
282
|
+
async getTimetable(session, options) {
|
|
283
|
+
return this.request("getTimetable", { options }, session);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
*
|
|
287
|
+
*/
|
|
288
|
+
async getSubjects(session) {
|
|
289
|
+
return this.request("getSubjects", {}, session);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
*
|
|
293
|
+
*/
|
|
294
|
+
async getTeachers(session) {
|
|
295
|
+
return this.request("getTeachers", {}, session);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
*
|
|
299
|
+
*/
|
|
300
|
+
async getRooms(session) {
|
|
301
|
+
return this.request("getRooms", {}, session);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
*
|
|
305
|
+
*/
|
|
306
|
+
async getKlassen(session) {
|
|
307
|
+
return this.request("getKlassen", {}, session);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
*
|
|
311
|
+
*/
|
|
312
|
+
async getHolidays(session) {
|
|
313
|
+
return this.request("getHolidays", {}, session);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
*
|
|
317
|
+
*/
|
|
318
|
+
async getClassId(session) {
|
|
319
|
+
var _a, _b;
|
|
320
|
+
const url = this.endpoint.replace(/\/jsonrpc\.do\?.*$/i, "/api/daytimetable/config");
|
|
321
|
+
const response = await this.fetchImpl(url, {
|
|
322
|
+
method: "GET",
|
|
323
|
+
headers: {
|
|
324
|
+
accept: "application/json",
|
|
325
|
+
...this.sessionCookie ? { cookie: this.sessionCookie } : { cookie: `JSESSIONID=${session.sessionId}` }
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
const cookie = mergeCookieHeader(this.sessionCookie, response.headers);
|
|
329
|
+
this.sessionCookie = cookie.value;
|
|
330
|
+
const contentType = response.headers.get("content-type") || "";
|
|
331
|
+
const body = await response.text();
|
|
332
|
+
const type = getResponseType(body, contentType);
|
|
333
|
+
let payload;
|
|
334
|
+
try {
|
|
335
|
+
payload = JSON.parse(body);
|
|
336
|
+
} catch {
|
|
337
|
+
(_a = this.onDiagnostic) == null ? void 0 : _a.call(this, {
|
|
338
|
+
method: "getClassId",
|
|
339
|
+
status: response.status,
|
|
340
|
+
contentType,
|
|
341
|
+
url: redactUrl(response.url),
|
|
342
|
+
redirected: response.redirected,
|
|
343
|
+
length: body.length,
|
|
344
|
+
type
|
|
345
|
+
});
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
(_b = this.onDiagnostic) == null ? void 0 : _b.call(this, {
|
|
349
|
+
method: "getClassId",
|
|
350
|
+
status: response.status,
|
|
351
|
+
contentType,
|
|
352
|
+
url: redactUrl(response.url),
|
|
353
|
+
redirected: response.redirected,
|
|
354
|
+
length: body.length,
|
|
355
|
+
type,
|
|
356
|
+
resultType: "object",
|
|
357
|
+
resultKeys: isRecord(payload) ? Object.keys(payload) : void 0,
|
|
358
|
+
dataType: isRecord(payload) ? typeof payload.data : typeof payload,
|
|
359
|
+
dataKeys: isRecord(payload) && isRecord(payload.data) ? Object.keys(payload.data) : [],
|
|
360
|
+
classIdType: isRecord(payload) && isRecord(payload.data) ? typeof payload.data.klasseId : "missing",
|
|
361
|
+
classIdPresent: getClassIdFromConfig(payload) !== null,
|
|
362
|
+
cookiePresent: Boolean(this.sessionCookie),
|
|
363
|
+
cookieChanged: cookie.changed,
|
|
364
|
+
sessionCookieChanged: cookie.sessionCookieChanged,
|
|
365
|
+
cookieCount: this.sessionCookie ? this.sessionCookie.split(/;\s*/).length : 0,
|
|
366
|
+
classCandidateCount: isRecord(payload) && isRecord(payload.data) && Array.isArray(payload.data.klassen) ? payload.data.klassen.length : 0
|
|
367
|
+
});
|
|
368
|
+
if (response.redirected || isLoginPagePayload(payload)) {
|
|
369
|
+
throw new import_WebUntisErrors.WebUntisError("SESSION_EXPIRED", "WebUntis session expired");
|
|
370
|
+
}
|
|
371
|
+
return getClassIdFromConfig(payload);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
*
|
|
375
|
+
*/
|
|
376
|
+
async getPublicTimetable(session, date) {
|
|
377
|
+
var _a;
|
|
378
|
+
const url = new URL(this.endpoint.replace(/\/jsonrpc\.do\?.*$/i, "/api/public/timetable/weekly/data"));
|
|
379
|
+
url.searchParams.set("elementType", String(session.personType));
|
|
380
|
+
url.searchParams.set("elementId", String(session.personId));
|
|
381
|
+
url.searchParams.set("date", date.toISOString().slice(0, 10));
|
|
382
|
+
url.searchParams.set("formatId", "1");
|
|
383
|
+
const response = await this.fetchImpl(url, {
|
|
384
|
+
method: "GET",
|
|
385
|
+
headers: {
|
|
386
|
+
accept: "application/json",
|
|
387
|
+
...this.sessionCookie ? { cookie: this.sessionCookie } : { cookie: `JSESSIONID=${session.sessionId}` }
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
const cookie = mergeCookieHeader(this.sessionCookie, response.headers);
|
|
391
|
+
this.sessionCookie = cookie.value;
|
|
392
|
+
const contentType = response.headers.get("content-type") || "";
|
|
393
|
+
const body = await response.text();
|
|
394
|
+
const type = getResponseType(body, contentType);
|
|
395
|
+
let payload;
|
|
396
|
+
try {
|
|
397
|
+
payload = JSON.parse(body);
|
|
398
|
+
} catch {
|
|
399
|
+
throw new import_WebUntisErrors.WebUntisError("INVALID_RESPONSE", "Invalid WebUntis public timetable response");
|
|
400
|
+
}
|
|
401
|
+
const periods = publicPeriods(payload, session.personId);
|
|
402
|
+
(_a = this.onDiagnostic) == null ? void 0 : _a.call(this, {
|
|
403
|
+
method: "getPublicTimetable",
|
|
404
|
+
status: response.status,
|
|
405
|
+
contentType,
|
|
406
|
+
url: redactUrl(response.url),
|
|
407
|
+
redirected: response.redirected,
|
|
408
|
+
length: body.length,
|
|
409
|
+
type,
|
|
410
|
+
resultType: "array",
|
|
411
|
+
resultLength: periods.length,
|
|
412
|
+
resultKeys: isRecord(payload) ? Object.keys(payload) : void 0,
|
|
413
|
+
cookiePresent: Boolean(this.sessionCookie),
|
|
414
|
+
cookieChanged: cookie.changed,
|
|
415
|
+
sessionCookieChanged: cookie.sessionCookieChanged,
|
|
416
|
+
cookieCount: this.sessionCookie ? this.sessionCookie.split(/;\s*/).length : 0,
|
|
417
|
+
errorMessage: isRecord(payload) && typeof payload.errorMessage === "string" ? payload.errorMessage.slice(0, 120) : isRecord(payload) && isRecord(payload.error) && typeof payload.error.message === "string" ? payload.error.message.slice(0, 120) : void 0
|
|
418
|
+
});
|
|
419
|
+
if (!response.ok) {
|
|
420
|
+
throw new import_WebUntisErrors.WebUntisError("SERVER_UNREACHABLE", `WebUntis HTTP ${response.status}`);
|
|
421
|
+
}
|
|
422
|
+
return periods;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
*
|
|
426
|
+
*/
|
|
427
|
+
async getSupportedPerson(session) {
|
|
428
|
+
var _a, _b;
|
|
429
|
+
const url = this.endpoint.replace(/\/jsonrpc\.do\?.*$/i, "/api/app/config");
|
|
430
|
+
const response = await this.fetchImpl(url, {
|
|
431
|
+
method: "GET",
|
|
432
|
+
headers: {
|
|
433
|
+
accept: "application/json",
|
|
434
|
+
...this.sessionCookie ? { cookie: this.sessionCookie } : { cookie: `JSESSIONID=${session.sessionId}` }
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
const cookie = mergeCookieHeader(this.sessionCookie, response.headers);
|
|
438
|
+
this.sessionCookie = cookie.value;
|
|
439
|
+
const contentType = response.headers.get("content-type") || "";
|
|
440
|
+
const body = await response.text();
|
|
441
|
+
let payload;
|
|
442
|
+
try {
|
|
443
|
+
payload = JSON.parse(body);
|
|
444
|
+
} catch {
|
|
445
|
+
(_a = this.onDiagnostic) == null ? void 0 : _a.call(this, {
|
|
446
|
+
method: "getSupportedPerson",
|
|
447
|
+
status: response.status,
|
|
448
|
+
contentType,
|
|
449
|
+
url: redactUrl(response.url),
|
|
450
|
+
redirected: response.redirected,
|
|
451
|
+
length: body.length,
|
|
452
|
+
type: getResponseType(body, contentType)
|
|
453
|
+
});
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
const person = getSupportedPerson(payload);
|
|
457
|
+
(_b = this.onDiagnostic) == null ? void 0 : _b.call(this, {
|
|
458
|
+
method: "getSupportedPerson",
|
|
459
|
+
status: response.status,
|
|
460
|
+
contentType,
|
|
461
|
+
url: redactUrl(response.url),
|
|
462
|
+
redirected: response.redirected,
|
|
463
|
+
length: body.length,
|
|
464
|
+
type: getResponseType(body, contentType),
|
|
465
|
+
resultType: person ? "object" : "missing",
|
|
466
|
+
personCandidate: Boolean(person),
|
|
467
|
+
resultKeys: isRecord(payload) ? Object.keys(payload) : void 0,
|
|
468
|
+
cookiePresent: Boolean(this.sessionCookie),
|
|
469
|
+
cookieChanged: cookie.changed,
|
|
470
|
+
sessionCookieChanged: cookie.sessionCookieChanged,
|
|
471
|
+
cookieCount: this.sessionCookie ? this.sessionCookie.split(/;\s*/).length : 0,
|
|
472
|
+
errorMessage: isRecord(payload) && typeof payload.errorMessage === "string" ? payload.errorMessage.slice(0, 120) : void 0
|
|
473
|
+
});
|
|
474
|
+
if (response.redirected || isLoginPagePayload(payload)) {
|
|
475
|
+
throw new import_WebUntisErrors.WebUntisError("SESSION_EXPIRED", "WebUntis session expired");
|
|
476
|
+
}
|
|
477
|
+
return person;
|
|
478
|
+
}
|
|
479
|
+
async request(method, params, session) {
|
|
480
|
+
var _a;
|
|
481
|
+
const controller = new AbortController();
|
|
482
|
+
const timer = setTimeout(() => controller.abort(), this.requestTimeoutMs);
|
|
483
|
+
try {
|
|
484
|
+
const requestSequence = ++this.requestSequence;
|
|
485
|
+
const sessionEndpoint = this.endpoint.replace(
|
|
486
|
+
"/jsonrpc.do?",
|
|
487
|
+
`/jsonrpc.do;jsessionid=${encodeURIComponent(session.sessionId)}?`
|
|
488
|
+
);
|
|
489
|
+
const requestOptions = isRecord(params) && isRecord(params.options) ? params.options : void 0;
|
|
490
|
+
const response = await this.fetchImpl(sessionEndpoint, {
|
|
491
|
+
method: "POST",
|
|
492
|
+
headers: {
|
|
493
|
+
"content-type": "application/json",
|
|
494
|
+
accept: "application/json",
|
|
495
|
+
...this.sessionCookie ? { cookie: this.sessionCookie } : {}
|
|
496
|
+
},
|
|
497
|
+
body: JSON.stringify({ id: Date.now().toString(), method, params, jsonrpc: "2.0" }),
|
|
498
|
+
signal: controller.signal
|
|
499
|
+
});
|
|
500
|
+
const body = await response.text();
|
|
501
|
+
const cookie = mergeCookieHeader(this.sessionCookie, response.headers);
|
|
502
|
+
this.sessionCookie = cookie.value;
|
|
503
|
+
const contentType = response.headers.get("content-type") || "";
|
|
504
|
+
const type = getResponseType(body, contentType);
|
|
505
|
+
let payload;
|
|
506
|
+
if (type === "json") {
|
|
507
|
+
try {
|
|
508
|
+
payload = JSON.parse(body);
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const diagnostic = {
|
|
513
|
+
method,
|
|
514
|
+
status: response.status,
|
|
515
|
+
contentType,
|
|
516
|
+
url: redactUrl(response.url),
|
|
517
|
+
redirected: response.redirected,
|
|
518
|
+
length: body.length,
|
|
519
|
+
type,
|
|
520
|
+
cookiePresent: Boolean(this.sessionCookie),
|
|
521
|
+
cookieChanged: cookie.changed,
|
|
522
|
+
sessionCookieChanged: cookie.sessionCookieChanged,
|
|
523
|
+
cookieCount: this.sessionCookie ? this.sessionCookie.split(/;\s*/).length : 0,
|
|
524
|
+
sessionAgeSeconds: this.authenticatedAt ? Math.max(0, Math.round((Date.now() - this.authenticatedAt) / 1e3)) : void 0,
|
|
525
|
+
requestSequence,
|
|
526
|
+
sessionPresent: Boolean(session.sessionId),
|
|
527
|
+
personIdPresent: typeof session.personId === "number",
|
|
528
|
+
elementType: isRecord(requestOptions == null ? void 0 : requestOptions.element) ? typeof requestOptions.element.type === "number" ? requestOptions.element.type : void 0 : void 0,
|
|
529
|
+
startDate: typeof (requestOptions == null ? void 0 : requestOptions.startDate) === "number" ? requestOptions.startDate : void 0,
|
|
530
|
+
endDate: typeof (requestOptions == null ? void 0 : requestOptions.endDate) === "number" ? requestOptions.endDate : void 0
|
|
531
|
+
};
|
|
532
|
+
if (isRecord(payload)) {
|
|
533
|
+
diagnostic.keys = Object.keys(payload);
|
|
534
|
+
if (isRecord(payload.error)) {
|
|
535
|
+
diagnostic.errorKeys = Object.keys(payload.error);
|
|
536
|
+
if (typeof payload.error.code === "number") {
|
|
537
|
+
diagnostic.errorCode = payload.error.code;
|
|
538
|
+
}
|
|
539
|
+
if (typeof payload.error.message === "string") {
|
|
540
|
+
diagnostic.errorMessage = payload.error.message.slice(0, 120);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (Array.isArray(payload.result)) {
|
|
544
|
+
diagnostic.resultType = "array";
|
|
545
|
+
diagnostic.resultLength = payload.result.length;
|
|
546
|
+
} else if (isRecord(payload.result)) {
|
|
547
|
+
diagnostic.resultType = "object";
|
|
548
|
+
diagnostic.resultKeys = Object.keys(payload.result);
|
|
549
|
+
} else {
|
|
550
|
+
diagnostic.resultType = payload.result === void 0 ? "missing" : "primitive";
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
(_a = this.onDiagnostic) == null ? void 0 : _a.call(this, diagnostic);
|
|
554
|
+
if (!response.ok) {
|
|
555
|
+
throw new import_WebUntisErrors.WebUntisError("SERVER_UNREACHABLE", `WebUntis HTTP ${response.status}`);
|
|
556
|
+
}
|
|
557
|
+
if (!isRecord(payload)) {
|
|
558
|
+
throw new import_WebUntisErrors.WebUntisError(
|
|
559
|
+
type === "html" ? "HTML_RESPONSE" : "INVALID_RESPONSE",
|
|
560
|
+
type === "html" ? "WebUntis returned HTML instead of JSON" : "Invalid WebUntis timetable response"
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
if (isRecord(payload.error)) {
|
|
564
|
+
if (payload.error.code === -7001 && typeof payload.error.message === "string" && payload.error.message.startsWith("invalid elementType:")) {
|
|
565
|
+
throw new import_WebUntisErrors.WebUntisError("API_REQUEST", "WebUntis rejected the timetable element type");
|
|
566
|
+
}
|
|
567
|
+
if (payload.error.code === -7001) {
|
|
568
|
+
throw new import_WebUntisErrors.WebUntisError("SESSION_EXPIRED", "WebUntis session expired");
|
|
569
|
+
}
|
|
570
|
+
throw new import_WebUntisErrors.WebUntisError("UNEXPECTED_RESPONSE", "WebUntis timetable request failed");
|
|
571
|
+
}
|
|
572
|
+
if (!isPeriodList(payload.result)) {
|
|
573
|
+
throw new import_WebUntisErrors.WebUntisError("INVALID_RESPONSE", "Invalid WebUntis timetable result");
|
|
574
|
+
}
|
|
575
|
+
return payload.result;
|
|
576
|
+
} catch (error) {
|
|
577
|
+
if (error instanceof import_WebUntisErrors.WebUntisError) {
|
|
578
|
+
throw error;
|
|
579
|
+
}
|
|
580
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
581
|
+
throw new import_WebUntisErrors.WebUntisError("TIMEOUT", "WebUntis timetable request timed out");
|
|
582
|
+
}
|
|
583
|
+
throw new import_WebUntisErrors.WebUntisError("SERVER_UNREACHABLE", "WebUntis timetable request failed", { cause: error });
|
|
584
|
+
} finally {
|
|
585
|
+
clearTimeout(timer);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
590
|
+
0 && (module.exports = {
|
|
591
|
+
WebUntisClient
|
|
592
|
+
});
|
|
593
|
+
//# sourceMappingURL=WebUntisClient.js.map
|