itd-api 0.0.11 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +8 -4
  2. package/dist/{chunk-TB7HW3VX.js → chunk-6FB4HTKH.js} +660 -174
  3. package/dist/chunk-6FB4HTKH.js.map +1 -0
  4. package/dist/{chunk-QD4UHJFF.cjs → chunk-73CISRBG.cjs} +660 -174
  5. package/dist/chunk-73CISRBG.cjs.map +1 -0
  6. package/dist/{index-CrlTO7sR.d.cts → index-BZF4K90s.d.cts} +125 -22
  7. package/dist/{index-CrlTO7sR.d.ts → index-BZF4K90s.d.ts} +125 -22
  8. package/dist/index.cjs +109 -109
  9. package/dist/index.d.cts +1 -1
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/node.cjs +112 -112
  13. package/dist/node.d.cts +2 -2
  14. package/dist/node.d.ts +2 -2
  15. package/dist/node.js +2 -2
  16. package/guides/README.md +2 -0
  17. package/guides/multi-accounts/README.md +2 -1
  18. package/guides/plugins/README.md +93 -2
  19. package/guides/reference/README.md +67 -0
  20. package/guides/reference/accounts.md +101 -0
  21. package/guides/reference/auth.md +141 -0
  22. package/guides/reference/builders.md +135 -0
  23. package/guides/reference/client.md +184 -0
  24. package/guides/reference/comments.md +58 -0
  25. package/guides/reference/discovery.md +81 -0
  26. package/guides/reference/enums.md +103 -0
  27. package/guides/reference/errors.md +107 -0
  28. package/guides/reference/files.md +73 -0
  29. package/guides/reference/models.md +448 -0
  30. package/guides/reference/notifications.md +77 -0
  31. package/guides/reference/pagination.md +82 -0
  32. package/guides/reference/platform.md +47 -0
  33. package/guides/reference/posts.md +157 -0
  34. package/guides/reference/realtime.md +78 -0
  35. package/guides/reference/reports.md +28 -0
  36. package/guides/reference/subscription.md +41 -0
  37. package/guides/reference/users.md +146 -0
  38. package/guides/reference/verification.md +24 -0
  39. package/package.json +1 -1
  40. package/dist/chunk-QD4UHJFF.cjs.map +0 -1
  41. package/dist/chunk-TB7HW3VX.js.map +0 -1
@@ -619,8 +619,8 @@ var ItdTimeoutError = class extends ItdError {
619
619
  }
620
620
  };
