@riseonly/sdk 0.1.1 → 0.1.3

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/dist/index.cjs CHANGED
@@ -29,6 +29,12 @@ var SUPPORTED_METHODS = [
29
29
  "setMyCommands",
30
30
  "getMyCommands",
31
31
  "deleteMyCommands",
32
+ "setMyName",
33
+ "getMyName",
34
+ "setMyDescription",
35
+ "getMyDescription",
36
+ "setMyProfilePhoto",
37
+ "removeMyProfilePhoto",
32
38
  "answerCallbackQuery",
33
39
  "getChat"
34
40
  ];
@@ -94,6 +100,22 @@ var RiseonlyTimeoutError = class extends RiseonlyNetworkError {
94
100
  this.name = "RiseonlyTimeoutError";
95
101
  }
96
102
  };
103
+ var RiseonlyAbortError = class extends RiseonlyNetworkError {
104
+ constructor(message = "Request was aborted", cause) {
105
+ super(message, cause, 499);
106
+ this.name = "RiseonlyAbortError";
107
+ }
108
+ };
109
+ var RiseonlyResponseError = class extends RiseonlyNetworkError {
110
+ status;
111
+ body;
112
+ constructor(message, status, body) {
113
+ super(message, void 0, status);
114
+ this.name = "RiseonlyResponseError";
115
+ this.status = status;
116
+ this.body = body;
117
+ }
118
+ };
97
119
 
98
120
  // src/client.ts
99
121
  var ApiClient = class {
@@ -108,8 +130,15 @@ var ApiClient = class {
108
130
  }
109
131
  this.token = token.trim();
110
132
  this.baseUrl = (options.baseUrl ?? DEFAULT_API_BASE).replace(/\/$/, "");
111
- this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
133
+ const fetchImpl = options.fetch ?? globalThis.fetch;
134
+ if (typeof fetchImpl !== "function") {
135
+ throw new Error("global fetch is unavailable; provide ClientOptions.fetch");
136
+ }
137
+ this.fetchImpl = options.fetch ? fetchImpl : fetchImpl.bind(globalThis);
112
138
  this.requestTimeoutMs = options.requestTimeoutMs ?? 6e4;
139
+ if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs <= 0) {
140
+ throw new Error("requestTimeoutMs must be a positive number");
141
+ }
113
142
  this.useCompatibleEndpoint = options.useCompatibleEndpoint ?? false;
114
143
  }
115
144
  /**
@@ -117,15 +146,21 @@ var ApiClient = class {
117
146
  */
118
147
  async getCapabilities(signal) {
119
148
  const url = `${this.baseUrl}${CANONICAL_API_PREFIX}/capabilities`;
120
- const response = await this.fetchImpl(url, {
149
+ const { response, body } = await this.fetchJson(url, {
121
150
  method: "GET",
122
- headers: { Accept: "application/json" },
123
- signal
124
- });
151
+ headers: { Accept: "application/json" }
152
+ }, { signal });
125
153
  if (!response.ok) {
126
- throw new RiseonlyNetworkError(`capabilities request failed with status ${response.status}`);
154
+ throw new RiseonlyResponseError(
155
+ `capabilities request failed with status ${response.status}`,
156
+ response.status,
157
+ serializeResponseBody(body)
158
+ );
159
+ }
160
+ if (!isRecord(body) || !Array.isArray(body.methods)) {
161
+ throw new RiseonlyResponseError("capabilities response is invalid", response.status);
127
162
  }
128
- return response.json();
163
+ return body;
129
164
  }
