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.
- package/README.md +8 -4
- package/dist/{chunk-TB7HW3VX.js → chunk-6FB4HTKH.js} +660 -174
- package/dist/chunk-6FB4HTKH.js.map +1 -0
- package/dist/{chunk-QD4UHJFF.cjs → chunk-73CISRBG.cjs} +660 -174
- package/dist/chunk-73CISRBG.cjs.map +1 -0
- package/dist/{index-CrlTO7sR.d.cts → index-BZF4K90s.d.cts} +125 -22
- package/dist/{index-CrlTO7sR.d.ts → index-BZF4K90s.d.ts} +125 -22
- package/dist/index.cjs +109 -109
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/node.cjs +112 -112
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/guides/README.md +2 -0
- package/guides/multi-accounts/README.md +2 -1
- package/guides/plugins/README.md +93 -2
- package/guides/reference/README.md +67 -0
- package/guides/reference/accounts.md +101 -0
- package/guides/reference/auth.md +141 -0
- package/guides/reference/builders.md +135 -0
- package/guides/reference/client.md +184 -0
- package/guides/reference/comments.md +58 -0
- package/guides/reference/discovery.md +81 -0
- package/guides/reference/enums.md +103 -0
- package/guides/reference/errors.md +107 -0
- package/guides/reference/files.md +73 -0
- package/guides/reference/models.md +448 -0
- package/guides/reference/notifications.md +77 -0
- package/guides/reference/pagination.md +82 -0
- package/guides/reference/platform.md +47 -0
- package/guides/reference/posts.md +157 -0
- package/guides/reference/realtime.md +78 -0
- package/guides/reference/reports.md +28 -0
- package/guides/reference/subscription.md +41 -0
- package/guides/reference/users.md +146 -0
- package/guides/reference/verification.md +24 -0
- package/package.json +1 -1
- package/dist/chunk-QD4UHJFF.cjs.map +0 -1
- 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
|
|
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
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
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
|
|
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
|
|
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
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
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
|
|
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
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
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
|
-
|
|
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
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
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
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
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
|
|
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
|
-
* //
|
|
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.
|
|
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.
|
|
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
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
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
|
-
|
|
6855
|
-
this.#
|
|
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
|
-
* //
|
|
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.
|
|
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-
|
|
7277
|
-
//# sourceMappingURL=chunk-
|
|
7762
|
+
//# sourceMappingURL=chunk-6FB4HTKH.js.map
|
|
7763
|
+
//# sourceMappingURL=chunk-6FB4HTKH.js.map
|