@riseonly/sdk 0.1.1-next.0 → 0.1.2

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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createServer } from 'http';
2
- import { createHmac, timingSafeEqual } from 'crypto';
2
+ import { timingSafeEqual, createHmac } from 'crypto';
3
3
 
4
4
  // src/bot.ts
5
5
 
@@ -92,6 +92,22 @@ var RiseonlyTimeoutError = class extends RiseonlyNetworkError {
92
92
  this.name = "RiseonlyTimeoutError";
93
93
  }
94
94
  };
95
+ var RiseonlyAbortError = class extends RiseonlyNetworkError {
96
+ constructor(message = "Request was aborted", cause) {
97
+ super(message, cause, 499);
98
+ this.name = "RiseonlyAbortError";
99
+ }
100
+ };
101
+ var RiseonlyResponseError = class extends RiseonlyNetworkError {
102
+ status;
103
+ body;
104
+ constructor(message, status, body) {
105
+ super(message, void 0, status);
106
+ this.name = "RiseonlyResponseError";
107
+ this.status = status;
108
+ this.body = body;
109
+ }
110
+ };
95
111
 
96
112
  // src/client.ts
97
113
  var ApiClient = class {
@@ -106,8 +122,15 @@ var ApiClient = class {
106
122
  }
107
123
  this.token = token.trim();
108
124
  this.baseUrl = (options.baseUrl ?? DEFAULT_API_BASE).replace(/\/$/, "");
109
- this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
125
+ const fetchImpl = options.fetch ?? globalThis.fetch;
126
+ if (typeof fetchImpl !== "function") {
127
+ throw new Error("global fetch is unavailable; provide ClientOptions.fetch");
128
+ }
129
+ this.fetchImpl = options.fetch ? fetchImpl : fetchImpl.bind(globalThis);
110
130
  this.requestTimeoutMs = options.requestTimeoutMs ?? 6e4;
131
+ if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs <= 0) {
132
+ throw new Error("requestTimeoutMs must be a positive number");
133
+ }
111
134
  this.useCompatibleEndpoint = options.useCompatibleEndpoint ?? false;
112
135
  }
113
136
  /**
@@ -115,15 +138,21 @@ var ApiClient = class {
115
138
  */
116
139
  async getCapabilities(signal) {
117
140
  const url = `${this.baseUrl}${CANONICAL_API_PREFIX}/capabilities`;
118
- const response = await this.fetchImpl(url, {
141
+ const { response, body } = await this.fetchJson(url, {
119
142
  method: "GET",
120
- headers: { Accept: "application/json" },
121
- signal
122
- });
143
+ headers: { Accept: "application/json" }
144
+ }, { signal });
123
145
  if (!response.ok) {
124
- throw new RiseonlyNetworkError(`capabilities request failed with status ${response.status}`);
146
+ throw new RiseonlyResponseError(
147
+ `capabilities request failed with status ${response.status}`,
148
+ response.status,
149
+ serializeResponseBody(body)
150
+ );
125
151
  }
126
- return response.json();
152
+ if (!isRecord(body) || !Array.isArray(body.methods)) {
153
+ throw new RiseonlyResponseError("capabilities response is invalid", response.status);
154
+ }
155
+ return body;
127
156
  }