130
165
  /**
131
166
  * Calls a Bot API method and returns the typed result.
@@ -147,37 +182,36 @@ var ApiClient = class {
147
182
  if (options.requestId) {
148
183
  headers[IDEMPOTENCY_HEADER] = options.requestId;
149
184
  }
150
- const controller = new AbortController();
151
- const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
152
- const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
153
- let response;
154
- try {
155
- response = await this.fetchImpl(url, {
185
+ const { response, body } = await this.fetchJson(
186
+ url,
187
+ {
156
188
  method: "POST",
157
189
  headers,
158
- body: JSON.stringify(payload),
159
- signal
160
- });
161
- } catch (error) {
162
- if (error instanceof Error && error.name === "AbortError") {
163
- throw new RiseonlyTimeoutError();
164
- }
165
- throw new RiseonlyNetworkError("network request failed", error);
166
- } finally {
167
- clearTimeout(timeout);
168
- }
169
- const body = await response.json();
170
- if (!body.ok) {
190
+ body: JSON.stringify(payload)
191
+ },
192
+ options
193
+ );
194
+ if (isApiErrorResponse(body)) {
171
195
  throw RiseonlyError.fromApiResponse(body);
172
196
  }
197
+ if (!response.ok) {
198
+ throw new RiseonlyResponseError(
199
+ `Bot API request failed with status ${response.status}`,
200
+ response.status,
201
+ serializeResponseBody(body)
202
+ );
203
+ }
204
+ if (!isRecord(body) || body.ok !== true || !("result" in body)) {
205
+ throw new RiseonlyResponseError("Bot API response envelope is invalid", response.status);
206
+ }
173
207
  return body;
174
208
  }
175
209
  async getMe(options) {
176
210
  return this.callMethod("getMe", {}, options);
177
211
  }
178
212
  async sendMessage(options) {
179
- const { requestId, ...payload } = options;
180
- return this.callMethod("sendMessage", payload, { requestId });
213
+ const { request, payload } = splitRequestOptions(options);
214
+ return this.callMethod("sendMessage", payload, request);
181
215
  }
182
216
  async sendPhoto(photo, options) {
183
217
  return this.sendMedia("sendPhoto", "photo", photo, options);
@@ -198,72 +232,96 @@ var ApiClient = class {
198
232
  return this.sendMedia("sendAnimation", "animation", animation, options);
199
233
  }
200
234
  async editMessageText(options) {
201
- const { requestId, ...payload } = options;
202
- return this.callMethod("editMessageText", payload, { requestId });
235
+ const { request, payload } = splitRequestOptions(options);
236
+ return this.callMethod("editMessageText", payload, request);
203
237
  }
204
238
  async deleteMessage(options) {
205
- const { requestId, ...payload } = options;
206
- return this.callMethod("deleteMessage", payload, { requestId });
239
+ const { request, payload } = splitRequestOptions(options);
240
+ return this.callMethod("deleteMessage", payload, request);
207
241
  }
208
242
  async sendChatAction(options) {
209
- const { requestId, ...payload } = options;
210
- return this.callMethod("sendChatAction", payload, { requestId });
243
+ const { request, payload } = splitRequestOptions(options);
244
+ return this.callMethod("sendChatAction", payload, request);
211
245
  }
212
246
  async getUpdates(options = {}) {
213
- const { requestId, ...payload } = options;
214
- return this.callMethod("getUpdates", payload, { requestId });
247
+ const { request, payload } = splitRequestOptions(options);
248
+ const serverTimeoutMs = (options.timeout ?? 0) * 1e3 + 5e3;
249
+ request.timeoutMs ??= Math.max(this.requestTimeoutMs, serverTimeoutMs);
250
+ return this.callMethod("getUpdates", payload, request);
215
251
  }
216
252
  async setWebhook(options) {
217
- const { requestId, ...payload } = options;
218
- return this.callMethod("setWebhook", payload, { requestId });
253
+ const { request, payload } = splitRequestOptions(options);
254
+ return this.callMethod("setWebhook", payload, request);
219
255
  }
220
256
  async deleteWebhook(options = {}) {
221
- const { requestId, ...payload } = options;
222
- return this.callMethod("deleteWebhook", payload, { requestId });
257
+ const { request, payload } = splitRequestOptions(options);
258
+ return this.callMethod("deleteWebhook", payload, request);
223
259
  }
224
260
  async getWebhookInfo(options) {
225
261
  return this.callMethod("getWebhookInfo", {}, options);
226
262
  }
227
263
  async setMyCommands(options) {
228
- const { requestId, ...payload } = options;
229
- return this.callMethod("setMyCommands", payload, { requestId });
264
+ const { request, payload } = splitRequestOptions(options);
265
+ return this.callMethod("setMyCommands", payload, request);
230
266
  }
231
267
  async getMyCommands(options = {}) {
232
- const { requestId, ...payload } = options;
233
- return this.callMethod("getMyCommands", payload, { requestId });
268
+ const { request, payload } = splitRequestOptions(options);
269
+ return this.callMethod("getMyCommands", payload, request);
234
270
  }
235
271
  async deleteMyCommands(options = {}) {
236
- const { requestId, ...payload } = options;
237
- return this.callMethod("deleteMyCommands", payload, { requestId });
272
+ const { request, payload } = splitRequestOptions(options);
273
+ return this.callMethod("deleteMyCommands", payload, request);
274
+ }
275
+ async setMyName(options) {
276
+ const { request, payload } = splitRequestOptions(options);
277
+ return this.callMethod("setMyName", payload, request);
278
+ }
279
+ async getMyName(options = {}) {
280
+ return this.callMethod("getMyName", {}, options);
281
+ }
282
+ async setMyDescription(options) {
283
+ const { request, payload } = splitRequestOptions(options);
284
+ return this.callMethod("setMyDescription", payload, request);
285
+ }
286
+ async getMyDescription(options = {}) {
287
+ return this.callMethod("getMyDescription", {}, options);
288
+ }
289
+ async setMyProfilePhoto(options) {
290
+ const { request, payload } = splitRequestOptions(options);
291
+ return this.callMethod("setMyProfilePhoto", payload, request);
292
+ }
293
+ async removeMyProfilePhoto(options = {}) {
294
+ return this.callMethod("removeMyProfilePhoto", {}, options);
238
295
  }
239
296
  async answerCallbackQuery(options) {
240
- const { requestId, ...payload } = options;
241
- return this.callMethod("answerCallbackQuery", payload, { requestId });
297
+ const { request, payload } = splitRequestOptions(options);
298
+ return this.callMethod("answerCallbackQuery", payload, request);
242
299
  }
243
300
  async getChat(options) {
244
- const { requestId, ...payload } = options;
245
- return this.callMethod("getChat", payload, { requestId });
301
+ const { request, payload } = splitRequestOptions(options);
302
+ return this.callMethod("getChat", payload, request);
246
303
  }
247
304
  async sendMedia(method, field, media, options) {
248
- const { requestId, ...payload } = options;
305
+ const { request, payload } = splitRequestOptions(options);
249
306
  return this.callMethod(
250
307
  method,
251
308
  {
252
309
  ...payload,
253
310
  [field]: this.normalizeMedia(media)
254
311
  },
255
- { requestId }
312
+ request
256
313
  );
257
314
  }
258
315
  normalizeMedia(media) {
259
316
  if (typeof media === "string") {
260
317
  return media;
261
318
  }
262
- if (media.file_id) {
263
- return media.file_id;
264
- }
265
- if (media.url) {
266
- return media.url;
319
+ if (media.file_id || media.url) {
320
+ return Object.fromEntries(
321
+ Object.entries(media).filter(
322
+ (entry) => typeof entry[1] === "string" || typeof entry[1] === "number"
323
+ )
324
+ );
267
325
  }
268
326
  throw new Error("media requires file_id or url");
269
327
  }
@@ -273,9 +331,77 @@ var ApiClient = class {
273
331
  }
274
332
  return `${this.baseUrl}${CANONICAL_API_PREFIX}/${method}`;
275
333
  }
334
+ async fetchJson(url, init, options = {}) {
335
+ const controller = new AbortController();
336
+ let timedOut = false;
337
+ const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs;
338
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
339
+ throw new Error("timeoutMs must be a positive number");
340
+ }
341
+ const abortFromCaller = () => controller.abort(options.signal?.reason);
342
+ if (options.signal?.aborted) {
343
+ throw new RiseonlyAbortError("Request was aborted", options.signal.reason);
344
+ }
345
+ options.signal?.addEventListener("abort", abortFromCaller, { once: true });
346
+ const timeout = setTimeout(() => {
347
+ timedOut = true;
348
+ controller.abort();
349
+ }, timeoutMs);
350
+ let response;
351
+ try {
352
+ response = await this.fetchImpl(url, { ...init, signal: controller.signal });
353
+ } catch (error) {
354
+ if (controller.signal.aborted) {
355
+ if (timedOut) {
356
+ throw new RiseonlyTimeoutError();
357
+ }
358
+ throw new RiseonlyAbortError("Request was aborted", error);
359
+ }
360
+ throw new RiseonlyNetworkError("network request failed", error);
361
+ } finally {
362
+ clearTimeout(timeout);
363
+ options.signal?.removeEventListener("abort", abortFromCaller);
364
+ }
365
+ let text;
366
+ try {
367
+ text = await response.text();
368
+ } catch (error) {
369
+ throw new RiseonlyNetworkError("unable to read response body", error, response.status);
370
+ }
371
+ if (!text) {
372
+ throw new RiseonlyResponseError("Bot API returned an empty response", response.status);
373
+ }
374
+ try {
375
+ return { response, body: JSON.parse(text) };
376
+ } catch {
377
+ throw new RiseonlyResponseError(
378
+ "Bot API returned a non-JSON response",
379
+ response.status,
380
+ text.slice(0, 4096)
381
+ );
382
+ }
383
+ }
276
384
  };
277
-
278
- // src/webhook.ts
385
+ function splitRequestOptions(options) {
386
+ const { requestId, signal, timeoutMs, ...payload } = options;
387
+ return {
388
+ request: { requestId, signal, timeoutMs },
389
+ payload
390
+ };
391
+ }
392
+ function isRecord(value) {
393
+ return typeof value === "object" && value !== null && !Array.isArray(value);
394
+ }
395
+ function isApiErrorResponse(value) {
396
+ return isRecord(value) && value.ok === false && typeof value.error_code === "number" && typeof value.description === "string";
397
+ }
398
+ function serializeResponseBody(body) {
399
+ try {
400
+ return JSON.stringify(body).slice(0, 4096);
401
+ } catch {
402
+ return void 0;
403
+ }
404
+ }
279
405
  function parseWebhookUpdate(body) {
280
406
  if (!body || typeof body !== "object") {
281
407
  throw new Error("webhook body must be an object");
@@ -290,28 +416,49 @@ function verifyWebhookSecret(headers, secretToken) {
290
416
  if (!secretToken) {
291
417
  return true;
292
418
  }
293
- const headerValue = headers[WEBHOOK_SECRET_HEADER] ?? headers[WEBHOOK_SECRET_HEADER.toLowerCase()];
419
+ const headerEntry = Object.entries(headers).find(
420
+ ([name]) => name.toLowerCase() === WEBHOOK_SECRET_HEADER
421
+ );
422
+ const headerValue = headerEntry?.[1];
294
423
  if (!headerValue) {
295
424
  return false;
296
425
  }
297
426
  const received = Array.isArray(headerValue) ? headerValue[0] : headerValue;
298
- return received === secretToken;
427
+ if (typeof received !== "string") {
428
+ return false;
429
+ }
430
+ const actual = Buffer.from(received);
431
+ const expected = Buffer.from(secretToken);
432
+ return actual.length === expected.length && crypto$1.timingSafeEqual(actual, expected);
299
433
  }
300
434
  function createWebhookHandler(options) {
301
435
  return async (request) => {
436
+ if (!verifyWebhookSecret(request.headers, options.secretToken)) {
437
+ return { status: 401, body: { ok: false, description: "invalid webhook secret" } };
438
+ }
439
+ let update;
440
+ try {
441
+ update = parseWebhookUpdate(request.body);
442
+ } catch (error) {
443
+ await notifyWebhookError(options.onError, error);
444
+ return { status: 400, body: { ok: false, description: "invalid webhook payload" } };
445
+ }
302
446
  try {
303
- if (!verifyWebhookSecret(request.headers, options.secretToken)) {
304
- return { status: 401, body: { ok: false, description: "invalid webhook secret" } };
305
- }
306
- const update = parseWebhookUpdate(request.body);
307
447
  await options.onUpdate(update);
308
448
  return { status: 200, body: { ok: true } };
309
449
  } catch (error) {
310
- await options.onError?.(error);
311
- return { status: 400, body: { ok: false, description: "invalid webhook payload" } };
450
+ await notifyWebhookError(options.onError, error);
451
+ return { status: 500, body: { ok: false, description: "webhook handler failed" } };
312
452
  }
313
453
  };
314
454
  }
455
+ async function notifyWebhookError(handler, error) {
456
+ try {
457
+ await handler?.(error);
458
+ } catch {
459
+ return;
460
+ }
461
+ }
315
462
 
316
463
  // src/bot.ts
317
464
  var RiseonlyBot = class extends ApiClient {
@@ -327,6 +474,7 @@ var RiseonlyBot = class extends ApiClient {
327
474
  webhookOptions;
328
475
  polling = false;
329
476
  pollingAbort;
477
+ pollingTask;
330
478
  offset = 0;
331
479
  webhookServer;
332
480
  constructor(token, options = {}) {
@@ -368,6 +516,76 @@ var RiseonlyBot = class extends ApiClient {
368
516
  });
369
517
  return this.on(event, wrapper);
370
518
  }
519
+ command(commands, handler) {
520
+ const accepted = new Set(
521
+ (Array.isArray(commands) ? commands : [commands]).map((command) => command.replace(/^\//, "").toLowerCase()).filter(Boolean)
522
+ );
523
+ return this.on("message", async (message, update) => {
524
+ const text = message.text?.trim();
525
+ if (!text?.startsWith("/")) {
526
+ return;
527
+ }
528
+ const token = text.split(/\s+/, 1)[0].slice(1).split("@", 1)[0].toLowerCase();
529
+ if (accepted.has(token)) {
530
+ await handler(message, update);
531
+ }
532
+ });
533
+ }
534
+ hears(trigger, handler) {
535
+ return this.on("message", async (message, update) => {
536
+ if (message.text === void 0) {
537
+ return;
538
+ }
539
+ if (typeof trigger === "string") {
540
+ if (message.text === trigger) {
541
+ await handler(message, null, update);
542
+ }
543
+ return;
544
+ }
545
+ trigger.lastIndex = 0;
546
+ const match = trigger.exec(message.text);
547
+ if (match) {
548
+ await handler(message, match, update);
549
+ }
550
+ });
551
+ }
552
+ action(trigger, handler) {
553
+ return this.on("callback_query", async (query, update) => {
554
+ if (query.data === void 0) {
555
+ return;
556
+ }
557
+ const matches = typeof trigger === "string" ? query.data === trigger : testPattern(trigger, query.data);
558
+ if (matches) {
559
+ await handler(query, update);
560
+ }
561
+ });
562
+ }
563
+ catch(handler) {
564
+ return this.on("error", handler);
565
+ }
566
+ async setup(options) {
567
+ if (options.name !== void 0) {
568
+ await this.setMyName({ name: options.name });
569
+ }
570
+ if (options.description !== void 0) {
571
+ await this.setMyDescription({ description: options.description });
572
+ }
573
+ if (options.profilePhoto !== void 0) {
574
+ await this.setMyProfilePhoto({ photo: options.profilePhoto });
575
+ }
576
+ if (options.commands) {
577
+ await this.setMyCommands({
578
+ commands: options.commands,
579
+ scope: options.commandScope,
580
+ language_code: options.languageCode
581
+ });
582
+ }
583
+ if (options.webhook === false) {
584
+ await this.deleteWebhook();
585
+ } else if (options.webhook) {
586
+ await this.setWebhook(options.webhook);
587
+ }
588
+ }
371
589
  /**
372
590
  * Starts long polling for bot updates.
373
591
  */
