itd-api 0.0.3 → 0.0.4

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.
@@ -16,6 +16,30 @@ function resolveInput(input, factory, validate) {
16
16
 
17
17
  // src/core/errors.ts
18
18
  var ITD_ERROR = /* @__PURE__ */ Symbol.for("itd.error");
19
+ var ItdErrorKind = Object.freeze({
20
+ /** Сервер ответил статусом ≥ 400. */
21
+ Api: "api",
22
+ /** Запрос не дошёл до сервера. */
23
+ Network: "network",
24
+ /** Истёк таймаут запроса. */
25
+ Timeout: "timeout",
26
+ /** Запрос отменён через `AbortSignal`. */
27
+ Abort: "abort",
28
+ /** Некорректная конфигурация или аргументы — обнаружено до обращения к сети. */
29
+ Config: "config"
30
+ });
31
+ var ItdApiErrorKind = Object.freeze({
32
+ /** Ни одна из специализаций не подошла. */
33
+ Generic: "generic",
34
+ Validation: "validation",
35
+ Auth: "auth",
36
+ Forbidden: "forbidden",
37
+ NotFound: "not_found",
38
+ Conflict: "conflict",
39
+ RateLimit: "rate_limit",
40
+ PhoneVerification: "phone_verification",
41
+ Server: "server"
42
+ });
19
43
  var ItdError = class extends Error {
20
44
  /** @internal */
21
45
  [ITD_ERROR] = true;
@@ -28,6 +52,13 @@ var ItdError = class extends Error {
28
52
  }
29
53
  };
30
54
  var ItdApiError = class extends ItdError {
55
+ /**
56
+ * Разновидность ошибки: та же информация, что и класс, но пригодная для сравнения.
57
+ *
58
+ * Позволяет разбирать ошибку через `switch`, а проверкам вроде {@link isItdAuthError} —
59
+ * работать даже когда в проекте оказались две копии библиотеки.
60
+ */
61
+ apiKind;
31
62
  /** HTTP-статус ответа. */
32
63
  status;
33
64
  /** Строковый код ошибки, например `VALIDATION_ERROR`. */
@@ -58,9 +89,13 @@ var ItdApiError = class extends ItdError {
58
89
  rateLimit;
59
90
  /** Сколько запросов осталось в окне — заголовок `x-ratelimit-remaining`. */
60
91
  rateLimitRemaining;
61
- constructor(init) {
62
- super("api", init.message);
92
+ /**
93
+ * @param apiKind разновидность; подставляется подклассами, снаружи задавать не нужно
94
+ */
95
+ constructor(init, apiKind = ItdApiErrorKind.Generic) {
96
+ super(ItdErrorKind.Api, init.message);
63
97
  this.name = "ItdApiError";
98
+ this.apiKind = apiKind;
64
99
  this.status = init.status;
65
100
  this.code = init.code;
66
101
  this.detail = init.detail;
@@ -93,37 +128,37 @@ var ItdApiError = class extends ItdError {
93
128
  };
94
129
  var ItdValidationError = class extends ItdApiError {
95
130
  constructor(init) {
96
- super(init);
131
+ super(init, ItdApiErrorKind.Validation);
97
132
  this.name = "ItdValidationError";
98
133
  }
99
134
  };
100
135
  var ItdAuthError = class extends ItdApiError {
101
136
  constructor(init) {
102
- super(init);
137
+ super(init, ItdApiErrorKind.Auth);
103
138
  this.name = "ItdAuthError";
104
139
  }
105
140
  };
106
141
  var ItdForbiddenError = class extends ItdApiError {
107
142
  constructor(init) {
108
- super(init);
143
+ super(init, ItdApiErrorKind.Forbidden);
109
144
  this.name = "ItdForbiddenError";
110
145
  }
111
146
  };
112
147
  var ItdNotFoundError = class extends ItdApiError {
113
148
  constructor(init) {
114
- super(init);
149
+ super(init, ItdApiErrorKind.NotFound);
115
150
  this.name = "ItdNotFoundError";
116
151
  }
117
152
  };
118
153
  var ItdConflictError = class extends ItdApiError {
119
154
  constructor(init) {
120
- super(init);
155
+ super(init, ItdApiErrorKind.Conflict);
121
156
  this.name = "ItdConflictError";
122
157
  }
123
158
  };
124
159
  var ItdRateLimitError = class extends ItdApiError {
125
160
  constructor(init) {
126
- super(init);
161
+ super(init, ItdApiErrorKind.RateLimit);
127
162
  this.name = "ItdRateLimitError";
128
163
  }
129
164
  };
@@ -131,14 +166,14 @@ var ItdPhoneVerificationError = class extends ItdApiError {
131
166
  /** Ссылка на бота подтверждения, если удалось определить идентификатор пользователя. */
132
167
  verificationUrl;
133
168
  constructor(init) {
134
- super(init);
169
+ super(init, ItdApiErrorKind.PhoneVerification);
135
170
  this.name = "ItdPhoneVerificationError";
136
171
  this.verificationUrl = init.userId ? `https://t.me/itd_verification_bot?start=${encodeURIComponent(init.userId)}` : void 0;
137
172
  }
138
173
  };
139
174
  var ItdServerError = class extends ItdApiError {
140
175
  constructor(init) {
141
- super(init);
176
+ super(init, ItdApiErrorKind.Server);
142
177
  this.name = "ItdServerError";
143
178
  }
144
179
  };
@@ -148,7 +183,7 @@ var ItdNetworkError = class extends ItdError {
148
183
  /** Путь запроса без базового URL. */
149
184
  path;
150
185
  constructor(message, init) {
151
- super("network", message, { cause: init.cause });
186
+ super(ItdErrorKind.Network, message, { cause: init.cause });
152
187
  this.name = "ItdNetworkError";
153
188
  this.method = init.method;
154
189
  this.path = init.path;
@@ -162,7 +197,10 @@ var ItdTimeoutError = class extends ItdError {
162
197
  /** Путь запроса без базового URL. */
163
198
  path;
164
199
  constructor(init) {
165
- super("timeout", `\u0417\u0430\u043F\u0440\u043E\u0441 ${init.method} ${init.path} \u043F\u0440\u0435\u0432\u044B\u0441\u0438\u043B \u0442\u0430\u0439\u043C\u0430\u0443\u0442 ${init.timeout} \u043C\u0441`);
200
+ super(
201
+ ItdErrorKind.Timeout,
202
+ `\u0417\u0430\u043F\u0440\u043E\u0441 ${init.method} ${init.path} \u043F\u0440\u0435\u0432\u044B\u0441\u0438\u043B \u0442\u0430\u0439\u043C\u0430\u0443\u0442 ${init.timeout} \u043C\u0441`
203
+ );
166
204
  this.name = "ItdTimeoutError";
167
205
  this.timeout = init.timeout;
168
206
  this.method = init.method;
@@ -171,13 +209,13 @@ var ItdTimeoutError = class extends ItdError {
171
209
  };
172
210
  var ItdAbortError = class extends ItdError {
173
211
  constructor(message = "\u0417\u0430\u043F\u0440\u043E\u0441 \u043E\u0442\u043C\u0435\u043D\u0451\u043D") {
174
- super("abort", message);
212
+ super(ItdErrorKind.Abort, message);
175
213
  this.name = "ItdAbortError";
176
214
  }
177
215
  };
178
216
  var ItdConfigError = class extends ItdError {
179
217
  constructor(message) {
180
- super("config", message);
218
+ super(ItdErrorKind.Config, message);
181
219
  this.name = "ItdConfigError";
182
220
  }
183
221
  };
@@ -185,16 +223,34 @@ function isItdError(value) {
185
223
  return typeof value === "object" && value !== null && ITD_ERROR in value;
186
224
  }
187
225
  function isItdApiError(value) {
188
- return isItdError(value) && value.kind === "api";
226
+ return isItdError(value) && value.kind === ItdErrorKind.Api;
227
+ }
228
+ function hasApiKind(value, kind) {
229
+ return isItdApiError(value) && value.apiKind === kind;
189
230
  }
190
231
  function isItdValidationError(value) {
191
- return isItdApiError(value) && value instanceof ItdValidationError;
232
+ return hasApiKind(value, ItdApiErrorKind.Validation);
192
233
  }
193
234
  function isItdAuthError(value) {
194
- return isItdApiError(value) && value instanceof ItdAuthError;
235
+ return hasApiKind(value, ItdApiErrorKind.Auth);
236
+ }
237
+ function isItdForbiddenError(value) {
238
+ return hasApiKind(value, ItdApiErrorKind.Forbidden);
239
+ }
240
+ function isItdNotFoundError(value) {
241
+ return hasApiKind(value, ItdApiErrorKind.NotFound);
242
+ }
243
+ function isItdConflictError(value) {
244
+ return hasApiKind(value, ItdApiErrorKind.Conflict);
195
245
  }
196
246
  function isItdRateLimitError(value) {
197
- return isItdApiError(value) && value instanceof ItdRateLimitError;
247
+ return hasApiKind(value, ItdApiErrorKind.RateLimit);
248
+ }
249
+ function isItdPhoneVerificationError(value) {
250
+ return hasApiKind(value, ItdApiErrorKind.PhoneVerification);
251
+ }
252
+ function isItdServerError(value) {
253
+ return hasApiKind(value, ItdApiErrorKind.Server);
198
254
  }
199
255
 
200
256
  // src/builders/comment.ts
@@ -624,13 +680,14 @@ var ItdErrorCode = Object.freeze({
624
680
 
625
681
  // src/builders/report.ts
626
682
  var REASONS = new Set(Object.values(ReportReason));
683
+ var TARGET_TYPES = new Set(Object.values(ReportTargetType));
627
684
  function validateReport(input) {
628
685
  if (!input?.targetId || typeof input.targetId !== "string") {
629
686
  throw new ItdConfigError("\u0416\u0430\u043B\u043E\u0431\u0430 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043E\u0431\u044A\u0435\u043A\u0442\u0430 (targetId)");
630
687
  }
631
- if (input.targetType !== "post" && input.targetType !== "comment" && input.targetType !== "user") {
688
+ if (!TARGET_TYPES.has(input.targetType)) {
632
689
  throw new ItdConfigError(
633
- `targetType \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C 'post', 'comment' \u0438\u043B\u0438 'user', \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: ${String(input.targetType)}`
690
+ `targetType \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043E\u0434\u043D\u0438\u043C \u0438\u0437 ${[...TARGET_TYPES].join(", ")}, \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: ${String(input.targetType)}`
634
691
  );
635
692
  }
636
693
  if (!REASONS.has(input.reason)) {
@@ -668,11 +725,11 @@ function start(targetType, targetId) {
668
725
  }
669
726
  var report = Object.freeze({
670
727
  /** Жалоба на пост. */
671
- post: (postId) => start("post", postId),
728
+ post: (postId) => start(ReportTargetType.Post, postId),
672
729
  /** Жалоба на комментарий. */
673
- comment: (commentId) => start("comment", commentId),
730
+ comment: (commentId) => start(ReportTargetType.Comment, commentId),
674
731
  /** Жалоба на пользователя. */
675
- user: (userId) => start("user", userId)
732
+ user: (userId) => start(ReportTargetType.User, userId)
676
733
  });
677
734
  function resolveReport(input) {
678
735
  return resolveInput(input, () => new ReportBuilder({}), validateReport);
@@ -1101,21 +1158,35 @@ var Emitter = class {
1101
1158
  };
1102
1159
 
1103
1160
  // src/core/runtime.ts
1161
+ var RuntimeMode = Object.freeze({
1162
+ /** Определяется по среде исполнения. Значение по умолчанию. */
1163
+ Auto: "auto",
1164
+ /** Cookie ведёт браузер, запросы уходят с `credentials: 'include'`. */
1165
+ Browser: "browser",
1166
+ /** Cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную. */
1167
+ Server: "server"
1168
+ });
1169
+ var DetectedRuntime = Object.freeze({
1170
+ Browser: "browser",
1171
+ /** Есть `window`, но нет `document`; cookie ведёт нативный сетевой слой. */
1172
+ ReactNative: "react-native",
1173
+ Server: "server"
1174
+ });
1104
1175
  function detectRuntime() {
1105
1176
  const nav = globalThis.navigator;
1106
- if (nav?.product === "ReactNative") return "react-native";
1107
- if (typeof document !== "undefined") return "browser";
1108
- return "server";
1177
+ if (nav?.product === "ReactNative") return DetectedRuntime.ReactNative;
1178
+ if (typeof document !== "undefined") return DetectedRuntime.Browser;
1179
+ return DetectedRuntime.Server;
1109
1180
  }
1110
1181
  function shouldUseCookieJar(mode) {
1111
- if (mode === "browser") return false;
1112
- if (mode === "server") return true;
1113
- return detectRuntime() === "server";
1182
+ if (mode === RuntimeMode.Browser) return false;
1183
+ if (mode === RuntimeMode.Server) return true;
1184
+ return detectRuntime() === DetectedRuntime.Server;
1114
1185
  }
1115
1186
  function shouldSendCredentials(mode) {
1116
- if (mode === "browser") return true;
1117
- if (mode === "server") return false;
1118
- return detectRuntime() === "browser";
1187
+ if (mode === RuntimeMode.Browser) return true;
1188
+ if (mode === RuntimeMode.Server) return false;
1189
+ return detectRuntime() === DetectedRuntime.Browser;
1119
1190
  }
1120
1191
  function resolveFetch(custom) {
1121
1192
  if (custom) return custom;
@@ -1126,6 +1197,12 @@ function resolveFetch(custom) {
1126
1197
  "\u0412 \u044D\u0442\u043E\u0439 \u0441\u0440\u0435\u0434\u0435 \u043D\u0435\u0442 \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u043E\u0433\u043E fetch. \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u0435\u0441\u044C \u0434\u043E Node 18+ \u043B\u0438\u0431\u043E \u043F\u0435\u0440\u0435\u0434\u0430\u0439\u0442\u0435 \u0441\u0432\u043E\u044E \u0440\u0435\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u044E \u0447\u0435\u0440\u0435\u0437 \u043E\u043F\u0446\u0438\u044E fetch."
1127
1198
  );
1128
1199
  }
1200
+ function isBlob(value) {
1201
+ return typeof Blob !== "undefined" && value instanceof Blob;
1202
+ }
1203
+ function isFile(value) {
1204
+ return typeof File !== "undefined" && value instanceof File;
1205
+ }
1129
1206
  function supportsStreamingBody() {
1130
1207
  return typeof ReadableStream !== "undefined" && typeof TextDecoder !== "undefined";
1131
1208
  }
@@ -1200,11 +1277,19 @@ var AuthManager = class {
1200
1277
  /**
1201
1278
  * Есть ли признак живой refresh-сессии.
1202
1279
  *
1203
- * Сайт итд.com ставит рядом с refresh-токеном незакрытую cookie `is_auth` — по ней клиент
1204
- * понимает, что обновление вообще имеет смысл, и не дёргает API у анонимов.
1280
+ * Рядом с refresh-токеном сервер ставит незакрытую cookie `is_auth` — по ней видно,
1281
+ * что продлевать сессию вообще есть смысл, и API не дёргается у анонимов.
1205
1282
  * В браузере cookie ведёт сама среда, поэтому там ответ всегда `true`.
1283
+ *
1284
+ * Асинхронный, потому что признак может лежать в {@link TokenStorage}: до чтения оттуда
1285
+ * ответ был бы `false` даже при полностью рабочей сохранённой сессии.
1206
1286
  */
1207
- hasRefreshSession() {
1287
+ async hasRefreshSession() {
1288
+ await this.#loadSession();
1289
+ return this.#hasRefreshSession();
1290
+ }
1291
+ /** То же самое, но без чтения хранилища — для вызовов, где сессия уже загружена. */
1292
+ #hasRefreshSession() {
1208
1293
  if (!this.#config.useCookieJar) return true;
1209
1294
  if (this.#jar.has(AUTH_FLAG_COOKIE)) return true;
1210
1295
  return Boolean(this.#session?.refreshToken);
@@ -1390,7 +1475,7 @@ var AuthManager = class {
1390
1475
  }
1391
1476
  async #performRefresh() {
1392
1477
  await this.#loadSession();
1393
- if (!this.hasRefreshSession()) {
1478
+ if (!this.#hasRefreshSession()) {
1394
1479
  return this.#reloginOrNull();
1395
1480
  }
1396
1481
  try {
@@ -1401,7 +1486,10 @@ var AuthManager = class {
1401
1486
  // #seedRefreshCookie. По той же причине не нужен и устаревший Bearer.
1402
1487
  skipAuth: true,
1403
1488
  // Без этого 401 на самом обновлении вызвал бы новое обновление — и так по кругу.
1404
- skipAuthRefresh: true
1489
+ skipAuthRefresh: true,
1490
+ // Обновление почти всегда запускается изнутри запроса, который занимает место
1491
+ // в очереди и ждёт его результата. Встать в ту же очередь — значит зависнуть.
1492
+ skipQueue: true
1405
1493
  });
1406
1494
  const accessToken = readAccessToken(payload);
1407
1495
  if (!accessToken) return this.#reloginOrNull();
@@ -1478,7 +1566,10 @@ var AuthManager = class {
1478
1566
  path: AUTH_PATHS.signIn,
1479
1567
  body: { email: credentials.email, password: credentials.password, turnstileToken },
1480
1568
  skipAuth: true,
1481
- skipAuthRefresh: true
1569
+ skipAuthRefresh: true,
1570
+ // Отложенный вход происходит при сборке заголовков уже начатого запроса — тот держит
1571
+ // место в очереди и ждёт токена. См. `skipQueue` в RawRequestOptions.
1572
+ skipQueue: true
1482
1573
  });
1483
1574
  const accessToken = readAccessToken(payload);
1484
1575
  if (!accessToken) {
@@ -1607,7 +1698,7 @@ function normalizeBaseUrl(baseUrl) {
1607
1698
  // src/core/config.ts
1608
1699
  var DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
1609
1700
  var DEFAULT_TIMEOUT = 3e4;
1610
- var LIBRARY_VERSION = "0.0.2";
1701
+ var LIBRARY_VERSION = "0.0.4";
1611
1702
  var DEFAULT_USER_AGENT = `Mozilla/5.0 (compatible; itd-api/${LIBRARY_VERSION}; +https://github.com/KiowDev/itd-api)`;
1612
1703
  var DEFAULT_RATE_LIMIT_DELAYS = Object.freeze([1e3, 5e3, 3e4, 6e4, 9e4]);
1613
1704
  function requirePositive(value, name) {
@@ -1720,9 +1811,11 @@ function validateAuth(auth) {
1720
1811
  );
1721
1812
  }
1722
1813
  function resolveConfig(options = {}) {
1723
- const mode = options.mode ?? "auto";
1724
- if (mode !== "auto" && mode !== "browser" && mode !== "server") {
1725
- throw new ItdConfigError(`mode \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C 'auto', 'browser' \u0438\u043B\u0438 'server', \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: ${mode}`);
1814
+ const mode = options.mode ?? RuntimeMode.Auto;
1815
+ if (!Object.values(RuntimeMode).includes(mode)) {
1816
+ throw new ItdConfigError(
1817
+ `mode \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043E\u0434\u043D\u0438\u043C \u0438\u0437 ${Object.values(RuntimeMode).join(", ")}, \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: ${mode}`
1818
+ );
1726
1819
  }
1727
1820
  const timeout = requirePositive(options.timeout ?? DEFAULT_TIMEOUT, "timeout");
1728
1821
  if (options.deviceId !== void 0 && (typeof options.deviceId !== "string" || options.deviceId.trim() === "")) {
@@ -1783,7 +1876,7 @@ function redactHeaders(headers) {
1783
1876
  function redactBody(body) {
1784
1877
  if (body === null || body === void 0) return body;
1785
1878
  if (typeof FormData !== "undefined" && body instanceof FormData) return "[FormData]";
1786
- if (typeof Blob !== "undefined" && body instanceof Blob) return "[Blob]";
1879
+ if (isBlob(body)) return "[Blob]";
1787
1880
  if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return "[binary]";
1788
1881
  if (Array.isArray(body)) return body.map(redactBody);
1789
1882
  if (typeof body === "object") {
@@ -1796,13 +1889,47 @@ function redactBody(body) {
1796
1889
  return body;
1797
1890
  }
1798
1891
 
1799
- // src/core/error-factory.ts
1892
+ // src/core/unwrap.ts
1893
+ function unwrapData(body) {
1894
+ if (typeof body !== "object" || body === null || Array.isArray(body)) return body;
1895
+ const keys = Object.keys(body);
1896
+ if (keys.length !== 1 || keys[0] !== "data") return body;
1897
+ return body.data;
1898
+ }
1800
1899
  function isRecord(value) {
1801
1900
  return typeof value === "object" && value !== null && !Array.isArray(value);
1802
1901
  }
1803
1902
  function asString(value) {
1804
1903
  return typeof value === "string" && value.length > 0 ? value : void 0;
1805
1904
  }
1905
+ function pickArray(source, field) {
1906
+ if (typeof source !== "object" || source === null) return [];
1907
+ const value = source[field];
1908
+ return Array.isArray(value) ? value : [];
1909
+ }
1910
+ function pickObject(source, field) {
1911
+ if (typeof source !== "object" || source === null) return void 0;
1912
+ const value = source[field];
1913
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
1914
+ return value;
1915
+ }
1916
+ function pickBoolean(source, field, fallback = false) {
1917
+ if (typeof source !== "object" || source === null) return fallback;
1918
+ const value = source[field];
1919
+ return typeof value === "boolean" ? value : fallback;
1920
+ }
1921
+ function pickNumber(source, field, fallback) {
1922
+ if (typeof source !== "object" || source === null) return fallback;
1923
+ const value = source[field];
1924
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
1925
+ }
1926
+ function pickString(source, field) {
1927
+ if (typeof source !== "object" || source === null) return void 0;
1928
+ const value = source[field];
1929
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1930
+ }
1931
+
1932
+ // src/core/error-factory.ts
1806
1933
  function collectFieldErrors(source) {
1807
1934
  const result = {};
1808
1935
  const errors = source.errors;
@@ -1962,40 +2089,6 @@ function createApiError(context) {
1962
2089
  return new Ctor(init);
1963
2090
  }
1964
2091
 
1965
- // src/core/unwrap.ts
1966
- function unwrapData(body) {
1967
- if (typeof body !== "object" || body === null || Array.isArray(body)) return body;
1968
- const keys = Object.keys(body);
1969
- if (keys.length !== 1 || keys[0] !== "data") return body;
1970
- return body.data;
1971
- }
1972
- function pickArray(source, field) {
1973
- if (typeof source !== "object" || source === null) return [];
1974
- const value = source[field];
1975
- return Array.isArray(value) ? value : [];
1976
- }
1977
- function pickObject(source, field) {
1978
- if (typeof source !== "object" || source === null) return void 0;
1979
- const value = source[field];
1980
- if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
1981
- return value;
1982
- }
1983
- function pickBoolean(source, field, fallback = false) {
1984
- if (typeof source !== "object" || source === null) return fallback;
1985
- const value = source[field];
1986
- return typeof value === "boolean" ? value : fallback;
1987
- }
1988
- function pickNumber(source, field, fallback) {
1989
- if (typeof source !== "object" || source === null) return fallback;
1990
- const value = source[field];
1991
- return typeof value === "number" && Number.isFinite(value) ? value : fallback;
1992
- }
1993
- function pickString(source, field) {
1994
- if (typeof source !== "object" || source === null) return void 0;
1995
- const value = source[field];
1996
- return typeof value === "string" && value.length > 0 ? value : void 0;
1997
- }
1998
-
1999
2092
  // src/core/http.ts
2000
2093
  function sleep(ms) {
2001
2094
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -2011,7 +2104,7 @@ function setHeader(headers, name, value) {
2011
2104
  }
2012
2105
  function isRawBody(body) {
2013
2106
  if (typeof body !== "object" || body === null) return typeof body === "string";
2014
- return typeof FormData !== "undefined" && body instanceof FormData || typeof Blob !== "undefined" && body instanceof Blob || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams || typeof ReadableStream !== "undefined" && body instanceof ReadableStream || body instanceof ArrayBuffer || ArrayBuffer.isView(body);
2107
+ return typeof FormData !== "undefined" && body instanceof FormData || isBlob(body) || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams || typeof ReadableStream !== "undefined" && body instanceof ReadableStream || body instanceof ArrayBuffer || ArrayBuffer.isView(body);
2015
2108
  }
2016
2109
  async function readBody(response) {
2017
2110
  if (response.status === 204 || response.status === 205) return void 0;
@@ -2081,7 +2174,8 @@ var HttpClient = class {
2081
2174
  */
2082
2175
  async request(options) {
2083
2176
  const task = () => this.#withRetries(options);
2084
- return this.#collaborators.schedule ? this.#collaborators.schedule(task) : task();
2177
+ if (!this.#collaborators.schedule || options.skipQueue) return task();
2178
+ return this.#collaborators.schedule(task);
2085
2179
  }
2086
2180
  async #withRetries(options) {
2087
2181
  const method = options.method.toUpperCase();
@@ -2350,21 +2444,15 @@ function isKnownNotificationType(type) {
2350
2444
  }
2351
2445
 
2352
2446
  // src/notifications/normalize.ts
2353
- function isRecord2(value) {
2354
- return typeof value === "object" && value !== null && !Array.isArray(value);
2355
- }
2356
- function asString2(value) {
2357
- return typeof value === "string" && value.length > 0 ? value : void 0;
2358
- }
2359
2447
  function asActor(value) {
2360
- if (!isRecord2(value)) return void 0;
2361
- const id = asString2(value.id);
2448
+ if (!isRecord(value)) return void 0;
2449
+ const id = asString(value.id);
2362
2450
  if (!id) return void 0;
2363
2451
  return {
2364
2452
  id,
2365
- username: asString2(value.username) ?? "",
2366
- displayName: asString2(value.displayName) ?? "",
2367
- avatar: asString2(value.avatar) ?? "",
2453
+ username: asString(value.username) ?? "",
2454
+ displayName: asString(value.displayName) ?? "",
2455
+ avatar: asString(value.avatar) ?? "",
2368
2456
  ...typeof value.isFollowing === "boolean" ? { isFollowing: value.isFollowing } : {},
2369
2457
  ...typeof value.isFollowedBy === "boolean" ? { isFollowedBy: value.isFollowedBy } : {}
2370
2458
  };
@@ -2377,33 +2465,34 @@ function readActors(source) {
2377
2465
  return single ? [single] : [];
2378
2466
  }
2379
2467
  function normalizeNotification(input) {
2380
- const source = isRecord2(input) ? input : {};
2381
- const payload = isRecord2(source.payload) ? source.payload : source;
2382
- const rawType = asString2(payload.type) ?? asString2(source.type) ?? "";
2383
- const createdAt = asString2(payload.createdAt) ?? asString2(source.createdAt) ?? "";
2384
- const readAt = asString2(payload.readAt) ?? asString2(source.readAt);
2468
+ const source = isRecord(input) ? input : {};
2469
+ const payload = isRecord(source.payload) ? source.payload : source;
2470
+ const rawType = asString(payload.type) ?? asString(source.type) ?? "";
2471
+ const createdAt = asString(payload.createdAt) ?? asString(source.createdAt) ?? "";
2472
+ const readAt = asString(payload.readAt) ?? asString(source.readAt);
2385
2473
  const isRead = typeof payload.isRead === "boolean" ? payload.isRead : typeof payload.read === "boolean" ? payload.read : Boolean(readAt);
2386
- const subjectId = asString2(payload.subjectId);
2387
- const targetId = asString2(payload.targetId);
2474
+ const subjectId = asString(payload.subjectId);
2475
+ const targetId = asString(payload.targetId);
2388
2476
  const subjectIsComment = payload.subjectType === "comment";
2477
+ const clickUrl = asString(payload.clickUrl);
2389
2478
  return {
2390
- id: asString2(payload.id) ?? asString2(source.id) ?? "",
2479
+ id: asString(payload.id) ?? asString(source.id) ?? "",
2391
2480
  type: canonicalNotificationType(rawType),
2392
2481
  rawType,
2393
- entityId: asString2(payload.entityId) ?? (subjectIsComment ? subjectId ?? targetId : targetId) ?? null,
2394
- parentEntityId: asString2(payload.parentEntityId) ?? (subjectIsComment ? targetId ?? null : null),
2482
+ entityId: asString(payload.entityId) ?? (subjectIsComment ? subjectId ?? targetId : targetId) ?? null,
2483
+ parentEntityId: asString(payload.parentEntityId) ?? (subjectIsComment ? targetId ?? null : null),
2395
2484
  isRead,
2396
2485
  actors: readActors(payload),
2397
2486
  count: typeof payload.count === "number" && payload.count > 0 ? payload.count : 1,
2398
- preview: asString2(payload.entityPreview) ?? asString2(payload.preview) ?? null,
2399
- ...asString2(payload.clickUrl) ? { clickUrl: asString2(payload.clickUrl) } : {},
2487
+ preview: asString(payload.entityPreview) ?? asString(payload.preview) ?? null,
2488
+ ...clickUrl ? { clickUrl } : {},
2400
2489
  createdAt,
2401
- updatedAt: asString2(payload.updatedAt) ?? readAt ?? createdAt,
2490
+ updatedAt: asString(payload.updatedAt) ?? readAt ?? createdAt,
2402
2491
  raw: input
2403
2492
  };
2404
2493
  }
2405
2494
  function readNotificationEvent(data) {
2406
- const source = isRecord2(data) ? data : {};
2495
+ const source = isRecord(data) ? data : {};
2407
2496
  return {
2408
2497
  notification: normalizeNotification(data),
2409
2498
  unreadCount: typeof source.unreadCount === "number" ? source.unreadCount : void 0,
@@ -2411,8 +2500,8 @@ function readNotificationEvent(data) {
2411
2500
  };
2412
2501
  }
2413
2502
  function readUnreadCountEvent(data) {
2414
- if (!isRecord2(data)) return void 0;
2415
- const payload = isRecord2(data.payload) ? data.payload : void 0;
2503
+ if (!isRecord(data)) return void 0;
2504
+ const payload = isRecord(data.payload) ? data.payload : void 0;
2416
2505
  if (!payload) return void 0;
2417
2506
  return typeof payload.count === "number" ? payload.count : void 0;
2418
2507
  }
@@ -2491,6 +2580,7 @@ var PollTransport = class {
2491
2580
  }
2492
2581
  /** Ждёт следующего опроса, прерываясь при отмене. */
2493
2582
  #wait(signal) {
2583
+ if (signal.aborted) return Promise.resolve();
2494
2584
  return new Promise((resolve) => {
2495
2585
  const timer = setTimeout(finish, this.#interval);
2496
2586
  function finish() {
@@ -2763,6 +2853,14 @@ var SseTransport = class {
2763
2853
  };
2764
2854
 
2765
2855
  // src/realtime/stream.ts
2856
+ var RealtimeTransportKind = Object.freeze({
2857
+ /** Поток событий, если среда умеет читать тело по частям, иначе опрос. */
2858
+ Auto: "auto",
2859
+ /** Поток `text/event-stream`. */
2860
+ Sse: "sse",
2861
+ /** Периодический опрос REST. */
2862
+ Poll: "poll"
2863
+ });
2766
2864
  var ItdRealtime = class {
2767
2865
  #deps;
2768
2866
  #options;
@@ -2770,6 +2868,15 @@ var ItdRealtime = class {
2770
2868
  #transport;
2771
2869
  #maxAttempts;
2772
2870
  #controller;
2871
+ /**
2872
+ * Хочет ли вызывающий код, чтобы соединение было живо.
2873
+ *
2874
+ * Отдельно от `#controller`, потому что тот появляется только после `await` внутри
2875
+ * {@link connect}. Без этого флага два вызова подряд проскочили бы проверку оба
2876
+ * и подняли два соединения, а `disconnect()` во время ожидания счётчика не был бы
2877
+ * замечен и соединение поднялось бы уже после отмены.
2878
+ */
2879
+ #wanted = false;
2773
2880
  #status = RealtimeStatus.Disconnected;
2774
2881
  #attempt = 0;
2775
2882
  #timer;
@@ -2805,7 +2912,8 @@ var ItdRealtime = class {
2805
2912
  * Возвращает управление сразу после запуска: соединение живёт в фоне.
2806
2913
  */
2807
2914
  async connect() {
2808
- if (this.#controller) return;
2915
+ if (this.#wanted) return;
2916
+ this.#wanted = true;
2809
2917
  this.#attachEnvironmentListeners();
2810
2918
  if (this.#options.syncCount !== false) {
2811
2919
  try {
@@ -2814,10 +2922,11 @@ var ItdRealtime = class {
2814
2922
  this.#deps.logger?.debug("\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u0447\u0438\u0441\u043B\u043E \u043D\u0435\u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u0445", error);
2815
2923
  }
2816
2924
  }
2817
- this.#run();
2925
+ if (this.#wanted) this.#run();
2818
2926
  }
2819
2927
  /** Закрывает соединение и отменяет запланированные попытки. */
2820
2928
  disconnect() {
2929
+ this.#wanted = false;
2821
2930
  if (this.#timer !== void 0) {
2822
2931
  clearTimeout(this.#timer);
2823
2932
  this.#timer = void 0;
@@ -2834,9 +2943,9 @@ var ItdRealtime = class {
2834
2943
  this.#emitter.removeAllListeners();
2835
2944
  }
2836
2945
  #createTransport() {
2837
- const kind = this.#options.transport ?? "auto";
2946
+ const kind = this.#options.transport ?? RealtimeTransportKind.Auto;
2838
2947
  if (typeof kind === "object") return kind;
2839
- if (kind === "poll" || kind === "auto" && !supportsStreamingBody()) {
2948
+ if (kind === RealtimeTransportKind.Poll || kind === RealtimeTransportKind.Auto && !supportsStreamingBody()) {
2840
2949
  return new PollTransport({
2841
2950
  ...this.#options.pollInterval !== void 0 ? { interval: this.#options.pollInterval } : {}
2842
2951
  });
@@ -2847,6 +2956,7 @@ var ItdRealtime = class {
2847
2956
  }
2848
2957
  /** Запускает попытку подключения; повторы планирует сам. */
2849
2958
  #run() {
2959
+ this.#controller?.abort();
2850
2960
  const controller = new AbortController();
2851
2961
  this.#controller = controller;
2852
2962
  this.#setStatus(RealtimeStatus.Connecting);
@@ -2876,8 +2986,7 @@ var ItdRealtime = class {
2876
2986
  #handleEvent(name, data) {
2877
2987
  this.#emitter.emit("message", { name, data });
2878
2988
  if (name === "connected") {
2879
- const userId = typeof data === "object" && data !== null && "userId" in data ? String(data.userId) : void 0;
2880
- this.#emitter.emit("ready", { userId });
2989
+ this.#emitter.emit("ready", { userId: pickString(data, "userId") });
2881
2990
  return;
2882
2991
  }
2883
2992
  if (name === "notification") {
@@ -2965,13 +3074,22 @@ var ItdRealtime = class {
2965
3074
  };
2966
3075
 
2967
3076
  // src/core/pagination.ts
3077
+ var PaginationMode = Object.freeze({
3078
+ /** Следующая страница запрашивается непрозрачным курсором. */
3079
+ Cursor: "cursor",
3080
+ /** Следующая страница запрашивается номером. */
3081
+ Page: "page",
3082
+ /** Следующая страница запрашивается смещением от начала списка. */
3083
+ Offset: "offset"
3084
+ });
2968
3085
  function readItems(body, fields) {
2969
3086
  if (Array.isArray(body)) return body;
2970
3087
  for (const field of fields) {
2971
3088
  const items = pickArray(body, field);
2972
3089
  if (items.length > 0) return items;
2973
3090
  }
2974
- return fields.length > 0 ? pickArray(body, fields[0]) : [];
3091
+ const primary = fields[0];
3092
+ return primary === void 0 ? [] : pickArray(body, primary);
2975
3093
  }
2976
3094
  function readCursor(body) {
2977
3095
  const pagination = pickObject(body, "pagination");
@@ -3030,12 +3148,13 @@ function readOffsetPage(body, field, offset) {
3030
3148
  var Paginator = class {
3031
3149
  #options;
3032
3150
  #maxPages;
3033
- #state = {};
3151
+ #state;
3034
3152
  #finished = false;
3035
3153
  #pagesLoaded = 0;
3036
3154
  constructor(options) {
3037
3155
  this.#options = options;
3038
3156
  this.#maxPages = options.maxPages ?? 1e3;
3157
+ this.#state = options.start ?? {};
3039
3158
  }
3040
3159
  /**
3041
3160
  * Загружает следующую страницу.
@@ -3101,7 +3220,7 @@ var Paginator = class {
3101
3220
  this.#finished = true;
3102
3221
  return previous;
3103
3222
  }
3104
- if (this.#options.mode === "cursor") {
3223
+ if (this.#options.mode === PaginationMode.Cursor) {
3105
3224
  const cursor = page.nextCursor ?? void 0;
3106
3225
  if (!cursor || cursor === previous.cursor) {
3107
3226
  this.#finished = true;
@@ -3109,7 +3228,7 @@ var Paginator = class {
3109
3228
  }
3110
3229
  return { cursor };
3111
3230
  }
3112
- if (this.#options.mode === "page") {
3231
+ if (this.#options.mode === PaginationMode.Page) {
3113
3232
  return { page: (previous.page ?? 1) + 1 };
3114
3233
  }
3115
3234
  return { offset: page.nextOffset ?? (previous.offset ?? 0) + page.items.length };
@@ -3138,13 +3257,15 @@ var BaseResource = class {
3138
3257
  *
3139
3258
  * @param mode схема пагинации эндпоинта
3140
3259
  * @param load загружает одну страницу для указанной позиции
3260
+ * @param options `maxPages` и `signal`, а также `start` — позиция, с которой продолжить
3141
3261
  */
3142
3262
  paginate(mode, load, options) {
3143
3263
  return new Paginator({
3144
3264
  mode,
3145
3265
  load,
3146
3266
  ...options?.maxPages !== void 0 ? { maxPages: options.maxPages } : {},
3147
- ...options?.signal !== void 0 ? { signal: options.signal } : {}
3267
+ ...options?.signal !== void 0 ? { signal: options.signal } : {},
3268
+ ...options?.start !== void 0 ? { start: options.start } : {}
3148
3269
  });
3149
3270
  }
3150
3271
  };
@@ -3158,6 +3279,14 @@ function withPageState(query, state) {
3158
3279
  }
3159
3280
 
3160
3281
  // src/resources/auth.ts
3282
+ var OAuthProvider = Object.freeze({
3283
+ Yandex: "yandex",
3284
+ Google: "google"
3285
+ });
3286
+ var SignInStatus = Object.freeze({
3287
+ Authenticated: "authenticated",
3288
+ OtpRequired: "otp_required"
3289
+ });
3161
3290
  var AuthResource = class extends BaseResource {
3162
3291
  #auth;
3163
3292
  constructor(http, deps) {
@@ -3206,9 +3335,9 @@ var AuthResource = class extends BaseResource {
3206
3335
  const accessToken = pickString(body, "accessToken");
3207
3336
  if (accessToken) {
3208
3337
  await this.#auth.setAccessToken(accessToken);
3209
- return { status: "authenticated", accessToken };
3338
+ return { status: SignInStatus.Authenticated, accessToken };
3210
3339
  }
3211
- return { status: "otp_required", flowToken: pickString(body, "flowToken") };
3340
+ return { status: SignInStatus.OtpRequired, flowToken: pickString(body, "flowToken") };
3212
3341
  }
3213
3342
  /**
3214
3343
  * Подтверждает вход кодом из письма.
@@ -3263,7 +3392,7 @@ var AuthResource = class extends BaseResource {
3263
3392
  async signInWithOtp(input, options = {}) {
3264
3393
  const { getOtp, ...credentials } = input;
3265
3394
  const result = await this.signIn(credentials, options);
3266
- if (result.status === "authenticated") return result.accessToken;
3395
+ if (result.status === SignInStatus.Authenticated) return result.accessToken;
3267
3396
  if (!result.flowToken) {
3268
3397
  throw new ItdConfigError(
3269
3398
  "\u0421\u0435\u0440\u0432\u0435\u0440 \u0437\u0430\u043F\u0440\u043E\u0441\u0438\u043B \u043A\u043E\u0434 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F, \u043D\u043E \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B flowToken \u2014 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0432\u0445\u043E\u0434 \u043D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E"
@@ -3292,9 +3421,18 @@ var AuthResource = class extends BaseResource {
3292
3421
  /**
3293
3422
  * Есть ли признак живой сессии обновления.
3294
3423
  *
3295
- * Проверяет cookie `is_auth`, которую сервер ставит рядом с refresh-токеном. Позволяет
3296
- * не дёргать API у неавторизованного пользователя. В браузере всегда `true`:
3297
- * cookie ведёт сама среда, и прочитать её из JS нельзя.
3424
+ * Проверяет cookie `is_auth`, которую сервер ставит рядом с refresh-токеном, а также
3425
+ * refresh-токен, переданный строкой. Позволяет не дёргать API у неавторизованного
3426
+ * пользователя. В браузере всегда `true`: cookie ведёт сама среда, и прочитать её
3427
+ * из JS нельзя.
3428
+ *
3429
+ * Читает {@link TokenStorage}, поэтому результат верен и до первого запроса.
3430
+ *
3431
+ * @example
3432
+ * ```ts
3433
+ * if (await itd.auth.hasRefreshSession()) await itd.auth.refresh();
3434
+ * else redirectToLogin();
3435
+ * ```
3298
3436
  */
3299
3437
  hasRefreshSession() {
3300
3438
  return this.#auth.hasRefreshSession();
@@ -3469,7 +3607,7 @@ var CommentsResource = class extends BaseResource {
3469
3607
  iterateReplies(commentId, params = {}) {
3470
3608
  const path = `/api/comments/${encodePathSegment(commentId, "commentId")}/replies`;
3471
3609
  return this.paginate(
3472
- "page",
3610
+ PaginationMode.Page,
3473
3611
  async (state) => {
3474
3612
  const body = await this.http.request({
3475
3613
  method: "GET",
@@ -3479,7 +3617,7 @@ var CommentsResource = class extends BaseResource {
3479
3617
  });
3480
3618
  return readPagedPage(body, "replies");
3481
3619
  },
3482
- params
3620
+ { ...params, ...params.page !== void 0 ? { start: { page: params.page } } : {} }
3483
3621
  );
3484
3622
  }
3485
3623
  /**
@@ -3560,7 +3698,11 @@ var IMAGE_MIME_TYPES = Object.freeze([
3560
3698
  "image/heic",
3561
3699
  "image/heif"
3562
3700
  ]);
3563
- var VIDEO_MIME_TYPES = Object.freeze(["video/mp4", "video/webm", "video/quicktime"]);
3701
+ var VIDEO_MIME_TYPES = Object.freeze([
3702
+ "video/mp4",
3703
+ "video/webm",
3704
+ "video/quicktime"
3705
+ ]);
3564
3706
  var AUDIO_MIME_TYPES = Object.freeze(["audio/ogg"]);
3565
3707
  var ALLOWED_MIME_TYPES = Object.freeze([
3566
3708
  ...IMAGE_MIME_TYPES,
@@ -3639,7 +3781,7 @@ var FilesResource = class extends BaseResource {
3639
3781
  * ```
3640
3782
  */
3641
3783
  async upload(input, options = {}) {
3642
- const prepared = await this.prepare(input, options);
3784
+ const prepared = await this.#prepare(input, options);
3643
3785
  const form = new FormData();
3644
3786
  form.set("file", prepared.blob, prepared.filename);
3645
3787
  return this.http.request({
@@ -3690,11 +3832,11 @@ var FilesResource = class extends BaseResource {
3690
3832
  });
3691
3833
  }
3692
3834
  /** Приводит любой поддерживаемый вход к `Blob` с именем и проверенным типом. */
3693
- async prepare(input, options) {
3835
+ async #prepare(input, options) {
3694
3836
  const { data, filename, contentType } = await this.#normalize(input, options);
3695
- const type = contentType ?? ((data instanceof Blob ? data.type : void 0) || mimeFromFilename(filename));
3837
+ const type = contentType ?? ((isBlob(data) ? data.type : void 0) || mimeFromFilename(filename));
3696
3838
  if (options.validateMime !== false) assertAllowedMime(type || void 0, filename);
3697
- const blob = data instanceof Blob && (!type || data.type === type) ? data : new Blob([data], { type: type ?? "" });
3839
+ const blob = isBlob(data) && (!type || data.type === type) ? data : new Blob([data], { type: type ?? "" });
3698
3840
  return { blob, filename };
3699
3841
  }
3700
3842
  async #normalize(input, options) {
@@ -3711,8 +3853,8 @@ var FilesResource = class extends BaseResource {
3711
3853
  ...options.contentType ? { contentType: options.contentType } : {}
3712
3854
  };
3713
3855
  }
3714
- if (input instanceof ArrayBuffer || ArrayBuffer.isView(input) || input instanceof Blob) {
3715
- const fallbackName = input instanceof File ? input.name : options.filename ?? this.#nameFromMime(options.contentType);
3856
+ if (input instanceof ArrayBuffer || ArrayBuffer.isView(input) || isBlob(input)) {
3857
+ const fallbackName = isFile(input) ? input.name : options.filename ?? this.#nameFromMime(options.contentType);
3716
3858
  return {
3717
3859
  data: input,
3718
3860
  filename: options.filename ?? fallbackName,
@@ -3778,7 +3920,7 @@ var HashtagsResource = class extends BaseResource {
3778
3920
  iteratePosts(tag, params = {}) {
3779
3921
  const path = `/api/hashtags/${encodePathSegment(tag, "tag")}/posts`;
3780
3922
  return this.paginate(
3781
- "cursor",
3923
+ PaginationMode.Cursor,
3782
3924
  async (state) => {
3783
3925
  const body = await this.http.request({
3784
3926
  method: "GET",
@@ -3788,7 +3930,7 @@ var HashtagsResource = class extends BaseResource {
3788
3930
  });
3789
3931
  return readCursorPage(body, "posts");
3790
3932
  },
3791
- params
3933
+ { ...params, ...params.cursor ? { start: { cursor: params.cursor } } : {} }
3792
3934
  );
3793
3935
  }
3794
3936
  };
@@ -4019,8 +4161,11 @@ var NotificationsResource = class extends BaseResource {
4019
4161
  * const next = await itd.notifications.list({ limit: 20, offset: page.nextOffset });
4020
4162
  * ```
4021
4163
  */
4022
- async list(params = {}) {
4023
- const offset = params.offset ?? 0;
4164
+ list(params = {}) {
4165
+ return this.#loadPage(params, params.offset ?? 0);
4166
+ }
4167
+ /** Общая загрузка страницы для {@link list} и {@link iterate}. */
4168
+ async #loadPage(params, offset) {
4024
4169
  const body = await this.http.request({
4025
4170
  method: "GET",
4026
4171
  // Завершающий слэш обязателен: без него сервер отвечает ошибкой.
@@ -4043,19 +4188,9 @@ var NotificationsResource = class extends BaseResource {
4043
4188
  */
4044
4189
  iterate(params = {}) {
4045
4190
  return this.paginate(
4046
- "offset",
4047
- async (state) => {
4048
- const offset = state.offset ?? params.offset ?? 0;
4049
- const body = await this.http.request({
4050
- method: "GET",
4051
- path: "/api/notifications/",
4052
- query: { limit: params.limit, offset },
4053
- ...this.requestOptions(params)
4054
- });
4055
- const page = readOffsetPage(body, "notifications", offset);
4056
- return { ...page, items: page.items.map(normalizeNotification) };
4057
- },
4058
- params
4191
+ PaginationMode.Offset,
4192
+ (state) => this.#loadPage(params, state.offset ?? 0),
4193
+ { ...params, ...params.offset !== void 0 ? { start: { offset: params.offset } } : {} }
4059
4194
  );
4060
4195
  }
4061
4196
  /** Загружает число непрочитанных уведомлений. */
@@ -4187,7 +4322,7 @@ var PostsResource = class extends BaseResource {
4187
4322
  */
4188
4323
  iterate(params = {}) {
4189
4324
  return this.paginate(
4190
- "cursor",
4325
+ PaginationMode.Cursor,
4191
4326
  async (state) => {
4192
4327
  const body = await this.http.request({
4193
4328
  method: "GET",
@@ -4197,7 +4332,7 @@ var PostsResource = class extends BaseResource {
4197
4332
  });
4198
4333
  return readCursorPage(body, "posts");
4199
4334
  },
4200
- params
4335
+ { ...params, ...params.cursor ? { start: { cursor: params.cursor } } : {} }
4201
4336
  );
4202
4337
  }
4203
4338
  /**
@@ -4361,7 +4496,7 @@ var PostsResource = class extends BaseResource {
4361
4496
  iterateByUser(user, params = {}) {
4362
4497
  const path = `/api/posts/user/${encodePathSegment(user, "user")}`;
4363
4498
  return this.paginate(
4364
- "cursor",
4499
+ PaginationMode.Cursor,
4365
4500
  async (state) => {
4366
4501
  const body = await this.http.request({
4367
4502
  method: "GET",
@@ -4374,7 +4509,7 @@ var PostsResource = class extends BaseResource {
4374
4509
  });
4375
4510
  return readCursorPage(body, "posts");
4376
4511
  },
4377
- params
4512
+ { ...params, ...params.cursor ? { start: { cursor: params.cursor } } : {} }
4378
4513
  );
4379
4514
  }
4380
4515
  /** Загружает страницу постов, которые пользователь отметил реакцией. */
@@ -4391,7 +4526,7 @@ var PostsResource = class extends BaseResource {
4391
4526
  iterateLikedByUser(user, params = {}) {
4392
4527
  const path = `/api/posts/user/${encodePathSegment(user, "user")}/liked`;
4393
4528
  return this.paginate(
4394
- "cursor",
4529
+ PaginationMode.Cursor,
4395
4530
  async (state) => {
4396
4531
  const body = await this.http.request({
4397
4532
  method: "GET",
@@ -4401,7 +4536,7 @@ var PostsResource = class extends BaseResource {
4401
4536
  });
4402
4537
  return readCursorPage(body, "posts");
4403
4538
  },
4404
- params
4539
+ { ...params, ...params.cursor ? { start: { cursor: params.cursor } } : {} }
4405
4540
  );
4406
4541
  }
4407
4542
  /**
@@ -4423,7 +4558,7 @@ var PostsResource = class extends BaseResource {
4423
4558
  iterateComments(postId, params = {}) {
4424
4559
  const path = `/api/posts/${encodePathSegment(postId, "postId")}/comments`;
4425
4560
  return this.paginate(
4426
- "cursor",
4561
+ PaginationMode.Cursor,
4427
4562
  async (state) => {
4428
4563
  const body = await this.http.request({
4429
4564
  method: "GET",
@@ -4433,7 +4568,7 @@ var PostsResource = class extends BaseResource {
4433
4568
  });
4434
4569
  return readFlatCursorPage(body, "comments");
4435
4570
  },
4436
- params
4571
+ { ...params, ...params.cursor ? { start: { cursor: params.cursor } } : {} }
4437
4572
  );
4438
4573
  }
4439
4574
  /**
@@ -4711,28 +4846,33 @@ var UsersResource = class extends BaseResource {
4711
4846
  ...this.requestOptions(options)
4712
4847
  });
4713
4848
  }
4714
- async #userPage(path, params) {
4849
+ /**
4850
+ * Загружает одну страницу списка пользователей.
4851
+ *
4852
+ * Имена полей перечислены с запасом: списки подписчиков и заблокированных приходят
4853
+ * под `users`, но альтернативное имя ничего не стоит и спасает, если эндпоинт назовёт
4854
+ * список по-своему.
4855
+ */
4856
+ async #loadUserPage(path, params, state) {
4715
4857
  const body = await this.http.request({
4716
4858
  method: "GET",
4717
4859
  path,
4718
- query: { limit: params.limit, page: params.page },
4860
+ query: withPageState({ limit: params.limit }, state),
4719
4861
  ...this.requestOptions(params)
4720
4862
  });
4721
- return readPagedPage(body, "users");
4863
+ return readPagedPage(body, "users", "followers", "following", "blocked");
4864
+ }
4865
+ #userPage(path, params) {
4866
+ return this.#loadUserPage(path, params, {
4867
+ ...params.page !== void 0 ? { page: params.page } : {}
4868
+ });
4722
4869
  }
4723
4870
  #userPaginator(path, params) {
4724
4871
  return this.paginate(
4725
- "page",
4726
- async (state) => {
4727
- const body = await this.http.request({
4728
- method: "GET",
4729
- path,
4730
- query: withPageState({ limit: params.limit }, state),
4731
- ...this.requestOptions(params)
4732
- });
4733
- return readPagedPage(body, "users");
4734
- },
4735
- params
4872
+ PaginationMode.Page,
4873
+ (state) => this.#loadUserPage(path, params, state),
4874
+ // Без `start` перебор начинался бы с первой страницы, молча игнорируя `page`.
4875
+ { ...params, ...params.page !== void 0 ? { start: { page: params.page } } : {} }
4736
4876
  );
4737
4877
  }
4738
4878
  };
@@ -5061,16 +5201,19 @@ exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
5061
5201
  exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
5062
5202
  exports.DEFAULT_USER_AGENT = DEFAULT_USER_AGENT;
5063
5203
  exports.DEVICE_ID_HEADER = DEVICE_ID_HEADER;
5204
+ exports.DetectedRuntime = DetectedRuntime;
5064
5205
  exports.FeedTab = FeedTab;
5065
5206
  exports.IMAGE_MIME_TYPES = IMAGE_MIME_TYPES;
5066
5207
  exports.ItdAbortError = ItdAbortError;
5067
5208
  exports.ItdApiError = ItdApiError;
5209
+ exports.ItdApiErrorKind = ItdApiErrorKind;
5068
5210
  exports.ItdAuthError = ItdAuthError;
5069
5211
  exports.ItdClient = ItdClient;
5070
5212
  exports.ItdConfigError = ItdConfigError;
5071
5213
  exports.ItdConflictError = ItdConflictError;
5072
5214
  exports.ItdError = ItdError;
5073
5215
  exports.ItdErrorCode = ItdErrorCode;
5216
+ exports.ItdErrorKind = ItdErrorKind;
5074
5217
  exports.ItdForbiddenError = ItdForbiddenError;
5075
5218
  exports.ItdNetworkError = ItdNetworkError;
5076
5219
  exports.ItdNotFoundError = ItdNotFoundError;
@@ -5087,16 +5230,22 @@ exports.MAX_RECONNECT_ATTEMPTS = MAX_RECONNECT_ATTEMPTS;
5087
5230
  exports.MemoryTokenStorage = MemoryTokenStorage;
5088
5231
  exports.NOTIFICATION_TYPE_ALIASES = NOTIFICATION_TYPE_ALIASES;
5089
5232
  exports.NotificationType = NotificationType;
5233
+ exports.OAuthProvider = OAuthProvider;
5234
+ exports.PaginationMode = PaginationMode;
5090
5235
  exports.Paginator = Paginator;
5091
5236
  exports.RECONNECT_BACKOFF = RECONNECT_BACKOFF;
5092
5237
  exports.RECONNECT_JITTER = RECONNECT_JITTER;
5093
5238
  exports.REFRESH_COOKIE = REFRESH_COOKIE;
5094
5239
  exports.REFRESH_COOKIE_PATH = REFRESH_COOKIE_PATH;
5095
5240
  exports.RealtimeStatus = RealtimeStatus;
5241
+ exports.RealtimeTransportKind = RealtimeTransportKind;
5096
5242
  exports.ReportReason = ReportReason;
5097
5243
  exports.ReportTargetType = ReportTargetType;
5244
+ exports.RuntimeMode = RuntimeMode;
5098
5245
  exports.STREAM_PATH = STREAM_PATH;
5246
+ exports.SignInStatus = SignInStatus;
5099
5247
  exports.TURNSTILE_SITE_KEY = TURNSTILE_SITE_KEY;
5248
+ exports.UnauthorizedStreamError = UnauthorizedStreamError;
5100
5249
  exports.VIDEO_MIME_TYPES = VIDEO_MIME_TYPES;
5101
5250
  exports.WallAccess = WallAccess;
5102
5251
  exports.canonicalNotificationType = canonicalNotificationType;
@@ -5107,8 +5256,13 @@ exports.formatNotificationText = formatNotificationText;
5107
5256
  exports.isBuilder = isBuilder;
5108
5257
  exports.isItdApiError = isItdApiError;
5109
5258
  exports.isItdAuthError = isItdAuthError;
5259
+ exports.isItdConflictError = isItdConflictError;
5110
5260
  exports.isItdError = isItdError;
5261
+ exports.isItdForbiddenError = isItdForbiddenError;
5262
+ exports.isItdNotFoundError = isItdNotFoundError;
5263
+ exports.isItdPhoneVerificationError = isItdPhoneVerificationError;
5111
5264
  exports.isItdRateLimitError = isItdRateLimitError;
5265
+ exports.isItdServerError = isItdServerError;
5112
5266
  exports.isItdValidationError = isItdValidationError;
5113
5267
  exports.isKnownNotificationType = isKnownNotificationType;
5114
5268
  exports.isMyProfile = isMyProfile;
@@ -5120,5 +5274,5 @@ exports.readUnreadCountEvent = readUnreadCountEvent;
5120
5274
  exports.report = report;
5121
5275
  exports.resolveNotificationUrl = resolveNotificationUrl;
5122
5276
  exports.toDate = toDate;
5123
- //# sourceMappingURL=chunk-3ODOEKQO.cjs.map
5124
- //# sourceMappingURL=chunk-3ODOEKQO.cjs.map
5277
+ //# sourceMappingURL=chunk-XG43KEYF.cjs.map
5278
+ //# sourceMappingURL=chunk-XG43KEYF.cjs.map