128
157
  /**
129
158
  * Calls a Bot API method and returns the typed result.
@@ -145,37 +174,36 @@ var ApiClient = class {
145
174
  if (options.requestId) {
146
175
  headers[IDEMPOTENCY_HEADER] = options.requestId;
147
176
  }
148
- const controller = new AbortController();
149
- const timeout = setTimeout(() => controller.abort(), this.requestTimeoutMs);
150
- const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
151
- let response;
152
- try {
153
- response = await this.fetchImpl(url, {
177
+ const { response, body } = await this.fetchJson(
178
+ url,
179
+ {
154
180
  method: "POST",
155
181
  headers,
156
- body: JSON.stringify(payload),
157
- signal
158
- });
159
- } catch (error) {
160
- if (error instanceof Error && error.name === "AbortError") {
161
- throw new RiseonlyTimeoutError();
162
- }
163
- throw new RiseonlyNetworkError("network request failed", error);
164
- } finally {
165
- clearTimeout(timeout);
166
- }
167
- const body = await response.json();
168
- if (!body.ok) {
182
+ body: JSON.stringify(payload)
183
+ },
184
+ options
185
+ );
186
+ if (isApiErrorResponse(body)) {
169
187
  throw RiseonlyError.fromApiResponse(body);
170
188
  }
189
+ if (!response.ok) {
190
+ throw new RiseonlyResponseError(
191
+ `Bot API request failed with status ${response.status}`,
192
+ response.status,
193
+ serializeResponseBody(body)
194
+ );
195
+ }
196
+ if (!isRecord(body) || body.ok !== true || !("result" in body)) {
197
+ throw new RiseonlyResponseError("Bot API response envelope is invalid", response.status);
198
+ }
171
199
  return body;
172
200
  }
173
201
  async getMe(options) {
174
202
  return this.callMethod("getMe", {}, options);
175
203
  }
176
204
  async sendMessage(options) {
177
- const { requestId, ...payload } = options;
178
- return this.callMethod("sendMessage", payload, { requestId });
205
+ const { request, payload } = splitRequestOptions(options);
206
+ return this.callMethod("sendMessage", payload, request);
179
207
  }
180
208
  async sendPhoto(photo, options) {
181
209
  return this.sendMedia("sendPhoto", "photo", photo, options);
@@ -196,72 +224,75 @@ var ApiClient = class {
196
224
  return this.sendMedia("sendAnimation", "animation", animation, options);
197
225
  }
198
226
  async editMessageText(options) {
199
- const { requestId, ...payload } = options;
200
- return this.callMethod("editMessageText", payload, { requestId });
227
+ const { request, payload } = splitRequestOptions(options);
228
+ return this.callMethod("editMessageText", payload, request);
201
229
  }
202
230
  async deleteMessage(options) {
203
- const { requestId, ...payload } = options;
204
- return this.callMethod("deleteMessage", payload, { requestId });
231
+ const { request, payload } = splitRequestOptions(options);
232
+ return this.callMethod("deleteMessage", payload, request);
205
233
  }
206
234
  async sendChatAction(options) {
207
- const { requestId, ...payload } = options;
208
- return this.callMethod("sendChatAction", payload, { requestId });
235
+ const { request, payload } = splitRequestOptions(options);
236
+ return this.callMethod("sendChatAction", payload, request);
209
237
  }
210
238
  async getUpdates(options = {}) {
211
- const { requestId, ...payload } = options;
212
- return this.callMethod("getUpdates", payload, { requestId });
239
+ const { request, payload } = splitRequestOptions(options);
240
+ const serverTimeoutMs = (options.timeout ?? 0) * 1e3 + 5e3;
241
+ request.timeoutMs ??= Math.max(this.requestTimeoutMs, serverTimeoutMs);
242
+ return this.callMethod("getUpdates", payload, request);
213
243
  }
214
244
  async setWebhook(options) {
215
- const { requestId, ...payload } = options;
216
- return this.callMethod("setWebhook", payload, { requestId });
245
+ const { request, payload } = splitRequestOptions(options);
246
+ return this.callMethod("setWebhook", payload, request);
217
247
  }
218
248
  async deleteWebhook(options = {}) {
219
- const { requestId, ...payload } = options;
220
- return this.callMethod("deleteWebhook", payload, { requestId });
249
+ const { request, payload } = splitRequestOptions(options);
250
+ return this.callMethod("deleteWebhook", payload, request);
221
251
  }
222
252
  async getWebhookInfo(options) {
223
253
  return this.callMethod("getWebhookInfo", {}, options);
224
254
  }
225
255
  async setMyCommands(options) {
226
- const { requestId, ...payload } = options;
227
- return this.callMethod("setMyCommands", payload, { requestId });
256
+ const { request, payload } = splitRequestOptions(options);
257
+ return this.callMethod("setMyCommands", payload, request);
228
258
  }
229
259
  async getMyCommands(options = {}) {
230
- const { requestId, ...payload } = options;
231
- return this.callMethod("getMyCommands", payload, { requestId });
260
+ const { request, payload } = splitRequestOptions(options);
261
+ return this.callMethod("getMyCommands", payload, request);
232
262
  }
233
263
  async deleteMyCommands(options = {}) {
234
- const { requestId, ...payload } = options;
235
- return this.callMethod("deleteMyCommands", payload, { requestId });
264
+ const { request, payload } = splitRequestOptions(options);
265
+ return this.callMethod("deleteMyCommands", payload, request);
236
266
  }
237
267
  async answerCallbackQuery(options) {
238
- const { requestId, ...payload } = options;
239
- return this.callMethod("answerCallbackQuery", payload, { requestId });
268
+ const { request, payload } = splitRequestOptions(options);
269
+ return this.callMethod("answerCallbackQuery", payload, request);
240
270
  }
241
271
  async getChat(options) {
242
- const { requestId, ...payload } = options;
243
- return this.callMethod("getChat", payload, { requestId });
272
+ const { request, payload } = splitRequestOptions(options);
273
+ return this.callMethod("getChat", payload, request);
244
274
  }
245
275
  async sendMedia(method, field, media, options) {
246
- const { requestId, ...payload } = options;
276
+ const { request, payload } = splitRequestOptions(options);
247
277
  return this.callMethod(
248
278
  method,
249
279
  {
250
280
  ...payload,
251
281
  [field]: this.normalizeMedia(media)
252
282
  },
253
- { requestId }
283
+ request
254
284
  );
255
285
  }
256
286
  normalizeMedia(media) {
257
287
  if (typeof media === "string") {
258
288
  return media;
259
289
  }
260
- if (media.file_id) {
261
- return media.file_id;
262
- }
263
- if (media.url) {
264
- return media.url;
290
+ if (media.file_id || media.url) {
291
+ return Object.fromEntries(
292
+ Object.entries(media).filter(
293
+ (entry) => typeof entry[1] === "string" || typeof entry[1] === "number"
294
+ )
295
+ );
265
296
  }
266
297
  throw new Error("media requires file_id or url");
267
298
  }
@@ -271,9 +302,77 @@ var ApiClient = class {
271
302
  }
272
303
  return `${this.baseUrl}${CANONICAL_API_PREFIX}/${method}`;
273
304
  }
305
+ async fetchJson(url, init, options = {}) {
306
+ const controller = new AbortController();
307
+ let timedOut = false;
308
+ const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs;
309
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
310
+ throw new Error("timeoutMs must be a positive number");
311
+ }
312
+ const abortFromCaller = () => controller.abort(options.signal?.reason);
313
+ if (options.signal?.aborted) {
314
+ throw new RiseonlyAbortError("Request was aborted", options.signal.reason);
315
+ }
316
+ options.signal?.addEventListener("abort", abortFromCaller, { once: true });
317
+ const timeout = setTimeout(() => {
318
+ timedOut = true;
319
+ controller.abort();
320
+ }, timeoutMs);
321
+ let response;
322
+ try {
323
+ response = await this.fetchImpl(url, { ...init, signal: controller.signal });
324
+ } catch (error) {
325
+ if (controller.signal.aborted) {
326
+ if (timedOut) {
327
+ throw new RiseonlyTimeoutError();
328
+ }
329
+ throw new RiseonlyAbortError("Request was aborted", error);
330
+ }
331
+ throw new RiseonlyNetworkError("network request failed", error);
332
+ } finally {
333
+ clearTimeout(timeout);
334
+ options.signal?.removeEventListener("abort", abortFromCaller);
335
+ }
336
+ let text;
337
+ try {
338
+ text = await response.text();
339
+ } catch (error) {
340
+ throw new RiseonlyNetworkError("unable to read response body", error, response.status);
341
+ }
342
+ if (!text) {
343
+ throw new RiseonlyResponseError("Bot API returned an empty response", response.status);
344
+ }
345
+ try {
346
+ return { response, body: JSON.parse(text) };
347
+ } catch {
348
+ throw new RiseonlyResponseError(
349
+ "Bot API returned a non-JSON response",
350
+ response.status,
351
+ text.slice(0, 4096)
352
+ );
353
+ }
354
+ }
274
355
  };
275
-
276
- // src/webhook.ts
356
+ function splitRequestOptions(options) {
357
+ const { requestId, signal, timeoutMs, ...payload } = options;
358
+ return {
359
+ request: { requestId, signal, timeoutMs },
360
+ payload
361
+ };
362
+ }
363
+ function isRecord(value) {
364
+ return typeof value === "object" && value !== null && !Array.isArray(value);
365
+ }
366
+ function isApiErrorResponse(value) {
367
+ return isRecord(value) && value.ok === false && typeof value.error_code === "number" && typeof value.description === "string";
368
+ }
369
+ function serializeResponseBody(body) {
370
+ try {
371
+ return JSON.stringify(body).slice(0, 4096);
372
+ } catch {
373
+ return void 0;
374
+ }
375
+ }
277
376
  function parseWebhookUpdate(body) {
278
377
  if (!body || typeof body !== "object") {
279
378
  throw new Error("webhook body must be an object");
@@ -288,28 +387,49 @@ function verifyWebhookSecret(headers, secretToken) {
288
387
  if (!secretToken) {
289
388
  return true;
290
389
  }
291
- const headerValue = headers[WEBHOOK_SECRET_HEADER] ?? headers[WEBHOOK_SECRET_HEADER.toLowerCase()];
390
+ const headerEntry = Object.entries(headers).find(
391
+ ([name]) => name.toLowerCase() === WEBHOOK_SECRET_HEADER
392
+ );
393
+ const headerValue = headerEntry?.[1];
292
394
  if (!headerValue) {
293
395
  return false;
294
396
  }
295
397
  const received = Array.isArray(headerValue) ? headerValue[0] : headerValue;
296
- return received === secretToken;
398
+ if (typeof received !== "string") {
399
+ return false;
400
+ }
401
+ const actual = Buffer.from(received);
402
+ const expected = Buffer.from(secretToken);
403
+ return actual.length === expected.length && timingSafeEqual(actual, expected);
297
404
  }
298
405
  function createWebhookHandler(options) {
299
406
  return async (request) => {
407
+ if (!verifyWebhookSecret(request.headers, options.secretToken)) {
408
+ return { status: 401, body: { ok: false, description: "invalid webhook secret" } };
409
+ }
410
+ let update;
411
+ try {
412
+ update = parseWebhookUpdate(request.body);
413
+ } catch (error) {
414
+ await notifyWebhookError(options.onError, error);
415
+ return { status: 400, body: { ok: false, description: "invalid webhook payload" } };
416
+ }
300
417
  try {
301
- if (!verifyWebhookSecret(request.headers, options.secretToken)) {
302
- return { status: 401, body: { ok: false, description: "invalid webhook secret" } };
303
- }
304
- const update = parseWebhookUpdate(request.body);
305
418
  await options.onUpdate(update);
306
419
  return { status: 200, body: { ok: true } };
307
420
  } catch (error) {
308
- await options.onError?.(error);
309
- return { status: 400, body: { ok: false, description: "invalid webhook payload" } };
421
+ await notifyWebhookError(options.onError, error);
422
+ return { status: 500, body: { ok: false, description: "webhook handler failed" } };
310
423
  }
311
424
  };
312
425
  }
426
+ async function notifyWebhookError(handler, error) {
427
+ try {
428
+ await handler?.(error);
429
+ } catch {
430
+ return;
431
+ }
432
+ }
313
433
 
314
434
  // src/bot.ts
315
435
  var RiseonlyBot = class extends ApiClient {
@@ -325,6 +445,7 @@ var RiseonlyBot = class extends ApiClient {
325
445
  webhookOptions;
326
446
  polling = false;
327
447
  pollingAbort;
448
+ pollingTask;
328
449
  offset = 0;
329
450
  webhookServer;
330
451
  constructor(token, options = {}) {
@@ -366,6 +487,67 @@ var RiseonlyBot = class extends ApiClient {
366
487
  });
367
488
  return this.on(event, wrapper);
368
489
  }
490
+ command(commands, handler) {
491
+ const accepted = new Set(
492
+ (Array.isArray(commands) ? commands : [commands]).map((command) => command.replace(/^\//, "").toLowerCase()).filter(Boolean)
493
+ );
494
+ return this.on("message", async (message, update) => {
495
+ const text = message.text?.trim();
496
+ if (!text?.startsWith("/")) {
497
+ return;
498
+ }
499
+ const token = text.split(/\s+/, 1)[0].slice(1).split("@", 1)[0].toLowerCase();
500
+ if (accepted.has(token)) {
501
+ await handler(message, update);
502
+ }
503
+ });
504
+ }
505
+ hears(trigger, handler) {
506
+ return this.on("message", async (message, update) => {
507
+ if (message.text === void 0) {
508
+ return;
509
+ }
510
+ if (typeof trigger === "string") {
511
+ if (message.text === trigger) {
512
+ await handler(message, null, update);
513
+ }
514
+ return;
515
+ }
516
+ trigger.lastIndex = 0;
517
+ const match = trigger.exec(message.text);
518
+ if (match) {
519
+ await handler(message, match, update);
520
+ }
521
+ });
522
+ }
523
+ action(trigger, handler) {
524
+ return this.on("callback_query", async (query, update) => {
525
+ if (query.data === void 0) {
526
+ return;
527
+ }
528
+ const matches = typeof trigger === "string" ? query.data === trigger : testPattern(trigger, query.data);
529
+ if (matches) {
530
+ await handler(query, update);
531
+ }
532
+ });
533
+ }
534
+ catch(handler) {
535
+ return this.on("error", handler);
536
+ }
537
+ async setup(options) {
538
+ if (options.commands) {
539
+ await this.setMyCommands({
540
+ commands: options.commands,
541
+ scope: options.commandScope,
542
+ language_code: options.languageCode
543
+ });
544
+ }
545
+ if (options.webhook === false) {
546
+ await this.deleteWebhook();
547
+ } else if (options.webhook) {
548
+ await this.setWebhook(options.webhook);
549
+ }
550
+ }
369
551
  /**
370
552
  * Starts long polling for bot updates.
371
553
  */