@@ -378,7 +596,7 @@ var RiseonlyBot = class extends ApiClient {
378
596
  this.polling = true;
379
597
  this.pollingOptions = { ...this.pollingOptions, ...options, autoStart: true };
380
598
  this.pollingAbort = new AbortController();
381
- void this.pollLoop();
599
+ this.pollingTask = this.pollLoop();
382
600
  }
383
601
  /**
384
602
  * Stops long polling.
@@ -386,7 +604,15 @@ var RiseonlyBot = class extends ApiClient {
386
604
  async stopPolling() {
387
605
  this.polling = false;
388
606
  this.pollingAbort?.abort();
607
+ await this.pollingTask;
389
608
  this.pollingAbort = void 0;
609
+ this.pollingTask = void 0;
610
+ }
611
+ async launch(options = {}) {
612
+ await this.startPolling(options);
613
+ }
614
+ async stop() {
615
+ await Promise.all([this.stopPolling(), this.stopWebhook()]);
390
616
  }
391
617
  /**
392
618
  * Starts a built-in HTTP server that receives webhook updates.
@@ -397,6 +623,12 @@ var RiseonlyBot = class extends ApiClient {
397
623
  const path = config.path ?? "/";
398
624
  const host = config.host ?? "0.0.0.0";
399
625
  const port = config.port ?? 3e3;
626
+ if (!path.startsWith("/")) {
627
+ throw new Error("webhook path must start with /");
628
+ }
629
+ if (config.maxBodyBytes !== void 0 && config.maxBodyBytes <= 0) {
630
+ throw new Error("maxBodyBytes must be positive");
631
+ }
400
632
  const handler = createWebhookHandler({
401
633
  secretToken: config.secretToken,
402
634
  onUpdate: (update) => this.processUpdate(update),
@@ -411,15 +643,16 @@ var RiseonlyBot = class extends ApiClient {
411
643
  return;
412
644
  }
413
645
  try {
414
- const body = await readJsonBody(req);
646
+ const body = await readJsonBody(req, config.maxBodyBytes);
415
647
  const result = await handler({
416
648
  headers: normalizeHeaders(req.headers),
417
649
  body
418
650
  });
419
651
  respond(res, result.status, result.body);
420
652
  } catch (error) {
421
- await this.emit("error", error);
422
- respond(res, 500, { ok: false });
653
+ await this.emitError(error);
654
+ const status = error instanceof WebhookPayloadTooLargeError ? 413 : 500;
655
+ respond(res, status, { ok: false });
423
656
  }
424
657
  });
425
658
  await new Promise((resolve, reject) => {
@@ -491,8 +724,8 @@ var RiseonlyBot = class extends ApiClient {
491
724
  if (update.update_id < this.offset) {
492
725
  continue;
493
726
  }
494
- this.offset = Math.max(this.offset, update.update_id + 1);
495
727
  await this.processUpdate(update);
728
+ this.offset = Math.max(this.offset, update.update_id + 1);
496
729
  processed += 1;
497
730
  }
498
731
  if (processed === 0) {
@@ -502,7 +735,10 @@ var RiseonlyBot = class extends ApiClient {
502
735
  if (!this.polling || isAbortError(error)) {
503
736
  return;
504
737
  }
505
- await this.emit("polling_error", error);
738
+ try {
739
+ await this.emit("polling_error", error);
740
+ } catch {
741
+ }
506
742
  if (error instanceof RiseonlyError && error.retryAfter) {
507
743
  await sleep(error.retryAfter * 1e3);
508
744
  } else {
@@ -512,11 +748,27 @@ var RiseonlyBot = class extends ApiClient {
512
748
  }
513
749
  }
514
750
  async emit(event, ...args) {
751
+ let firstError;
515
752
  for (const listener of [...this.listeners[event]]) {
516
753
  try {
517
754
  await listener(...args);
518
755
  } catch (error) {
519
- await this.emit("error", error);
756
+ firstError ??= error;
757
+ if (event !== "error") {
758
+ await this.emitError(error);
759
+ }
760
+ }
761
+ }
762
+ if (firstError !== void 0) {
763
+ throw firstError;
764
+ }
765
+ }
766
+ async emitError(error) {
767
+ for (const listener of [...this.listeners.error]) {
768
+ try {
769
+ await listener(error);
770
+ } catch {
771
+ continue;
520
772
  }
521
773
  }
522
774
  }
@@ -540,7 +792,11 @@ function sleep(ms) {
540
792
  return new Promise((resolve) => setTimeout(resolve, ms));
541
793
  }
542
794
  function isAbortError(error) {
543
- return error instanceof Error && error.name === "AbortError";
795
+ return error instanceof Error && (error.name === "AbortError" || error.name === "RiseonlyAbortError");
796
+ }
797
+ function testPattern(pattern, value) {
798
+ pattern.lastIndex = 0;
799
+ return pattern.test(value);
544
800
  }
545
801
  function normalizeHeaders(headers) {
546
802
  const normalized = {};
@@ -549,10 +805,19 @@ function normalizeHeaders(headers) {
549
805
  }
550
806
  return normalized;
551
807
  }
552
- async function readJsonBody(req) {
808
+ var WebhookPayloadTooLargeError = class extends Error {
809
+ };
810
+ async function readJsonBody(req, maxBodyBytes = 1024 * 1024) {
553
811
  const chunks = [];
812
+ let received = 0;
554
813
  for await (const chunk of req) {
555
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
814
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
815
+ received += buffer.length;
816
+ if (received > maxBodyBytes) {
817
+ req.resume();
818
+ throw new WebhookPayloadTooLargeError("webhook payload is too large");
819
+ }
820
+ chunks.push(buffer);
556
821
  }
557
822
  if (chunks.length === 0) {
558
823
  return {};
@@ -595,8 +860,12 @@ function verifyInitData(botToken, encoded) {
595
860
  if (left.length !== right.length || !crypto$1.timingSafeEqual(left, right)) {
596
861
  throw new Error("init data signature is invalid");
597
862
  }
598
- const expiresAt = fields.expires_at ? Number(fields.expires_at) : void 0;
599
- if (expiresAt !== void 0 && Number.isFinite(expiresAt) && expiresAt < Date.now()) {
863
+ const authDate = Number(fields.auth_date);
864
+ const expiresAt = Number(fields.expires_at);
865
+ if (!Number.isSafeInteger(authDate) || !Number.isSafeInteger(expiresAt) || expiresAt <= authDate) {
866
+ throw new Error("init data timestamps are invalid");
867
+ }
868
+ if (expiresAt < Date.now()) {
600
869
  throw new Error("init data has expired");
601
870
  }
602
871
  return fields;
@@ -619,9 +888,11 @@ exports.CHAT_ACTIONS = CHAT_ACTIONS;
619
888
  exports.COMMAND_SCOPES = COMMAND_SCOPES;
620
889
  exports.DEFAULT_API_BASE = DEFAULT_API_BASE;
621
890
  exports.IDEMPOTENCY_HEADER = IDEMPOTENCY_HEADER;
891
+ exports.RiseonlyAbortError = RiseonlyAbortError;
622
892
  exports.RiseonlyBot = RiseonlyBot;
623
893
  exports.RiseonlyError = RiseonlyError;
624
894
  exports.RiseonlyNetworkError = RiseonlyNetworkError;
895
+ exports.RiseonlyResponseError = RiseonlyResponseError;
625
896
  exports.RiseonlyTimeoutError = RiseonlyTimeoutError;
626
897
  exports.SUPPORTED_METHODS = SUPPORTED_METHODS;
627
898
  exports.UPDATE_TYPES = UPDATE_TYPES;