621
621
  var ItdAbortError = class extends ItdError {
622
- constructor(message = "\u0417\u0430\u043F\u0440\u043E\u0441 \u043E\u0442\u043C\u0435\u043D\u0451\u043D") {
623
- super(ItdErrorKind.Abort, message);
622
+ constructor(message = "\u0417\u0430\u043F\u0440\u043E\u0441 \u043E\u0442\u043C\u0435\u043D\u0451\u043D", options) {
623
+ super(ItdErrorKind.Abort, message, options);
624
624
  this.name = "ItdAbortError";
625
625
  }
626
626
  };
@@ -909,6 +909,15 @@ var AuthManager = class {
909
909
  #deviceId;
910
910
  /** Общий промис первичной выдачи `deviceId` — чтобы параллельные запросы получили один. */
911
911
  #deviceIdLoading = null;
912
+ /**
913
+ * Счётчик смен владельца авторизации.
914
+ *
915
+ * Растёт при каждой операции, которая заменяет или очищает сессию извне: `clear`,
916
+ * `setSession`, `setAccessToken`, вход. `#performRefresh` снимает его значение перед
917
+ * сетевым запросом и сверяет перед записью — иначе запоздавший ответ обновления мог бы
918
+ * воскресить уже очищенную сессию поверх `signOut`.
919
+ */
920
+ #authEpoch = 0;
912
921
  constructor(config, send, jar, hooks = {}) {
913
922
  this.#config = config;
914
923
  this.#send = send;
@@ -954,6 +963,10 @@ var AuthManager = class {
954
963
  #rotateAuthScope() {
955
964
  this.#authScope = nextAuthScope();
956
965
  }
966
+ /** Отмечает смену владельца авторизации — обесценивает результат идущего обновления. */
967
+ #invalidateInFlight() {
968
+ this.#authEpoch += 1;
969
+ }
957
970
  #identityForToken(accessToken) {
958
971
  const token = accessToken ? readTokenIdentity(accessToken) : {};
959
972
  return {
@@ -1099,6 +1112,7 @@ var AuthManager = class {
1099
1112
  /** Сохраняет токен, полученный извне, — например после подтверждения OTP. */
1100
1113
  async setAccessToken(accessToken) {
1101
1114
  await this.#loadSession();
1115
+ this.#invalidateInFlight();
1102
1116
  this.#transitionAuth(accessToken);
1103
1117
  await this.#saveSession({ ...this.#session ?? {}, accessToken, obtainedAt: Date.now() });
1104
1118
  this.#emitter.emit("tokens", { accessToken });
@@ -1121,6 +1135,7 @@ var AuthManager = class {
1121
1135
  /** Заменяет сессию и связанные с ней cookie целиком. */
1122
1136
  async setSession(session) {
1123
1137
  await this.#loadSession();
1138
+ this.#invalidateInFlight();
1124
1139
  this.#transitionAuth(session.accessToken);
1125
1140
  this.#jar.clear();
1126
1141
  this.#jar.deserialize(session.cookies);
@@ -1137,6 +1152,7 @@ var AuthManager = class {
1137
1152
  */
1138
1153
  async clear() {
1139
1154
  await this.#loadSession();
1155
+ this.#invalidateInFlight();
1140
1156
  this.#transitionAuth(void 0);
1141
1157
  this.#session = null;
1142
1158
  this.#jar.clear();
@@ -1219,6 +1235,7 @@ var AuthManager = class {
1219
1235
  if (!this.#hasRefreshSession()) {
1220
1236
  return this.#reloginOrNull();
1221
1237
  }
1238
+ const epoch = this.#authEpoch;
1222
1239
  try {
1223
1240
  const payload = await this.#send({
1224
1241
  method: "POST",
@@ -1231,6 +1248,9 @@ var AuthManager = class {
1231
1248
  });
1232
1249
  const accessToken = readAccessToken(payload);
1233
1250
  if (!accessToken) return this.#reloginOrNull();
1251
+ if (this.#authEpoch !== epoch) {
1252
+ return this.#session?.accessToken ?? null;
1253
+ }
1234
1254
  const rotated = this.#jar.getValue(
1235
1255
  REFRESH_COOKIE,
1236
1256
  this.#config.baseUrl + REFRESH_COOKIE_PATH
@@ -1246,6 +1266,9 @@ var AuthManager = class {
1246
1266
  return accessToken;
1247
1267
  } catch (error) {
1248
1268
  if (error instanceof ItdApiError) {
1269
+ if (this.#authEpoch !== epoch) {
1270
+ return this.#session?.accessToken ?? null;
1271
+ }
1249
1272
  this.#transitionAuth(void 0);
1250
1273
  this.#session = null;
1251
1274
  this.#jar.clear();
@@ -1315,6 +1338,7 @@ var AuthManager = class {
1315
1338
  "\u0412\u0445\u043E\u0434 \u043F\u043E email \u0438 \u043F\u0430\u0440\u043E\u043B\u044E \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u043A\u043E\u0434\u043E\u043C \u0438\u0437 \u043F\u0438\u0441\u044C\u043C\u0430. \u0410\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u0432\u0445\u043E\u0434 \u043D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u0435\u043D: \u0432\u043E\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435\u0441\u044C itd.auth.signInWithOtp() \u0438 \u043F\u0435\u0440\u0435\u0434\u0430\u0439\u0442\u0435 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044B\u0439 accessToken \u0432 \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044E \u043A\u043B\u0438\u0435\u043D\u0442\u0430."
1316
1339
  );
1317
1340
  }
1341
+ this.#invalidateInFlight();
1318
1342
  this.#transitionAuth(accessToken);
1319
1343
  await this.#saveSession({ accessToken, obtainedAt: Date.now() });
1320
1344
  this.#emitter.emit("tokens", { accessToken });
@@ -1382,7 +1406,7 @@ function normalizeBaseUrl(baseUrl) {
1382
1406
  }
1383
1407
 
1384
1408
  // src/core/version.ts
1385
- var LIBRARY_VERSION = "0.0.11";
1409
+ var LIBRARY_VERSION = "0.1.0";
1386
1410
 
1387
1411
  // src/core/config.ts
1388
1412
  var DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
@@ -1667,6 +1691,396 @@ function withLayerHeaders(request, headers) {
1667
1691
  return { ...request, layerHeaders: { ...request.layerHeaders, ...headers } };
1668
1692
  }
1669
1693
 
1694
+ // src/core/plugins.ts
1695
+ var NO_KEYS = /* @__PURE__ */ new Set();
1696
+ var RESERVED_OPTION_KEYS = /* @__PURE__ */ new Set([
1697
+ "signal",
1698
+ "timeout",
1699
+ "headers",
1700
+ "retry",
1701
+ "method",
1702
+ "path",
1703
+ "service",
1704
+ "baseUrl",
1705
+ "query",
1706
+ "body",
1707
+ "skipAuth",
1708
+ "skipAuthRefresh",
1709
+ "skipQueue",
1710
+ "raw"
1711
+ ]);
1712
+ var RELATION_FIELDS = ["requires", "conflicts", "before", "after"];
1713
+ var HOOK_FIELDS = ["onRequest", "onResponse", "onError", "onRetry"];
1714
+ function validateNameList(plugin, field) {
1715
+ const values = plugin[field];
1716
+ if (values === void 0) return;
1717
+ if (!Array.isArray(values)) {
1718
+ throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin.name}\xBB: ${field} \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043C\u0430\u0441\u0441\u0438\u0432\u043E\u043C`);
1719
+ }
1720
+ const seen = /* @__PURE__ */ new Set();
1721
+ for (const value of values) {
1722
+ if (typeof value !== "string" || value.trim() === "") {
1723
+ throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin.name}\xBB: ${field} \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043F\u0443\u0441\u0442\u043E\u0435 \u0438\u043C\u044F \u043F\u043B\u0430\u0433\u0438\u043D\u0430`);
1724
+ }
1725
+ if (value === plugin.name) {
1726
+ throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin.name}\xBB \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u0443\u043A\u0430\u0437\u0430\u0442\u044C \u0441\u0435\u0431\u044F \u0432 ${field}`);
1727
+ }
1728
+ if (seen.has(value)) {
1729
+ throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin.name}\xBB: ${field} \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0435\u0442 \u0438\u043C\u044F \xAB${value}\xBB`);
1730
+ }
1731
+ seen.add(value);
1732
+ }
1733
+ }
1734
+ function validateHooks(plugin, hooks) {
1735
+ if (typeof hooks !== "object" || hooks === null) {
1736
+ throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin}\xBB \u043F\u0435\u0440\u0435\u0434\u0430\u043B \u0432 useHooks() \u043D\u0435 \u043E\u0431\u044A\u0435\u043A\u0442`);
1737
+ }
1738
+ for (const field of HOOK_FIELDS) {
1739
+ if (hooks[field] !== void 0 && typeof hooks[field] !== "function") {
1740
+ throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin}\xBB: useHooks().${field} \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0444\u0443\u043D\u043A\u0446\u0438\u0435\u0439`);
1741
+ }
1742
+ }
1743
+ }
1744
+ function validatePluginDefinition(plugin) {
1745
+ if (typeof plugin?.install !== "function") {
1746
+ throw new ItdConfigError("\u041F\u043B\u0430\u0433\u0438\u043D \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043E\u0431\u044A\u0435\u043A\u0442\u043E\u043C \u0441 \u043C\u0435\u0442\u043E\u0434\u043E\u043C install()");
1747
+ }
1748
+ const name = plugin.name;
1749
+ if (typeof name !== "string" || name.trim() === "") {
1750
+ throw new ItdConfigError("\u0423 \u043F\u043B\u0430\u0433\u0438\u043D\u0430 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435\u043F\u0443\u0441\u0442\u043E\u0435 \u0438\u043C\u044F");
1751
+ }
1752
+ const keys = plugin.optionKeys ?? [];
1753
+ if (!Array.isArray(keys)) {
1754
+ throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB: optionKeys \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043C\u0430\u0441\u0441\u0438\u0432\u043E\u043C`);
1755
+ }
1756
+ for (const key of keys) {
1757
+ if (typeof key !== "string" || key.trim() === "") {
1758
+ throw new ItdConfigError(`\u041F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB \u0437\u0430\u044F\u0432\u0438\u043B \u043F\u0443\u0441\u0442\u043E\u0435 \u0438\u043C\u044F \u043E\u043F\u0446\u0438\u0438`);
1759
+ }
1760
+ if (RESERVED_OPTION_KEYS.has(key)) {
1761
+ throw new ItdConfigError(
1762
+ `\u041F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB \u0437\u0430\u044F\u0432\u0438\u043B \u043E\u043F\u0446\u0438\u044E \xAB${key}\xBB: \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u0437\u0430\u043F\u0440\u043E\u0441\u0430, \u0438\u043C\u044F \u0437\u0430\u043D\u044F\u0442\u043E. \u0417\u0430\u043D\u044F\u0442\u044B\u0435 \u0438\u043C\u0435\u043D\u0430: ${[...RESERVED_OPTION_KEYS].join(", ")}`
1763
+ );
1764
+ }
1765
+ }
1766
+ for (const field of RELATION_FIELDS) validateNameList(plugin, field);
1767
+ }
1768
+ function addEdge(from, to, edges, indegree) {
1769
+ const targets = edges.get(from);
1770
+ if (!targets || targets.has(to)) return;
1771
+ targets.add(to);
1772
+ indegree.set(to, (indegree.get(to) ?? 0) + 1);
1773
+ }
1774
+ function orderPluginDefinitions(plugins) {
1775
+ const entries = [];
1776
+ const byName = /* @__PURE__ */ new Map();
1777
+ for (const [sequence, plugin] of plugins.entries()) {
1778
+ validatePluginDefinition(plugin);
1779
+ if (byName.has(plugin.name)) {
1780
+ throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin.name}\xBB \u0443\u0436\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D`);
1781
+ }
1782
+ const entry = { plugin, sequence };
1783
+ entries.push(entry);
1784
+ byName.set(plugin.name, entry);
1785
+ }
1786
+ for (const { plugin } of entries) {
1787
+ for (const required of plugin.requires ?? []) {
1788
+ if (!byName.has(required)) {
1789
+ throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D\u0443 \xAB${plugin.name}\xBB \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F \u043F\u043B\u0430\u0433\u0438\u043D \xAB${required}\xBB`);
1790
+ }
1791
+ }
1792
+ for (const conflict of plugin.conflicts ?? []) {
1793
+ if (byName.has(conflict)) {
1794
+ throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin.name}\xBB \u043D\u0435\u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C \u0441 \u043F\u043B\u0430\u0433\u0438\u043D\u043E\u043C \xAB${conflict}\xBB`);
1795
+ }
1796
+ }
1797
+ for (const { plugin: other } of entries) {
1798
+ if (other.conflicts?.includes(plugin.name)) {
1799
+ throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin.name}\xBB \u043D\u0435\u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C \u0441 \u043F\u043B\u0430\u0433\u0438\u043D\u043E\u043C \xAB${other.name}\xBB`);
1800
+ }
1801
+ }
1802
+ }
1803
+ const edges = /* @__PURE__ */ new Map();
1804
+ const indegree = /* @__PURE__ */ new Map();
1805
+ for (const { plugin } of entries) {
1806
+ edges.set(plugin.name, /* @__PURE__ */ new Set());
1807
+ indegree.set(plugin.name, 0);
1808
+ }
1809
+ for (const { plugin } of entries) {
1810
+ for (const required of plugin.requires ?? []) {
1811
+ addEdge(required, plugin.name, edges, indegree);
1812
+ }
1813
+ for (const target of plugin.before ?? []) {
1814
+ if (byName.has(target)) addEdge(plugin.name, target, edges, indegree);
1815
+ }
1816
+ for (const target of plugin.after ?? []) {
1817
+ if (byName.has(target)) addEdge(target, plugin.name, edges, indegree);
1818
+ }
1819
+ }
1820
+ const ready = entries.filter(({ plugin }) => indegree.get(plugin.name) === 0);
1821
+ ready.sort((a, b) => a.sequence - b.sequence);
1822
+ const ordered = [];
1823
+ while (ready.length > 0) {
1824
+ const current = ready.shift();
1825
+ if (!current) break;
1826
+ ordered.push(current.plugin);
1827
+ for (const target of edges.get(current.plugin.name) ?? []) {
1828
+ const next = (indegree.get(target) ?? 0) - 1;
1829
+ indegree.set(target, next);
1830
+ if (next === 0) {
1831
+ const entry = byName.get(target);
1832
+ if (entry) {
1833
+ ready.push(entry);
1834
+ ready.sort((a, b) => a.sequence - b.sequence);
1835
+ }
1836
+ }
1837
+ }
1838
+ }
1839
+ if (ordered.length !== entries.length) {
1840
+ const cycle = entries.filter(({ plugin }) => (indegree.get(plugin.name) ?? 0) > 0).map(({ plugin }) => plugin.name);
1841
+ throw new ItdConfigError(`\u0446\u0438\u043A\u043B\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u043F\u043E\u0440\u044F\u0434\u043E\u043A \u043F\u043B\u0430\u0433\u0438\u043D\u043E\u0432: ${cycle.join(" \u2192 ")}`);
1842
+ }
1843
+ return ordered;
1844
+ }
1845
+ function assertPluginRemovable(plugins, name) {
1846
+ const dependent = plugins.find((plugin) => plugin.requires?.includes(name));
1847
+ if (dependent) {
1848
+ throw new ItdConfigError(
1849
+ `\u043D\u0435\u043B\u044C\u0437\u044F \u043E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB: \u043E\u0442 \u043D\u0435\u0433\u043E \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \xAB${dependent.name}\xBB`
1850
+ );
1851
+ }
1852
+ }
1853
+ var REQUEST_HOOK_DISPATCHERS = /* @__PURE__ */ new WeakMap();
1854
+ var PLUGIN_HOOK_SCOPE = /* @__PURE__ */ Symbol("itd-api.plugin-hooks");
1855
+ async function dispatchRequestHook(hooks, field, context, request) {
1856
+ const dispatcher = REQUEST_HOOK_DISPATCHERS.get(hooks);
1857
+ if (dispatcher) {
1858
+ await dispatcher(field, context, request);
1859
+ return;
1860
+ }
1861
+ const hook = hooks[field];
1862
+ await hook?.(context);
1863
+ }
1864
+ var PluginRegistry = class {
1865
+ #entries = /* @__PURE__ */ new Map();
1866
+ #optionKeys = /* @__PURE__ */ new Set();
1867
+ #removing = /* @__PURE__ */ new Set();
1868
+ #cleanups = /* @__PURE__ */ new Set();
1869
+ #ordered = [];
1870
+ /** Сколько плагинов подключено. */
1871
+ get size() {
1872
+ return this.#entries.size;
1873
+ }
1874
+ /** Имена опций активных плагинов. */
1875
+ get optionKeys() {
1876
+ return this.#optionKeys.size === 0 ? NO_KEYS : this.#optionKeys;
1877
+ }
1878
+ /** Имена плагинов в фактическом порядке выполнения. */
1879
+ names() {
1880
+ return this.#ordered.map(({ plugin }) => plugin.name);
1881
+ }
1882
+ /** Подключён ли плагин с таким именем. */
1883
+ has(name) {
1884
+ return this.#entries.has(name);
1885
+ }
1886
+ /** Проверяет добавление без вызова `install()`. @internal */
1887
+ assertCanAdd(plugin) {
1888
+ validatePluginDefinition(plugin);
1889
+ if (this.#removing.has(plugin.name)) {
1890
+ throw new ItdConfigError(
1891
+ `\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin.name}\xBB \u0435\u0449\u0451 \u043E\u0442\u043A\u043B\u044E\u0447\u0430\u0435\u0442\u0441\u044F; \u0434\u043E\u0436\u0434\u0438\u0442\u0435\u0441\u044C \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F unuse() \u0438\u043B\u0438 dispose()`
1892
+ );
1893
+ }
1894
+ orderPluginDefinitions([...this.#ordered.map((entry) => entry.plugin), plugin]);
1895
+ }
1896
+ /** Проверяет удаление без изменения реестра. @internal */
1897
+ assertCanRemove(name) {
1898
+ const entry = this.#entries.get(name);
1899
+ if (!entry) return;
1900
+ assertPluginRemovable(
1901
+ this.#ordered.map((current) => current.plugin),
1902
+ name
1903
+ );
1904
+ }
1905
+ /**
1906
+ * Подключает плагин.
1907
+ *
1908
+ * @throws {ItdConfigError} если плагин задан неверно, уже подключён, нарушает зависимости
1909
+ * или заявил занятое имя опции
1910
+ */
1911
+ add(plugin, context) {
1912
+ this.assertCanAdd(plugin);
1913
+ const ordered = orderPluginDefinitions([...this.#ordered.map((entry) => entry.plugin), plugin]);
1914
+ const transformers = [];
1915
+ const hooks = [];
1916
+ const installed = plugin.install({
1917
+ ...context,
1918
+ use: (transformer) => {
1919
+ if (typeof transformer !== "function") {
1920
+ throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin.name}\xBB \u043F\u0435\u0440\u0435\u0434\u0430\u043B \u0432 use() \u043D\u0435 \u0444\u0443\u043D\u043A\u0446\u0438\u044E`);
1921
+ }
1922
+ transformers.push(transformer);
1923
+ },
1924
+ useHooks: (value) => {
1925
+ validateHooks(plugin.name, value);
1926
+ hooks.push({ ...value });
1927
+ }
1928
+ });
1929
+ const teardown = typeof installed === "function" ? installed : void 0;
1930
+ this.#entries.set(plugin.name, {
1931
+ plugin,
1932
+ transformers,
1933
+ hooks,
1934
+ teardown,
1935
+ activeRequests: 0,
1936
+ drain: void 0,
1937
+ finishDrain: void 0
1938
+ });
1939
+ this.#ordered = ordered.map((definition) => {
1940
+ const entry = this.#entries.get(definition.name);
1941
+ if (!entry) throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${definition.name}\xBB \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D`);
1942
+ return entry;
1943
+ });
1944
+ this.#rebuildOptionKeys();
1945
+ }
1946
+ /**
1947
+ * Отключает плагин и вызывает его функцию очистки.
1948
+ *
1949
+ * Новые запросы перестают видеть плагин сразу. Если его обёртка уже выполняется,
1950
+ * очистка дождётся завершения этого логического запроса.
1951
+ *
1952
+ * @returns `false`, если такого плагина не было
1953
+ */
1954
+ async remove(name) {
1955
+ const entry = this.#entries.get(name);
1956
+ if (!entry) return false;
1957
+ this.assertCanRemove(name);
1958
+ this.#entries.delete(name);
1959
+ this.#ordered = this.#ordered.filter((current) => current !== entry);
1960
+ this.#rebuildOptionKeys();
1961
+ this.#removing.add(name);
1962
+ const cleanup = this.#trackCleanup(
1963
+ (async () => {
1964
+ await this.#waitForDrain(entry);
1965
+ await entry.teardown?.();
1966
+ })()
1967
+ );
1968
+ try {
1969
+ await cleanup;
1970
+ return true;
1971
+ } finally {
1972
+ this.#removing.delete(name);
1973
+ }
1974
+ }
1975
+ /**
1976
+ * Отключает все плагины окончательно.
1977
+ *
1978
+ * Очистка идёт изнутри наружу — в порядке, обратном выполнению обёрток.
1979
+ */
1980
+ async dispose() {
1981
+ const entries = [...this.#ordered].reverse();
1982
+ const previousCleanups = [...this.#cleanups];
1983
+ this.#entries.clear();
1984
+ this.#ordered = [];
1985
+ this.#optionKeys.clear();
1986
+ for (const { plugin } of entries) this.#removing.add(plugin.name);
1987
+ const cleanup = this.#trackCleanup(
1988
+ (async () => {
1989
+ const errors = [];
1990
+ const previous = await Promise.allSettled(previousCleanups);
1991
+ for (const result of previous) {
1992
+ if (result.status === "rejected") errors.push(result.reason);
1993
+ }
1994
+ for (const entry of entries) {
1995
+ try {
1996
+ await this.#waitForDrain(entry);
1997
+ await entry.teardown?.();
1998
+ } catch (error) {
1999
+ errors.push(error);
2000
+ } finally {
2001
+ this.#removing.delete(entry.plugin.name);
2002
+ }
2003
+ }
2004
+ if (errors.length > 0) {
2005
+ throw new AggregateError(errors, "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u044C \u0440\u0435\u0441\u0443\u0440\u0441\u044B \u043F\u043B\u0430\u0433\u0438\u043D\u043E\u0432");
2006
+ }
2007
+ })()
2008
+ );
2009
+ await cleanup;
2010
+ }
2011
+ /**
2012
+ * Объединяет конструкторские хуки с хуками подключаемых плагинов.
2013
+ *
2014
+ * Возвращённый объект динамический: подключение и отключение плагина начинает действовать
2015
+ * со следующего логического запроса без пересоздания транспорта.
2016
+ */
2017
+ hooks(base) {
2018
+ const hooks = {};
2019
+ REQUEST_HOOK_DISPATCHERS.set(hooks, ((field, context, request) => this.#runHook(field, context, request, base)));
2020
+ return hooks;
2021
+ }
2022
+ /**
2023
+ * Прогоняет запрос через цепочку обёрток.
2024
+ *
2025
+ * Снимок цепочки берётся в начале: `unuse()` влияет на новые запросы, но не обрывает
2026
+ * уже выполняющийся посередине.
2027
+ *
2028
+ * @param execute настоящий запрос, вызывается самой внутренней обёрткой
2029
+ */
2030
+ async run(request, execute) {
2031
+ const entries = [...this.#ordered];
2032
+ for (const entry of entries) entry.activeRequests += 1;
2033
+ const hookScope = entries.flatMap((entry) => entry.hooks);
2034
+ const scoped = (current) => current[PLUGIN_HOOK_SCOPE] === hookScope ? current : { ...current, [PLUGIN_HOOK_SCOPE]: hookScope };
2035
+ const chain = entries.flatMap((entry) => entry.transformers).reduceRight(
2036
+ (next, transformer) => (current) => transformer(scoped(current), (prepared) => next(scoped(prepared))),
2037
+ (current) => execute(scoped(current))
2038
+ );
2039
+ try {
2040
+ return await chain(scoped(request));
2041
+ } finally {
2042
+ for (const entry of entries) {
2043
+ entry.activeRequests -= 1;
2044
+ if (entry.activeRequests === 0) {
2045
+ entry.finishDrain?.();
2046
+ entry.finishDrain = void 0;
2047
+ entry.drain = void 0;
2048
+ }
2049
+ }
2050
+ }
2051
+ }
2052
+ #rebuildOptionKeys() {
2053
+ this.#optionKeys.clear();
2054
+ for (const { plugin } of this.#ordered) {
2055
+ for (const key of plugin.optionKeys ?? []) this.#optionKeys.add(key);
2056
+ }
2057
+ }
2058
+ #waitForDrain(entry) {
2059
+ if (entry.activeRequests === 0) return Promise.resolve();
2060
+ entry.drain ??= new Promise((resolve) => {
2061
+ entry.finishDrain = resolve;
2062
+ });
2063
+ return entry.drain;
2064
+ }
2065
+ #trackCleanup(cleanup) {
2066
+ this.#cleanups.add(cleanup);
2067
+ void cleanup.then(
2068
+ () => this.#cleanups.delete(cleanup),
2069
+ () => this.#cleanups.delete(cleanup)
2070
+ );
2071
+ return cleanup;
2072
+ }
2073
+ async #runHook(field, context, request, base) {
2074
+ const baseHook = base[field];
2075
+ await baseHook?.(context);
2076
+ const scope = request[PLUGIN_HOOK_SCOPE] ?? [];
2077
+ for (const hooks of scope) {
2078
+ const hook = hooks[field];
2079
+ await hook?.(context);
2080
+ }
2081
+ }
2082
+ };
2083
+
1670
2084
  // src/core/retry.ts
1671
2085
  var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
1672
2086
  function isRetryable(error, method, retryWrites) {
@@ -1722,10 +2136,7 @@ function createQueueMiddleware(schedule) {
1722
2136
  return (request, next) => request.skipQueue ? next(request) : schedule(request, () => next(request));
1723
2137
  }
1724
2138
  function createPluginsMiddleware(plugins) {
1725
- return (request, next) => {
1726
- if (plugins.size === 0) return next(request);
1727
- return plugins.run(request, next);
1728
- };
2139
+ return (request, next) => plugins.run(request, next);
1729
2140
  }
1730
2141
  function createServicesMiddleware(registry) {
1731
2142
  return async (request, next) => {
@@ -1797,16 +2208,21 @@ function createRetryMiddleware(deps) {
1797
2208
  } catch (error) {
1798
2209
  const delay = nextDelay(error, attempt, request, method, backoff);
1799
2210
  if (delay === void 0) throw error;
1800
- await deps.hooks.onRetry?.({
1801
- method,
1802
- path: request.path,
1803
- url: deps.buildUrl(request),
1804
- // Умолчания транспорта добавляются после слоя повторов и сюда не входят.
1805
- headers: new Headers({ ...request.layerHeaders, ...request.headers }),
1806
- attempt,
1807
- error,
1808
- delay
1809
- });
2211
+ await dispatchRequestHook(
2212
+ deps.hooks,
2213
+ "onRetry",
2214
+ {
2215
+ method,
2216
+ path: request.path,
2217
+ url: deps.buildUrl(request),
2218
+ // Умолчания транспорта добавляются после слоя повторов и сюда не входят.
2219
+ headers: new Headers({ ...request.layerHeaders, ...request.headers }),
2220
+ attempt,
2221
+ error,
2222
+ delay
2223
+ },
2224
+ request
2225
+ );
1810
2226
  deps.logger?.debug(
1811
2227
  `\u043F\u043E\u0432\u0442\u043E\u0440 ${method} ${request.path}, \u043F\u043E\u043F\u044B\u0442\u043A\u0430 ${attempt + 1} \u0447\u0435\u0440\u0435\u0437 ${delay} \u043C\u0441`
1812
2228
  );
@@ -1816,104 +2232,6 @@ function createRetryMiddleware(deps) {
1816
2232
  };
1817
2233
  }
1818
2234
 
1819
- // src/core/plugins.ts
1820
- var NO_KEYS = /* @__PURE__ */ new Set();
1821
- var RESERVED_OPTION_KEYS = /* @__PURE__ */ new Set([
1822
- "signal",
1823
- "timeout",
1824
- "headers",
1825
- "retry",
1826
- "method",
1827
- "path",
1828
- "service",
1829
- "baseUrl",
1830
- "query",
1831
- "body",
1832
- "skipAuth",
1833
- "skipAuthRefresh",
1834
- "skipQueue",
1835
- "raw"
1836
- ]);
1837
- function validatePluginDefinition(plugin) {
1838
- if (typeof plugin?.install !== "function") {
1839
- throw new ItdConfigError("\u041F\u043B\u0430\u0433\u0438\u043D \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043E\u0431\u044A\u0435\u043A\u0442\u043E\u043C \u0441 \u043C\u0435\u0442\u043E\u0434\u043E\u043C install()");
1840
- }
1841
- const name = plugin.name;
1842
- if (typeof name !== "string" || name.trim() === "") {
1843
- throw new ItdConfigError("\u0423 \u043F\u043B\u0430\u0433\u0438\u043D\u0430 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435\u043F\u0443\u0441\u0442\u043E\u0435 \u0438\u043C\u044F");
1844
- }
1845
- const keys = plugin.optionKeys ?? [];
1846
- for (const key of keys) {
1847
- if (typeof key !== "string" || key.trim() === "") {
1848
- throw new ItdConfigError(`\u041F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB \u0437\u0430\u044F\u0432\u0438\u043B \u043F\u0443\u0441\u0442\u043E\u0435 \u0438\u043C\u044F \u043E\u043F\u0446\u0438\u0438`);
1849
- }
1850
- if (RESERVED_OPTION_KEYS.has(key)) {
1851
- throw new ItdConfigError(
1852
- `\u041F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB \u0437\u0430\u044F\u0432\u0438\u043B \u043E\u043F\u0446\u0438\u044E \xAB${key}\xBB: \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u0437\u0430\u043F\u0440\u043E\u0441\u0430, \u0438\u043C\u044F \u0437\u0430\u043D\u044F\u0442\u043E. \u0417\u0430\u043D\u044F\u0442\u044B\u0435 \u0438\u043C\u0435\u043D\u0430: ${[...RESERVED_OPTION_KEYS].join(", ")}`
1853
- );
1854
- }
1855
- }
1856
- }
1857
- var PluginRegistry = class {
1858
- #transformers = [];
1859
- #optionKeys = /* @__PURE__ */ new Set();
1860
- #names = /* @__PURE__ */ new Set();
1861
- /** Сколько обёрток подключено. Ноль означает, что запрос идёт прежним путём. */
1862
- get size() {
1863
- return this.#transformers.length;
1864
- }
1865
- /** Имена опций запроса, заявленные плагинами. */
1866
- get optionKeys() {
1867
- return this.#optionKeys.size === 0 ? NO_KEYS : this.#optionKeys;
1868
- }
1869
- /**
1870
- * Подключает плагин.
1871
- *
1872
- * @throws {ItdConfigError} если плагин задан неверно, уже подключён или заявил занятое
1873
- * имя опции
1874
- */
1875
- add(plugin, context) {
1876
- validatePluginDefinition(plugin);
1877
- const name = plugin.name;
1878
- if (this.#names.has(name)) {
1879
- throw new ItdConfigError(`\u041F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB \u0443\u0436\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D`);
1880
- }
1881
- const keys = plugin.optionKeys ?? [];
1882
- const before = this.#transformers.length;
1883
- try {
1884
- plugin.install({
1885
- ...context,
1886
- use: (transformer) => {
1887
- if (typeof transformer !== "function") {
1888
- throw new ItdConfigError(`\u041F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB \u043F\u0435\u0440\u0435\u0434\u0430\u043B \u0432 use() \u043D\u0435 \u0444\u0443\u043D\u043A\u0446\u0438\u044E`);
1889
- }
1890
- this.#transformers.push(transformer);
1891
- }
1892
- });
1893
- } catch (error) {
1894
- this.#transformers.length = before;
1895
- throw error;
1896
- }
1897
- this.#names.add(name);
1898
- for (const key of keys) this.#optionKeys.add(key);
1899
- }
1900
- /**
1901
- * Прогоняет запрос через цепочку обёрток.
1902
- *
1903
- * Цепочка собирается на каждый запрос заново: плагин можно подключить в любой момент,
1904
- * а обёрток единицы — экономить тут не на чем.
1905
- *
1906
- * @param execute настоящий запрос, вызывается самой внутренней обёрткой
1907
- */
1908
- run(request, execute) {
1909
- const chain = this.#transformers.reduceRight(
1910
- (next, transformer) => (current) => transformer(current, next),
1911
- execute
1912
- );
1913
- return chain(request);
1914
- }
1915
- };
1916
-
1917
2235
  // src/core/rate-limit.ts
1918
2236
  function queueAbortError() {
1919
2237
  return new ItdAbortError("\u0417\u0430\u043F\u0440\u043E\u0441 \u043E\u0442\u043C\u0435\u043D\u0451\u043D \u0432\u043E \u0432\u0440\u0435\u043C\u044F \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F \u043E\u0447\u0435\u0440\u0435\u0434\u0438");
@@ -2511,7 +2829,7 @@ var Transport = class {
2511
2829
  }
2512
2830
  }
2513
2831
  const context = { method, path: request.path, url, headers, attempt };
2514
- await this.#config.hooks.onRequest?.(context);
2832
+ await dispatchRequestHook(this.#config.hooks, "onRequest", context, request);
2515
2833
  const timeout = request.timeout ?? this.#config.timeout;
2516
2834
  const abort = createAbortBundle(request.signal, timeout);
2517
2835
  const startedAt = Date.now();
@@ -2532,7 +2850,12 @@ var Transport = class {
2532
2850
  } catch (error) {
2533
2851
  const duration2 = Date.now() - startedAt;
2534
2852
  const failure = this.#toTransportError(error, abort, request, method, timeout);
2535
- await this.#config.hooks.onError?.({ ...context, duration: duration2, error: failure });
2853
+ await dispatchRequestHook(
2854
+ this.#config.hooks,
2855
+ "onError",
2856
+ { ...context, duration: duration2, error: failure },
2857
+ request
2858
+ );
2536
2859
  this.#config.logger?.warn(
2537
2860
  `\xD7 ${method} ${request.path} (${duration2} \u043C\u0441): ${failure.message}`
2538
2861
  );
@@ -2544,12 +2867,17 @@ var Transport = class {
2544
2867
  }
2545
2868
  if (this.#config.useCookieJar) this.#deps.cookies?.setFromResponse(url, response);
2546
2869
  if (response.ok) {
2547
- await this.#config.hooks.onResponse?.({
2548
- ...context,
2549
- status: response.status,
2550
- duration: Date.now() - startedAt,
2551
- response
2552
- });
2870
+ await dispatchRequestHook(
2871
+ this.#config.hooks,
2872
+ "onResponse",
2873
+ {
2874
+ ...context,
2875
+ status: response.status,
2876
+ duration: Date.now() - startedAt,
2877
+ response
2878
+ },
2879
+ request
2880
+ );
2553
2881
  }
2554
2882
  const payload = await this.#readBodyOrFail(
2555
2883
  response,
@@ -2570,7 +2898,12 @@ var Transport = class {
2570
2898
  response,
2571
2899
  body: payload
2572
2900
  });
2573
- await this.#config.hooks.onError?.({ ...context, duration, error });
2901
+ await dispatchRequestHook(
2902
+ this.#config.hooks,
2903
+ "onError",
2904
+ { ...context, duration, error },
2905
+ request
2906
+ );
2574
2907
  this.#config.logger?.warn(
2575
2908
  `\u2190 ${response.status} ${method} ${request.path} (${duration} \u043C\u0441): ${error.message}`
2576
2909
  );
@@ -2591,11 +2924,16 @@ var Transport = class {
2591
2924
  await response.body?.cancel().catch(() => {
2592
2925
  });
2593
2926
  const failure = this.#toTransportError(error, abort, request, method, timeout);
2594
- await this.#config.hooks.onError?.({
2595
- ...context,
2596
- duration: Date.now() - startedAt,
2597
- error: failure
2598
- });
2927
+ await dispatchRequestHook(
2928
+ this.#config.hooks,
2929
+ "onError",
2930
+ {
2931
+ ...context,
2932
+ duration: Date.now() - startedAt,
2933
+ error: failure
2934
+ },
2935
+ request
2936
+ );
2599
2937
  this.#config.logger?.warn(
2600
2938
  `\xD7 ${method} ${request.path}: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u0442\u0435\u043B\u043E \u043E\u0442\u0432\u0435\u0442\u0430 \u2014 ${failure.message}`
2601
2939
  );
@@ -2646,12 +2984,16 @@ var Transport = class {
2646
2984
  }
2647
2985
  /** Превращает исключение `fetch` в понятную ошибку библиотеки. */
2648
2986
  #toTransportError(error, abort, request, method, timeout) {
2649
- const aborted = error instanceof Error && error.name === "AbortError";
2987
+ const aborted = abort.signal.aborted || error instanceof Error && error.name === "AbortError";
2650
2988
  if (aborted && abort.timedOut()) {
2651
2989
  return new ItdTimeoutError({ timeout, method, path: request.path });
2652
2990
  }
2653
2991
  if (aborted) {
2654
- return new ItdAbortError(`\u0417\u0430\u043F\u0440\u043E\u0441 ${method} ${request.path} \u043E\u0442\u043C\u0435\u043D\u0451\u043D`);
2992
+ const reason = request.signal?.reason;
2993
+ return new ItdAbortError(
2994
+ `\u0417\u0430\u043F\u0440\u043E\u0441 ${method} ${request.path} \u043E\u0442\u043C\u0435\u043D\u0451\u043D`,
2995
+ reason !== void 0 ? { cause: reason } : void 0
2996
+ );
2655
2997
  }
2656
2998
  return new ItdNetworkError(
2657
2999
  `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C ${method} ${request.path}: ${error instanceof Error ? error.message : String(error)}`,
@@ -2949,7 +3291,6 @@ var PollTransport = class {
2949
3291
  const seen = /* @__PURE__ */ new Set();
2950
3292
  let firstRun = true;
2951
3293
  let lastUnreadCount;
2952
- context.onOpen();
2953
3294
  while (!context.signal.aborted) {
2954
3295
  const token = await context.getToken();
2955
3296
  if (!token) throw new UnauthorizedStreamError();
@@ -2964,6 +3305,7 @@ var PollTransport = class {
2964
3305
  });
2965
3306
  if (response.status === 401) throw new UnauthorizedStreamError();
2966
3307
  if (!response.ok) throw new Error(`\u041E\u043F\u0440\u043E\u0441 \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u0439 \u0432\u0435\u0440\u043D\u0443\u043B \u0441\u0442\u0430\u0442\u0443\u0441 ${response.status}`);
3308
+ context.onOpen();
2967
3309
  const body = await response.json();
2968
3310
  const payload = typeof body === "object" && body !== null && "data" in body ? body.data : body;
2969
3311
  const items = pickArray(payload, "notifications");
@@ -3209,10 +3551,12 @@ var STREAM_PATH = "/api/notifications/stream";
3209
3551
  var SseTransport = class {
3210
3552
  name = "sse";
3211
3553
  #idleTimeout;
3554
+ #handshakeTimeout;
3212
3555
  /** Идентификатор последнего события — отправляется при переподключении. */
3213
3556
  #lastEventId;
3214
3557
  constructor(options = {}) {
3215
3558
  this.#idleTimeout = options.idleTimeout ?? 9e4;
3559
+ this.#handshakeTimeout = options.handshakeTimeout ?? 2e4;
3216
3560
  }
3217
3561
  async connect(context) {
3218
3562
  const token = await context.getToken();
@@ -3223,16 +3567,36 @@ var SseTransport = class {
3223
3567
  headers.set("Authorization", `Bearer ${token}`);
3224
3568
  headers.set("Cache-Control", "no-cache");
3225
3569
  if (this.#lastEventId) headers.set("Last-Event-ID", this.#lastEventId);
3226
- const response = await context.fetch(url, {
3227
- method: "GET",
3228
- headers,
3229
- signal: context.signal
3230
- });
3231
- if (response.status === 401) throw new UnauthorizedStreamError();
3232
- if (!response.ok) throw new Error(`\u041F\u043E\u0442\u043E\u043A \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u0439 \u0432\u0435\u0440\u043D\u0443\u043B \u0441\u0442\u0430\u0442\u0443\u0441 ${response.status}`);
3233
- if (!response.body) throw new Error("\u041E\u0442\u0432\u0435\u0442 \u043F\u043E\u0442\u043E\u043A\u0430 \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u0439 \u043F\u0443\u0441\u0442");
3234
- context.onOpen();
3235
- await this.#read(response.body, context);
3570
+ const controller = new AbortController();
3571
+ const relayAbort = () => controller.abort(context.signal.reason);
3572
+ if (context.signal.aborted) controller.abort(context.signal.reason);
3573
+ else context.signal.addEventListener("abort", relayAbort, { once: true });
3574
+ try {
3575
+ const response = await this.#handshake(url, headers, context, controller);
3576
+ if (response.status === 401) throw new UnauthorizedStreamError();
3577
+ if (!response.ok) throw new Error(`\u041F\u043E\u0442\u043E\u043A \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u0439 \u0432\u0435\u0440\u043D\u0443\u043B \u0441\u0442\u0430\u0442\u0443\u0441 ${response.status}`);
3578
+ if (!response.body) throw new Error("\u041E\u0442\u0432\u0435\u0442 \u043F\u043E\u0442\u043E\u043A\u0430 \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u0439 \u043F\u0443\u0441\u0442");
3579
+ context.onOpen();
3580
+ await this.#read(response.body, context);
3581
+ } finally {
3582
+ context.signal.removeEventListener("abort", relayAbort);
3583
+ }
3584
+ }
3585
+ /** Выполняет запрос потока, обрывая его, если ответ не пришёл за отведённое время. */
3586
+ async #handshake(url, headers, context, controller) {
3587
+ let expired = false;
3588
+ const timer = this.#handshakeTimeout > 0 ? setTimeout(() => {
3589
+ expired = true;
3590
+ controller.abort(new Error("\u0418\u0441\u0442\u0451\u043A \u0442\u0430\u0439\u043C\u0430\u0443\u0442 \u0440\u0443\u043A\u043E\u043F\u043E\u0436\u0430\u0442\u0438\u044F SSE"));
3591
+ }, this.#handshakeTimeout) : void 0;
3592
+ try {
3593
+ return await context.fetch(url, { method: "GET", headers, signal: controller.signal });
3594
+ } catch (error) {
3595
+ if (expired) throw new Error("\u041F\u043E\u0442\u043E\u043A \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u0439 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B: \u0438\u0441\u0442\u0451\u043A \u0442\u0430\u0439\u043C\u0430\u0443\u0442 \u0440\u0443\u043A\u043E\u043F\u043E\u0436\u0430\u0442\u0438\u044F");
3596
+ throw error;
3597
+ } finally {
3598
+ if (timer !== void 0) clearTimeout(timer);
3599
+ }
3236
3600
  }
3237
3601
  async #read(body, context) {
3238
3602
  const reader = body.getReader();
@@ -3304,6 +3668,7 @@ function validateRealtimeOptions(options) {
3304
3668
  positiveInteger(options.maxAttempts, "maxAttempts");
3305
3669
  duration(options.pollInterval, "pollInterval", 1);
3306
3670
  duration(options.idleTimeout, "idleTimeout", 0);
3671
+ duration(options.handshakeTimeout, "handshakeTimeout", 0);
3307
3672
  if (options.jitter !== void 0 && !(options.jitter >= 0 && options.jitter <= 1)) {
3308
3673
  throw new ItdConfigError(
3309
3674
  `realtime.jitter \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0432 \u0434\u0438\u0430\u043F\u0430\u0437\u043E\u043D\u0435 0\u20261, \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: ${options.jitter}`
@@ -3425,7 +3790,8 @@ var ItdRealtime = class {
3425
3790
  });
3426
3791
  }
3427
3792
  return new SseTransport({
3428
- ...this.#options.idleTimeout !== void 0 ? { idleTimeout: this.#options.idleTimeout } : {}
3793
+ ...this.#options.idleTimeout !== void 0 ? { idleTimeout: this.#options.idleTimeout } : {},
3794
+ ...this.#options.handshakeTimeout !== void 0 ? { handshakeTimeout: this.#options.handshakeTimeout } : {}
3429
3795
  });
3430
3796
  }
3431
3797
  /** Запускает попытку подключения; повторы планирует сам. */
@@ -3640,6 +4006,11 @@ var Paginator = class {
3640
4006
  #finished = false;
3641
4007
  #pagesLoaded = 0;
3642
4008
  constructor(options) {
4009
+ if (options.maxPages !== void 0 && (!Number.isInteger(options.maxPages) || options.maxPages < 1)) {
4010
+ throw new ItdConfigError(
4011
+ `maxPages \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0446\u0435\u043B\u044B\u043C \u0447\u0438\u0441\u043B\u043E\u043C \u043D\u0435 \u043C\u0435\u043D\u044C\u0448\u0435 1, \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: ${options.maxPages}`
4012
+ );
4013
+ }
3643
4014
  this.#options = options;
3644
4015
  this.#maxPages = options.maxPages ?? 1e3;
3645
4016
  this.#state = options.start ?? {};
@@ -3689,6 +4060,7 @@ var Paginator = class {
3689
4060
  * @param max сколько элементов достаточно; без него перебираются все страницы
3690
4061
  */
3691
4062
  async collect(max) {
4063
+ if (max !== void 0 && max <= 0) return [];
3692
4064
  const result = [];
3693
4065
  for await (const item of this) {
3694
4066
  result.push(item);
@@ -6164,6 +6536,13 @@ var VerificationResource = class extends BaseResource {
6164
6536
  };
6165
6537
 
6166
6538
  // src/client.ts
6539
+ var CLIENT_PLUGIN_REGISTRIES = /* @__PURE__ */ new WeakMap();
6540
+ function assertClientCanUsePlugin(client, plugin) {
6541
+ CLIENT_PLUGIN_REGISTRIES.get(client)?.assertCanAdd(plugin);
6542
+ }
6543
+ function assertClientCanUnusePlugin(client, name) {
6544
+ CLIENT_PLUGIN_REGISTRIES.get(client)?.assertCanRemove(name);
6545
+ }
6167
6546
  var ItdClient = class _ItdClient {
6168
6547
  #config;
6169
6548
  #http;
@@ -6201,13 +6580,10 @@ var ItdClient = class _ItdClient {
6201
6580
  subscription;
6202
6581
  /** Сведения о платформе: изменения, анонсы, баннер события. */
6203
6582
  platform;
6204
- /**
6205
- * Телеметрия просмотров.
6206
- *
6207
- * @experimental Недокументированные эндпоинты. Библиотека никогда не отправляет их сама.
6208
- */
6583
+ /** Телеметрия просмотров. */
6209
6584
  telemetry;
6210
6585
  constructor(options = {}, internals = {}) {
6586
+ CLIENT_PLUGIN_REGISTRIES.set(this, this.#plugins);
6211
6587
  const config = resolveConfig(options);
6212
6588
  this.#config = config;
6213
6589
  this.#jar = new CookieJar();
@@ -6219,18 +6595,22 @@ var ItdClient = class _ItdClient {
6219
6595
  this.#queues = queues;
6220
6596
  this.#ownsQueues = shared === void 0;
6221
6597
  let authManager;
6222
- const transport = new Transport(config, {
6223
- cookies: config.useCookieJar ? this.#jar : void 0,
6224
- getDeviceId: () => authManager.getDeviceId(),
6225
- onRateLimit: queues && config.rateLimit?.respectHeaders ? (limit, remaining, request) => this.#throttleByHeaders(limit, remaining, request) : void 0
6226
- });
6598
+ const hooks = this.#plugins.hooks(config.hooks);
6599
+ const transport = new Transport(
6600
+ { ...config, hooks },
6601
+ {
6602
+ cookies: config.useCookieJar ? this.#jar : void 0,
6603
+ getDeviceId: () => authManager.getDeviceId(),
6604
+ onRateLimit: queues && config.rateLimit?.respectHeaders ? (limit, remaining, request) => this.#throttleByHeaders(limit, remaining, request) : void 0
6605
+ }
6606
+ );
6227
6607
  this.#transport = transport;
6228
6608
  const pluginsLayer = createPluginsMiddleware(this.#plugins);
6229
6609
  const retriesLayer = createRetryMiddleware({
6230
6610
  retry: config.retry,
6231
6611
  rateLimitDelays: config.rateLimit?.retryDelays ?? [],
6232
6612
  pauseQueue: queues ? (ms, request) => queues.for(request.service).pause(ms) : void 0,
6233
- hooks: config.hooks,
6613
+ hooks,
6234
6614
  logger: config.logger,
6235
6615
  buildUrl: (request) => transport.buildUrl(request)
6236
6616
  });
@@ -6330,6 +6710,26 @@ var ItdClient = class _ItdClient {
6330
6710
  });
6331
6711
  return this;
6332
6712
  }
6713
+ /** Имена подключённых плагинов в фактическом порядке выполнения обёрток. */
6714
+ pluginNames() {
6715
+ return this.#plugins.names();
6716
+ }
6717
+ /** Подключён ли плагин с таким именем. */
6718
+ hasPlugin(name) {
6719
+ return this.#plugins.has(name);
6720
+ }
6721
+ /**
6722
+ * Отключает плагин и освобождает заведённые им ресурсы.
6723
+ *
6724
+ * Новые запросы перестают видеть плагин сразу. Очистка дождётся логического запроса,
6725
+ * который уже проходил через его обёртку.
6726
+ *
6727
+ * @returns `false`, если такого плагина не было
6728
+ * @throws {ItdConfigError} если от плагина зависит другой подключённый плагин
6729
+ */
6730
+ unuse(name) {
6731
+ return this.#plugins.remove(name);
6732
+ }
6333
6733
  /**
6334
6734
  * Регистрирует сервис платформы — домен, отличный от основного.
6335
6735
  *
@@ -6435,13 +6835,25 @@ var ItdClient = class _ItdClient {
6435
6835
  * ```ts