@@ -376,7 +558,7 @@ var RiseonlyBot = class extends ApiClient {
376
558
  this.polling = true;
377
559
  this.pollingOptions = { ...this.pollingOptions, ...options, autoStart: true };
378
560
  this.pollingAbort = new AbortController();
379
- void this.pollLoop();
561
+ this.pollingTask = this.pollLoop();
380
562
  }
381
563
  /**
382
564
  * Stops long polling.
@@ -384,7 +566,15 @@ var RiseonlyBot = class extends ApiClient {
384
566
  async stopPolling() {
385
567
  this.polling = false;
386
568
  this.pollingAbort?.abort();
569
+ await this.pollingTask;
387
570
  this.pollingAbort = void 0;
571
+ this.pollingTask = void 0;
572
+ }
573
+ async launch(options = {}) {
574
+ await this.startPolling(options);
575
+ }
576
+ async stop() {
577
+ await Promise.all([this.stopPolling(), this.stopWebhook()]);
388
578
  }
389
579
  /**
390
580
  * Starts a built-in HTTP server that receives webhook updates.
@@ -395,6 +585,12 @@ var RiseonlyBot = class extends ApiClient {
395
585
  const path = config.path ?? "/";
396
586
  const host = config.host ?? "0.0.0.0";
397
587
  const port = config.port ?? 3e3;
588
+ if (!path.startsWith("/")) {
589
+ throw new Error("webhook path must start with /");
590
+ }
591
+ if (config.maxBodyBytes !== void 0 && config.maxBodyBytes <= 0) {
592
+ throw new Error("maxBodyBytes must be positive");
593
+ }
398
594
  const handler = createWebhookHandler({
399
595
  secretToken: config.secretToken,
400
596
  onUpdate: (update) => this.processUpdate(update),
@@ -409,15 +605,16 @@ var RiseonlyBot = class extends ApiClient {
409
605
  return;
410
606
  }
411
607
  try {
412
- const body = await readJsonBody(req);
608
+ const body = await readJsonBody(req, config.maxBodyBytes);
413
609
  const result = await handler({
414
610
  headers: normalizeHeaders(req.headers),
415
611
  body
416
612
  });
417
613
  respond(res, result.status, result.body);
418
614
  } catch (error) {
419
- await this.emit("error", error);
420
- respond(res, 500, { ok: false });
615
+ await this.emitError(error);
616
+ const status = error instanceof WebhookPayloadTooLargeError ? 413 : 500;
617
+ respond(res, status, { ok: false });
421
618
  }
422
619
  });
423
620
  await new Promise((resolve, reject) => {
@@ -489,8 +686,8 @@ var RiseonlyBot = class extends ApiClient {
489
686
  if (update.update_id < this.offset) {
490
687
  continue;
491
688
  }
492
- this.offset = Math.max(this.offset, update.update_id + 1);
493
689
  await this.processUpdate(update);
690
+ this.offset = Math.max(this.offset, update.update_id + 1);
494
691
  processed += 1;
495
692
  }
496
693
  if (processed === 0) {
@@ -500,7 +697,10 @@ var RiseonlyBot = class extends ApiClient {
500
697
  if (!this.polling || isAbortError(error)) {
501
698
  return;
502
699
  }
503
- await this.emit("polling_error", error);
700
+ try {
701
+ await this.emit("polling_error", error);
702
+ } catch {
703
+ }
504
704
  if (error instanceof RiseonlyError && error.retryAfter) {
505
705
  await sleep(error.retryAfter * 1e3);
506
706
  } else {
@@ -510,11 +710,27 @@ var RiseonlyBot = class extends ApiClient {
510
710
  }
511
711
  }
512
712
  async emit(event, ...args) {
713
+ let firstError;
513
714
  for (const listener of [...this.listeners[event]]) {
514
715
  try {
515
716
  await listener(...args);
516
717
  } catch (error) {
517
- await this.emit("error", error);
718
+ firstError ??= error;
719
+ if (event !== "error") {
720
+ await this.emitError(error);
721
+ }
722
+ }
723
+ }
724
+ if (firstError !== void 0) {
725
+ throw firstError;
726
+ }
727
+ }
728
+ async emitError(error) {
729
+ for (const listener of [...this.listeners.error]) {
730
+ try {
731
+ await listener(error);
732
+ } catch {
733
+ continue;
518
734
  }
519
735
  }
520
736
  }
@@ -538,7 +754,11 @@ function sleep(ms) {
538
754
  return new Promise((resolve) => setTimeout(resolve, ms));
539
755
  }
540
756
  function isAbortError(error) {
541
- return error instanceof Error && error.name === "AbortError";
757
+ return error instanceof Error && (error.name === "AbortError" || error.name === "RiseonlyAbortError");
758
+ }
759
+ function testPattern(pattern, value) {
760
+ pattern.lastIndex = 0;
761
+ return pattern.test(value);
542
762
  }
543
763
  function normalizeHeaders(headers) {
544
764
  const normalized = {};
@@ -547,10 +767,19 @@ function normalizeHeaders(headers) {
547
767
  }
548
768
  return normalized;
549
769
  }
550
- async function readJsonBody(req) {
770
+ var WebhookPayloadTooLargeError = class extends Error {
771
+ };
772
+ async function readJsonBody(req, maxBodyBytes = 1024 * 1024) {
551
773
  const chunks = [];
774
+ let received = 0;
552
775
  for await (const chunk of req) {
553
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
776
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
777
+ received += buffer.length;
778
+ if (received > maxBodyBytes) {
779
+ req.resume();
780
+ throw new WebhookPayloadTooLargeError("webhook payload is too large");
781
+ }
782
+ chunks.push(buffer);
554
783
  }
555
784
  if (chunks.length === 0) {
556
785
  return {};
@@ -593,8 +822,12 @@ function verifyInitData(botToken, encoded) {
593
822
  if (left.length !== right.length || !timingSafeEqual(left, right)) {
594
823
  throw new Error("init data signature is invalid");
595
824
  }
596
- const expiresAt = fields.expires_at ? Number(fields.expires_at) : void 0;
597
- if (expiresAt !== void 0 && Number.isFinite(expiresAt) && expiresAt < Date.now()) {
825
+ const authDate = Number(fields.auth_date);
826
+ const expiresAt = Number(fields.expires_at);
827
+ if (!Number.isSafeInteger(authDate) || !Number.isSafeInteger(expiresAt) || expiresAt <= authDate) {
828
+ throw new Error("init data timestamps are invalid");
829
+ }
830
+ if (expiresAt < Date.now()) {
598
831
  throw new Error("init data has expired");
599
832
  }
600
833
  return fields;
@@ -611,6 +844,6 @@ function createRequestId() {
611
844
  return crypto.randomUUID();
612
845
  }
613
846
 
614
- export { ApiClient, CANONICAL_API_PREFIX, CHAT_ACTIONS, COMMAND_SCOPES, DEFAULT_API_BASE, IDEMPOTENCY_HEADER, RiseonlyBot, RiseonlyError, RiseonlyNetworkError, RiseonlyTimeoutError, SUPPORTED_METHODS, UPDATE_TYPES, WEBHOOK_SECRET_HEADER, buildInitDataCheckString, createRequestId, createWebhookHandler, deriveInitDataSecret, extractInitDataFromLaunchUrl, parseInitData, parseWebhookUpdate, signInitData, verifyInitData, verifyWebhookSecret };
847
+ export { ApiClient, CANONICAL_API_PREFIX, CHAT_ACTIONS, COMMAND_SCOPES, DEFAULT_API_BASE, IDEMPOTENCY_HEADER, RiseonlyAbortError, RiseonlyBot, RiseonlyError, RiseonlyNetworkError, RiseonlyResponseError, RiseonlyTimeoutError, SUPPORTED_METHODS, UPDATE_TYPES, WEBHOOK_SECRET_HEADER, buildInitDataCheckString, createRequestId, createWebhookHandler, deriveInitDataSecret, extractInitDataFromLaunchUrl, parseInitData, parseWebhookUpdate, signInitData, verifyInitData, verifyWebhookSecret };
615
848
  //# sourceMappingURL=index.js.map
616
849
  //# sourceMappingURL=index.js.map