6436
6836
  * await using itd = new ItdClient({ auth: token });
6437
6837
  * // …работа…
6438
- * // close() вызовется сам на выходе из блока
6838
+ * // dispose() вызовется сам на выходе из блока
6439
6839
  * ```
6440
6840
  */
6441
6841
  async close() {
6442
6842
  this.#disconnectStreams();
6443
6843
  if (this.#ownsQueues) this.#queues?.stop();
6444
6844
  }
6845
+ /**
6846
+ * Окончательно освобождает клиент: выполняет {@link close} и отключает все плагины.
6847
+ *
6848
+ * В отличие от `close()`, после `dispose()` плагины не восстанавливаются автоматически.
6849
+ * Сам клиент остаётся пригоден для обычных запросов; при необходимости плагины можно
6850
+ * подключить заново через {@link use}.
6851
+ */
6852
+ async dispose() {
6853
+ const results = await Promise.allSettled([this.close(), this.#plugins.dispose()]);
6854
+ const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
6855
+ if (errors.length > 0) throw new AggregateError(errors, "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u044C \u043A\u043B\u0438\u0435\u043D\u0442");
6856
+ }
6445
6857
  /** Завершает потоки до того, как запросы начнут использовать другой аккаунт. */
6446
6858
  #disconnectStreams() {
6447
6859
  for (const stream of [...this.#streams]) stream.disconnect();
@@ -6449,7 +6861,7 @@ var ItdClient = class _ItdClient {
6449
6861
  }
6450
6862
  /** Позволяет использовать клиент с `await using`. */
6451
6863
  [Symbol.asyncDispose]() {
6452
- return this.close();
6864
+ return this.dispose();
6453
6865
  }
6454
6866
  static {
6455
6867
  if (typeof Symbol.asyncDispose !== "symbol") {
@@ -6648,10 +7060,13 @@ var ItdAccounts = class _ItdAccounts {
6648
7060
  #eventUnsubscribers = /* @__PURE__ */ new Map();
6649
7061
  /** Плагины для всех: и для уже заведённых аккаунтов, и для будущих. */
6650
7062
  #plugins;
7063
+ /** Имена плагинов, чья асинхронная очистка ещё не завершилась. */
7064
+ #removingPlugins = /* @__PURE__ */ new Set();
6651
7065
  /** Общая очередь. `undefined`, когда у каждого аккаунта своя. */
6652
7066
  #queues;
6653
7067
  #rateLimitScope;
6654
7068
  #emitter;
7069
+ #logger;
6655
7070
  #createClient;
6656
7071
  constructor(options = {}, internals = {}) {
6657
7072
  const { storage, plugins, rateLimitScope, ...base } = options;
@@ -6660,19 +7075,13 @@ var ItdAccounts = class _ItdAccounts {
6660
7075
  }
6661
7076
  this.#base = base;
6662
7077
  this.#storage = storage ?? new MemoryMultiTokenStorage();
6663
- this.#plugins = [];
6664
- for (const plugin of plugins ?? []) {
6665
- validatePluginDefinition(plugin);
6666
- if (this.#plugins.some((added) => added.name === plugin.name)) {
6667
- throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin.name}\xBB \u0443\u0436\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D`);
6668
- }
6669
- this.#plugins.push(plugin);
6670
- }
7078
+ this.#plugins = orderPluginDefinitions(plugins ?? []);
6671
7079
  this.#rateLimitScope = rateLimitScope ?? "account";
6672
7080
  this.#createClient = internals.createClient ?? ((clientOptions, clientInternals) => new ItdClient(clientOptions, clientInternals));
6673
7081
  const rateLimit = this.#rateLimitScope === "shared" ? resolveRateLimit(base.rateLimit) : void 0;
6674
7082
  this.#queues = rateLimit ? new RequestQueuePool(rateLimit) : void 0;
6675
7083
  const logger = typeof base.logger === "object" ? base.logger : void 0;
7084
+ this.#logger = logger;
6676
7085
  this.#emitter = new Emitter(
6677
7086
  (error) => reportListenerError(logger, "\u0430\u043A\u043A\u0430\u0443\u043D\u0442\u043E\u0432", error)
6678
7087
  );
@@ -6738,6 +7147,11 @@ var ItdAccounts = class _ItdAccounts {
6738
7147
  for (const plugin of this.#plugins) client.use(plugin);
6739
7148
  } catch (error) {
6740
7149
  storageControl.revoke();
7150
+ if (client) {
7151
+ void client.dispose().catch((cleanupError) => {
7152
+ this.#reportPluginCleanup("\u043D\u0435\u0443\u0434\u0430\u0447\u043D\u043E\u0433\u043E \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0438\u044F \u0430\u043A\u043A\u0430\u0443\u043D\u0442\u0430", cleanupError);
7153
+ });
7154
+ }
6741
7155
  throw error;
6742
7156
  }
6743
7157
  const unsubscribers = this.#forwardEvents(name, client);
@@ -6819,7 +7233,7 @@ var ItdAccounts = class _ItdAccounts {
6819
7233
  try {
6820
7234
  const errors = [];
6821
7235
  const closing = await Promise.allSettled([
6822
- client.close(),
7236
+ client.dispose(),
6823
7237
  storageControl?.drain() ?? Promise.resolve()
6824
7238
  ]);
6825
7239
  for (const result of closing) {
@@ -6847,14 +7261,64 @@ var ItdAccounts = class _ItdAccounts {
6847
7261
  * ```
6848
7262
  */
6849
7263
  use(plugin) {
6850
- validatePluginDefinition(plugin);
6851
- if (this.#plugins.some((added) => added.name === plugin?.name)) {
6852
- throw new ItdConfigError(`\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin.name}\xBB \u0443\u0436\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D`);
7264
+ if (this.#removingPlugins.has(plugin?.name)) {
7265
+ throw new ItdConfigError(
7266
+ `\u043F\u043B\u0430\u0433\u0438\u043D \xAB${plugin.name}\xBB \u0435\u0449\u0451 \u043E\u0442\u043A\u043B\u044E\u0447\u0430\u0435\u0442\u0441\u044F; \u0434\u043E\u0436\u0434\u0438\u0442\u0435\u0441\u044C \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F accounts.unuse()`
7267
+ );
6853
7268
  }
6854
- for (const client of this.#clients.values()) client.use(plugin);
6855
- this.#plugins.push(plugin);
7269
+ const ordered = orderPluginDefinitions([...this.#plugins, plugin]);
7270
+ const clients = [...this.#clients.values()];
7271
+ for (const client of clients) assertClientCanUsePlugin(client, plugin);
7272
+ const installed = [];
7273
+ try {
7274
+ for (const client of clients) {
7275
+ client.use(plugin);
7276
+ installed.push(client);
7277
+ }
7278
+ } catch (error) {
7279
+ for (const client of installed.reverse()) {
7280
+ void client.unuse(plugin.name).catch((cleanupError) => {
7281
+ this.#reportPluginCleanup(`\u043E\u0442\u043A\u0430\u0442\u0430 \u043F\u043B\u0430\u0433\u0438\u043D\u0430 \xAB${plugin.name}\xBB`, cleanupError);
7282
+ });
7283
+ }
7284
+ throw error;
7285
+ }
7286
+ this.#plugins.splice(0, this.#plugins.length, ...ordered);
6856
7287
  return this;
6857
7288
  }
7289
+ /** Имена общих плагинов в фактическом порядке выполнения обёрток. */
7290
+ pluginNames() {
7291
+ return this.#plugins.map((plugin) => plugin.name);
7292
+ }
7293
+ /** Подключён ли общий плагин с таким именем. */
7294
+ hasPlugin(name) {
7295
+ return this.#plugins.some((plugin) => plugin.name === name);
7296
+ }
7297
+ /**
7298
+ * Отключает общий плагин у существующих клиентов и не применяет его к будущим.
7299
+ *
7300
+ * @returns `false`, если такого плагина не было
7301
+ * @throws {ItdConfigError} если от плагина зависит другой общий плагин
7302
+ */
7303
+ async unuse(name) {
7304
+ const index = this.#plugins.findIndex((plugin) => plugin.name === name);
7305
+ if (index < 0) return false;
7306
+ assertPluginRemovable(this.#plugins, name);
7307
+ const clients = [...this.#clients.values()];
7308
+ for (const client of clients) assertClientCanUnusePlugin(client, name);
7309
+ this.#plugins.splice(index, 1);
7310
+ this.#removingPlugins.add(name);
7311
+ try {
7312
+ const results = await Promise.allSettled(clients.map((client) => client.unuse(name)));
7313
+ const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
7314
+ if (errors.length > 0) {
7315
+ throw new AggregateError(errors, `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB \u0443 \u0432\u0441\u0435\u0445 \u0430\u043A\u043A\u0430\u0443\u043D\u0442\u043E\u0432`);
7316
+ }
7317
+ return true;
7318
+ } finally {
7319
+ this.#removingPlugins.delete(name);
7320
+ }
7321
+ }
6858
7322
  /**
6859
7323
  * Подписывается на события авторизации всех аккаунтов сразу.
6860
7324
  *
@@ -6892,16 +7356,32 @@ var ItdAccounts = class _ItdAccounts {
6892
7356
  * ```ts
6893
7357
  * await using accounts = new ItdAccounts({ storage });
6894
7358
  * // …работа…
6895
- * // close() вызовется сам на выходе из блока
7359
+ * // dispose() вызовется сам на выходе из блока
6896
7360
  * ```
6897
7361
  */
6898
7362
  async close() {
6899
7363
  await Promise.all([...this.#clients.values()].map((client) => client.close()));
6900
7364
  this.#queues?.stop();
6901
7365
  }
7366
+ /**
7367
+ * Окончательно освобождает контейнер и отключает общие плагины у всех аккаунтов.
7368
+ *
7369
+ * Для временной остановки потоков и очереди без отключения плагинов используйте
7370
+ * {@link close}.
7371
+ */
7372
+ async dispose() {
7373
+ const results = await Promise.allSettled([
7374
+ ...[...this.#clients.values()].map((client) => client.dispose()),
7375
+ Promise.resolve().then(() => this.#queues?.stop())
7376
+ ]);
7377
+ this.#plugins.splice(0);
7378
+ this.#removingPlugins.clear();
7379
+ const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
7380
+ if (errors.length > 0) throw new AggregateError(errors, "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u044C \u0430\u043A\u043A\u0430\u0443\u043D\u0442\u044B");
7381
+ }
6902
7382
  /** Позволяет использовать контейнер с `await using`. */
6903
7383
  [Symbol.asyncDispose]() {
6904
- return this.close();
7384
+ return this.dispose();
6905
7385
  }
6906
7386
  static {
6907
7387
  if (typeof Symbol.asyncDispose !== "symbol") {
@@ -6927,6 +7407,12 @@ var ItdAccounts = class _ItdAccounts {
6927
7407
  storage
6928
7408
  };
6929
7409
  }
7410
+ /** Не теряет ошибку фонового teardown, который синхронный API не может await-нуть. */
7411
+ #reportPluginCleanup(scope, error) {
7412
+ const message = `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C teardown \u043F\u043E\u0441\u043B\u0435 ${scope}`;
7413
+ if (this.#logger) this.#logger.error(message, error);
7414
+ else console.error(`[itd-api] ${message}`, error);
7415
+ }
6930
7416
  /** Ретранслирует события клиента наружу, добавляя к ним имя аккаунта. */
6931
7417
  #forwardEvents(account, client) {
6932
7418
  return [
@@ -7273,5 +7759,5 @@ function statusDays(service) {
7273
7759
  }
7274
7760
 
7275
7761
  export { ALLOWED_MIME_TYPES, AUDIO_MIME_TYPES, AUTH_FLAG_COOKIE, AUTH_PATHS, AccessType, AttachmentType, BUILT_IN_SERVICES, CommentSort, DEFAULT_BASE_URL, DEFAULT_STATUS_BASE_URL, DEFAULT_TIMEOUT, DEFAULT_UPLOAD_TIMEOUT, DEFAULT_USER_AGENT, DEVICE_ID_HEADER, DetectedRuntime, FeedTab, IMAGE_MIME_TYPES, IncidentKind, InteractionType, ItdAbortError, ItdAccounts, ItdApiError, ItdApiErrorKind, ItdAuthError, ItdClient, ItdConfigError, ItdConflictError, ItdError, ItdErrorCode, ItdErrorKind, ItdForbiddenError, ItdNetworkError, ItdNotFoundError, ItdPhoneVerificationError, ItdRateLimitError, ItdRealtime, ItdServerError, ItdTimeoutError, ItdValidationError, LIBRARY_VERSION, LikesVisibility, LocalStorageTokenStorage, MAX_RECONNECT_ATTEMPTS, MemoryMultiTokenStorage, MemoryTokenStorage, NOTIFICATION_TYPE_ALIASES, NotificationType, OAuthProvider, PaginationMode, Paginator, RECONNECT_BACKOFF, RECONNECT_JITTER, REFRESH_COOKIE, REFRESH_COOKIE_PATH, REQUEST_OPTION_KEYS, RealtimeStatus, RealtimeTransportKind, ReportReason, ReportTargetType, RuntimeMode, STATUS_SERVICE, STREAM_PATH, ServiceRegistry, ServiceState, SignInStatus, SpanType, TURNSTILE_SITE_KEY, UnauthorizedStreamError, VIDEO_MIME_TYPES, ViewReason, ViewSource, WallAccess, autoSpans, canonicalNotificationType, comment, copySession, createAccounts, createClient, createMultiTokenStorage, createRecordMultiStorage, createTokenStorage, formatNotificationText, isBuilder, isItdApiError, isItdAuthError, isItdConflictError, isItdError, isItdForbiddenError, isItdNotFoundError, isItdPhoneVerificationError, isItdRateLimitError, isItdServerError, isItdValidationError, isKnownNotificationType, isMyProfile, mapPage, markup, normalizeNotification, poll, post, readNotificationEvent, readUnreadCountEvent, renderSpans, report, resolveNotificationUrl, scopedTokenStorage, statusDays, toDate, utcStampToIso };
7276
- //# sourceMappingURL=chunk-TB7HW3VX.js.map
7277
- //# sourceMappingURL=chunk-TB7HW3VX.js.map
7762
+ //# sourceMappingURL=chunk-6FB4HTKH.js.map
7763
+ //# sourceMappingURL=chunk-6FB4HTKH.js.map