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
|
@@ -621,8 +621,8 @@ var ItdTimeoutError = class extends ItdError {
|
|
|
621
621
|
}
|
|
622
622
|
};
|
|
623
623
|
var ItdAbortError = class extends ItdError {
|
|
624
|
-
constructor(message = "\u0417\u0430\u043F\u0440\u043E\u0441 \u043E\u0442\u043C\u0435\u043D\u0451\u043D") {
|
|
625
|
-
super(ItdErrorKind.Abort, message);
|
|
624
|
+
constructor(message = "\u0417\u0430\u043F\u0440\u043E\u0441 \u043E\u0442\u043C\u0435\u043D\u0451\u043D", options) {
|
|
625
|
+
super(ItdErrorKind.Abort, message, options);
|
|
626
626
|
this.name = "ItdAbortError";
|
|
627
627
|
}
|
|
628
628
|
};
|
|
@@ -911,6 +911,15 @@ var AuthManager = class {
|
|
|
911
911
|
#deviceId;
|
|
912
912
|
/** Общий промис первичной выдачи `deviceId` — чтобы параллельные запросы получили один. */
|
|
913
913
|
#deviceIdLoading = null;
|
|
914
|
+
/**
|
|
915
|
+
* Счётчик смен владельца авторизации.
|
|
916
|
+
*
|
|
917
|
+
* Растёт при каждой операции, которая заменяет или очищает сессию извне: `clear`,
|
|
918
|
+
* `setSession`, `setAccessToken`, вход. `#performRefresh` снимает его значение перед
|
|
919
|
+
* сетевым запросом и сверяет перед записью — иначе запоздавший ответ обновления мог бы
|
|
920
|
+
* воскресить уже очищенную сессию поверх `signOut`.
|
|
921
|
+
*/
|
|
922
|
+
#authEpoch = 0;
|
|
914
923
|
constructor(config, send, jar, hooks = {}) {
|
|
915
924
|
this.#config = config;
|
|
916
925
|
this.#send = send;
|
|
@@ -956,6 +965,10 @@ var AuthManager = class {
|
|
|
956
965
|
#rotateAuthScope() {
|
|
957
966
|
this.#authScope = nextAuthScope();
|
|
958
967
|
}
|
|
968
|
+
/** Отмечает смену владельца авторизации — обесценивает результат идущего обновления. */
|
|
969
|
+
#invalidateInFlight() {
|
|
970
|
+
this.#authEpoch += 1;
|
|
971
|
+
}
|
|
959
972
|
#identityForToken(accessToken) {
|
|
960
973
|
const token = accessToken ? readTokenIdentity(accessToken) : {};
|
|
961
974
|
return {
|
|
@@ -1101,6 +1114,7 @@ var AuthManager = class {
|
|
|
1101
1114
|
/** Сохраняет токен, полученный извне, — например после подтверждения OTP. */
|
|
1102
1115
|
async setAccessToken(accessToken) {
|
|
1103
1116
|
await this.#loadSession();
|
|
1117
|
+
this.#invalidateInFlight();
|
|
1104
1118
|
this.#transitionAuth(accessToken);
|
|
1105
1119
|
await this.#saveSession({ ...this.#session ?? {}, accessToken, obtainedAt: Date.now() });
|
|
1106
1120
|
this.#emitter.emit("tokens", { accessToken });
|
|
@@ -1123,6 +1137,7 @@ var AuthManager = class {
|
|
|
1123
1137
|
/** Заменяет сессию и связанные с ней cookie целиком. */
|
|
1124
1138
|
async setSession(session) {
|
|
1125
1139
|
await this.#loadSession();
|
|
1140
|
+
this.#invalidateInFlight();
|
|
1126
1141
|
this.#transitionAuth(session.accessToken);
|
|
1127
1142
|
this.#jar.clear();
|
|
1128
1143
|
this.#jar.deserialize(session.cookies);
|
|
@@ -1139,6 +1154,7 @@ var AuthManager = class {
|
|
|
1139
1154
|
*/
|
|
1140
1155
|
async clear() {
|
|
1141
1156
|
await this.#loadSession();
|
|
1157
|
+
this.#invalidateInFlight();
|
|
1142
1158
|
this.#transitionAuth(void 0);
|
|
1143
1159
|
this.#session = null;
|
|
1144
1160
|
this.#jar.clear();
|
|
@@ -1221,6 +1237,7 @@ var AuthManager = class {
|
|
|
1221
1237
|
if (!this.#hasRefreshSession()) {
|
|
1222
1238
|
return this.#reloginOrNull();
|
|
1223
1239
|
}
|
|
1240
|
+
const epoch = this.#authEpoch;
|
|
1224
1241
|
try {
|
|
1225
1242
|
const payload = await this.#send({
|
|
1226
1243
|
method: "POST",
|
|
@@ -1233,6 +1250,9 @@ var AuthManager = class {
|
|
|
1233
1250
|
});
|
|
1234
1251
|
const accessToken = readAccessToken(payload);
|
|
1235
1252
|
if (!accessToken) return this.#reloginOrNull();
|
|
1253
|
+
if (this.#authEpoch !== epoch) {
|
|
1254
|
+
return this.#session?.accessToken ?? null;
|
|
1255
|
+
}
|
|
1236
1256
|
const rotated = this.#jar.getValue(
|
|
1237
1257
|
REFRESH_COOKIE,
|
|
1238
1258
|
this.#config.baseUrl + REFRESH_COOKIE_PATH
|
|
@@ -1248,6 +1268,9 @@ var AuthManager = class {
|
|
|
1248
1268
|
return accessToken;
|
|
1249
1269
|
} catch (error) {
|
|
1250
1270
|
if (error instanceof ItdApiError) {
|
|
1271
|
+
if (this.#authEpoch !== epoch) {
|
|
1272
|
+
return this.#session?.accessToken ?? null;
|
|
1273
|
+
}
|
|
1251
1274
|
this.#transitionAuth(void 0);
|
|
1252
1275
|
this.#session = null;
|
|
1253
1276
|
this.#jar.clear();
|
|
@@ -1317,6 +1340,7 @@ var AuthManager = class {
|
|
|
1317
1340
|
"\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."
|
|
1318
1341
|
);
|
|
1319
1342
|
}
|
|
1343
|
+
this.#invalidateInFlight();
|
|
1320
1344
|
this.#transitionAuth(accessToken);
|
|
1321
1345
|
await this.#saveSession({ accessToken, obtainedAt: Date.now() });
|
|
1322
1346
|
this.#emitter.emit("tokens", { accessToken });
|
|
@@ -1384,7 +1408,7 @@ function normalizeBaseUrl(baseUrl) {
|
|
|
1384
1408
|
}
|
|
1385
1409
|
|
|
1386
1410
|
// src/core/version.ts
|
|
1387
|
-
var LIBRARY_VERSION = "0.0
|
|
1411
|
+
var LIBRARY_VERSION = "0.1.0";
|
|
1388
1412
|
|
|
1389
1413
|
// src/core/config.ts
|
|
1390
1414
|
var DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
|
|
@@ -1669,6 +1693,396 @@ function withLayerHeaders(request, headers) {
|
|
|
1669
1693
|
return { ...request, layerHeaders: { ...request.layerHeaders, ...headers } };
|
|
1670
1694
|
}
|
|
1671
1695
|
|
|
1696
|
+
// src/core/plugins.ts
|
|
1697
|
+
var NO_KEYS = /* @__PURE__ */ new Set();
|
|
1698
|
+
var RESERVED_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
1699
|
+
"signal",
|
|
1700
|
+
"timeout",
|
|
1701
|
+
"headers",
|
|
1702
|
+
"retry",
|
|
1703
|
+
"method",
|
|
1704
|
+
"path",
|
|
1705
|
+
"service",
|
|
1706
|
+
"baseUrl",
|
|
1707
|
+
"query",
|
|
1708
|
+
"body",
|
|
1709
|
+
"skipAuth",
|
|
1710
|
+
"skipAuthRefresh",
|
|
1711
|
+
"skipQueue",
|
|
1712
|
+
"raw"
|
|
1713
|
+
]);
|
|
1714
|
+
var RELATION_FIELDS = ["requires", "conflicts", "before", "after"];
|
|
1715
|
+
var HOOK_FIELDS = ["onRequest", "onResponse", "onError", "onRetry"];
|
|
1716
|
+
function validateNameList(plugin, field) {
|
|
1717
|
+
const values = plugin[field];
|
|
1718
|
+
if (values === void 0) return;
|
|
1719
|
+
if (!Array.isArray(values)) {
|
|
1720
|
+
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`);
|
|
1721
|
+
}
|
|
1722
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1723
|
+
for (const value of values) {
|
|
1724
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1725
|
+
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`);
|
|
1726
|
+
}
|
|
1727
|
+
if (value === plugin.name) {
|
|
1728
|
+
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}`);
|
|
1729
|
+
}
|
|
1730
|
+
if (seen.has(value)) {
|
|
1731
|
+
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`);
|
|
1732
|
+
}
|
|
1733
|
+
seen.add(value);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
function validateHooks(plugin, hooks) {
|
|
1737
|
+
if (typeof hooks !== "object" || hooks === null) {
|
|
1738
|
+
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`);
|
|
1739
|
+
}
|
|
1740
|
+
for (const field of HOOK_FIELDS) {
|
|
1741
|
+
if (hooks[field] !== void 0 && typeof hooks[field] !== "function") {
|
|
1742
|
+
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`);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
function validatePluginDefinition(plugin) {
|
|
1747
|
+
if (typeof plugin?.install !== "function") {
|
|
1748
|
+
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()");
|
|
1749
|
+
}
|
|
1750
|
+
const name = plugin.name;
|
|
1751
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
1752
|
+
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");
|
|
1753
|
+
}
|
|
1754
|
+
const keys = plugin.optionKeys ?? [];
|
|
1755
|
+
if (!Array.isArray(keys)) {
|
|
1756
|
+
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`);
|
|
1757
|
+
}
|
|
1758
|
+
for (const key of keys) {
|
|
1759
|
+
if (typeof key !== "string" || key.trim() === "") {
|
|
1760
|
+
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`);
|
|
1761
|
+
}
|
|
1762
|
+
if (RESERVED_OPTION_KEYS.has(key)) {
|
|
1763
|
+
throw new ItdConfigError(
|
|
1764
|
+
`\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(", ")}`
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
for (const field of RELATION_FIELDS) validateNameList(plugin, field);
|
|
1769
|
+
}
|
|
1770
|
+
function addEdge(from, to, edges, indegree) {
|
|
1771
|
+
const targets = edges.get(from);
|
|
1772
|
+
if (!targets || targets.has(to)) return;
|
|
1773
|
+
targets.add(to);
|
|
1774
|
+
indegree.set(to, (indegree.get(to) ?? 0) + 1);
|
|
1775
|
+
}
|
|
1776
|
+
function orderPluginDefinitions(plugins) {
|
|
1777
|
+
const entries = [];
|
|
1778
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1779
|
+
for (const [sequence, plugin] of plugins.entries()) {
|
|
1780
|
+
validatePluginDefinition(plugin);
|
|
1781
|
+
if (byName.has(plugin.name)) {
|
|
1782
|
+
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`);
|
|
1783
|
+
}
|
|
1784
|
+
const entry = { plugin, sequence };
|
|
1785
|
+
entries.push(entry);
|
|
1786
|
+
byName.set(plugin.name, entry);
|
|
1787
|
+
}
|
|
1788
|
+
for (const { plugin } of entries) {
|
|
1789
|
+
for (const required of plugin.requires ?? []) {
|
|
1790
|
+
if (!byName.has(required)) {
|
|
1791
|
+
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`);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
for (const conflict of plugin.conflicts ?? []) {
|
|
1795
|
+
if (byName.has(conflict)) {
|
|
1796
|
+
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`);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
for (const { plugin: other } of entries) {
|
|
1800
|
+
if (other.conflicts?.includes(plugin.name)) {
|
|
1801
|
+
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`);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
const edges = /* @__PURE__ */ new Map();
|
|
1806
|
+
const indegree = /* @__PURE__ */ new Map();
|
|
1807
|
+
for (const { plugin } of entries) {
|
|
1808
|
+
edges.set(plugin.name, /* @__PURE__ */ new Set());
|
|
1809
|
+
indegree.set(plugin.name, 0);
|
|
1810
|
+
}
|
|
1811
|
+
for (const { plugin } of entries) {
|
|
1812
|
+
for (const required of plugin.requires ?? []) {
|
|
1813
|
+
addEdge(required, plugin.name, edges, indegree);
|
|
1814
|
+
}
|
|
1815
|
+
for (const target of plugin.before ?? []) {
|
|
1816
|
+
if (byName.has(target)) addEdge(plugin.name, target, edges, indegree);
|
|
1817
|
+
}
|
|
1818
|
+
for (const target of plugin.after ?? []) {
|
|
1819
|
+
if (byName.has(target)) addEdge(target, plugin.name, edges, indegree);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
const ready = entries.filter(({ plugin }) => indegree.get(plugin.name) === 0);
|
|
1823
|
+
ready.sort((a, b) => a.sequence - b.sequence);
|
|
1824
|
+
const ordered = [];
|
|
1825
|
+
while (ready.length > 0) {
|
|
1826
|
+
const current = ready.shift();
|
|
1827
|
+
if (!current) break;
|
|
1828
|
+
ordered.push(current.plugin);
|
|
1829
|
+
for (const target of edges.get(current.plugin.name) ?? []) {
|
|
1830
|
+
const next = (indegree.get(target) ?? 0) - 1;
|
|
1831
|
+
indegree.set(target, next);
|
|
1832
|
+
if (next === 0) {
|
|
1833
|
+
const entry = byName.get(target);
|
|
1834
|
+
if (entry) {
|
|
1835
|
+
ready.push(entry);
|
|
1836
|
+
ready.sort((a, b) => a.sequence - b.sequence);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
if (ordered.length !== entries.length) {
|
|
1842
|
+
const cycle = entries.filter(({ plugin }) => (indegree.get(plugin.name) ?? 0) > 0).map(({ plugin }) => plugin.name);
|
|
1843
|
+
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 ")}`);
|
|
1844
|
+
}
|
|
1845
|
+
return ordered;
|
|
1846
|
+
}
|
|
1847
|
+
function assertPluginRemovable(plugins, name) {
|
|
1848
|
+
const dependent = plugins.find((plugin) => plugin.requires?.includes(name));
|
|
1849
|
+
if (dependent) {
|
|
1850
|
+
throw new ItdConfigError(
|
|
1851
|
+
`\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`
|
|
1852
|
+
);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
var REQUEST_HOOK_DISPATCHERS = /* @__PURE__ */ new WeakMap();
|
|
1856
|
+
var PLUGIN_HOOK_SCOPE = /* @__PURE__ */ Symbol("itd-api.plugin-hooks");
|
|
1857
|
+
async function dispatchRequestHook(hooks, field, context, request) {
|
|
1858
|
+
const dispatcher = REQUEST_HOOK_DISPATCHERS.get(hooks);
|
|
1859
|
+
if (dispatcher) {
|
|
1860
|
+
await dispatcher(field, context, request);
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
const hook = hooks[field];
|
|
1864
|
+
await hook?.(context);
|
|
1865
|
+
}
|
|
1866
|
+
var PluginRegistry = class {
|
|
1867
|
+
#entries = /* @__PURE__ */ new Map();
|
|
1868
|
+
#optionKeys = /* @__PURE__ */ new Set();
|
|
1869
|
+
#removing = /* @__PURE__ */ new Set();
|
|
1870
|
+
#cleanups = /* @__PURE__ */ new Set();
|
|
1871
|
+
#ordered = [];
|
|
1872
|
+
/** Сколько плагинов подключено. */
|
|
1873
|
+
get size() {
|
|
1874
|
+
return this.#entries.size;
|
|
1875
|
+
}
|
|
1876
|
+
/** Имена опций активных плагинов. */
|
|
1877
|
+
get optionKeys() {
|
|
1878
|
+
return this.#optionKeys.size === 0 ? NO_KEYS : this.#optionKeys;
|
|
1879
|
+
}
|
|
1880
|
+
/** Имена плагинов в фактическом порядке выполнения. */
|
|
1881
|
+
names() {
|
|
1882
|
+
return this.#ordered.map(({ plugin }) => plugin.name);
|
|
1883
|
+
}
|
|
1884
|
+
/** Подключён ли плагин с таким именем. */
|
|
1885
|
+
has(name) {
|
|
1886
|
+
return this.#entries.has(name);
|
|
1887
|
+
}
|
|
1888
|
+
/** Проверяет добавление без вызова `install()`. @internal */
|
|
1889
|
+
assertCanAdd(plugin) {
|
|
1890
|
+
validatePluginDefinition(plugin);
|
|
1891
|
+
if (this.#removing.has(plugin.name)) {
|
|
1892
|
+
throw new ItdConfigError(
|
|
1893
|
+
`\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()`
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
orderPluginDefinitions([...this.#ordered.map((entry) => entry.plugin), plugin]);
|
|
1897
|
+
}
|
|
1898
|
+
/** Проверяет удаление без изменения реестра. @internal */
|
|
1899
|
+
assertCanRemove(name) {
|
|
1900
|
+
const entry = this.#entries.get(name);
|
|
1901
|
+
if (!entry) return;
|
|
1902
|
+
assertPluginRemovable(
|
|
1903
|
+
this.#ordered.map((current) => current.plugin),
|
|
1904
|
+
name
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Подключает плагин.
|
|
1909
|
+
*
|
|
1910
|
+
* @throws {ItdConfigError} если плагин задан неверно, уже подключён, нарушает зависимости
|
|
1911
|
+
* или заявил занятое имя опции
|
|
1912
|
+
*/
|
|
1913
|
+
add(plugin, context) {
|
|
1914
|
+
this.assertCanAdd(plugin);
|
|
1915
|
+
const ordered = orderPluginDefinitions([...this.#ordered.map((entry) => entry.plugin), plugin]);
|
|
1916
|
+
const transformers = [];
|
|
1917
|
+
const hooks = [];
|
|
1918
|
+
const installed = plugin.install({
|
|
1919
|
+
...context,
|
|
1920
|
+
use: (transformer) => {
|
|
1921
|
+
if (typeof transformer !== "function") {
|
|
1922
|
+
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`);
|
|
1923
|
+
}
|
|
1924
|
+
transformers.push(transformer);
|
|
1925
|
+
},
|
|
1926
|
+
useHooks: (value) => {
|
|
1927
|
+
validateHooks(plugin.name, value);
|
|
1928
|
+
hooks.push({ ...value });
|
|
1929
|
+
}
|
|
1930
|
+
});
|
|
1931
|
+
const teardown = typeof installed === "function" ? installed : void 0;
|
|
1932
|
+
this.#entries.set(plugin.name, {
|
|
1933
|
+
plugin,
|
|
1934
|
+
transformers,
|
|
1935
|
+
hooks,
|
|
1936
|
+
teardown,
|
|
1937
|
+
activeRequests: 0,
|
|
1938
|
+
drain: void 0,
|
|
1939
|
+
finishDrain: void 0
|
|
1940
|
+
});
|
|
1941
|
+
this.#ordered = ordered.map((definition) => {
|
|
1942
|
+
const entry = this.#entries.get(definition.name);
|
|
1943
|
+
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`);
|
|
1944
|
+
return entry;
|
|
1945
|
+
});
|
|
1946
|
+
this.#rebuildOptionKeys();
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Отключает плагин и вызывает его функцию очистки.
|
|
1950
|
+
*
|
|
1951
|
+
* Новые запросы перестают видеть плагин сразу. Если его обёртка уже выполняется,
|
|
1952
|
+
* очистка дождётся завершения этого логического запроса.
|
|
1953
|
+
*
|
|
1954
|
+
* @returns `false`, если такого плагина не было
|
|
1955
|
+
*/
|
|
1956
|
+
async remove(name) {
|
|
1957
|
+
const entry = this.#entries.get(name);
|
|
1958
|
+
if (!entry) return false;
|
|
1959
|
+
this.assertCanRemove(name);
|
|
1960
|
+
this.#entries.delete(name);
|
|
1961
|
+
this.#ordered = this.#ordered.filter((current) => current !== entry);
|
|
1962
|
+
this.#rebuildOptionKeys();
|
|
1963
|
+
this.#removing.add(name);
|
|
1964
|
+
const cleanup = this.#trackCleanup(
|
|
1965
|
+
(async () => {
|
|
1966
|
+
await this.#waitForDrain(entry);
|
|
1967
|
+
await entry.teardown?.();
|
|
1968
|
+
})()
|
|
1969
|
+
);
|
|
1970
|
+
try {
|
|
1971
|
+
await cleanup;
|
|
1972
|
+
return true;
|
|
1973
|
+
} finally {
|
|
1974
|
+
this.#removing.delete(name);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
/**
|
|
1978
|
+
* Отключает все плагины окончательно.
|
|
1979
|
+
*
|
|
1980
|
+
* Очистка идёт изнутри наружу — в порядке, обратном выполнению обёрток.
|
|
1981
|
+
*/
|
|
1982
|
+
async dispose() {
|
|
1983
|
+
const entries = [...this.#ordered].reverse();
|
|
1984
|
+
const previousCleanups = [...this.#cleanups];
|
|
1985
|
+
this.#entries.clear();
|
|
1986
|
+
this.#ordered = [];
|
|
1987
|
+
this.#optionKeys.clear();
|
|
1988
|
+
for (const { plugin } of entries) this.#removing.add(plugin.name);
|
|
1989
|
+
const cleanup = this.#trackCleanup(
|
|
1990
|
+
(async () => {
|
|
1991
|
+
const errors = [];
|
|
1992
|
+
const previous = await Promise.allSettled(previousCleanups);
|
|
1993
|
+
for (const result of previous) {
|
|
1994
|
+
if (result.status === "rejected") errors.push(result.reason);
|
|
1995
|
+
}
|
|
1996
|
+
for (const entry of entries) {
|
|
1997
|
+
try {
|
|
1998
|
+
await this.#waitForDrain(entry);
|
|
1999
|
+
await entry.teardown?.();
|
|
2000
|
+
} catch (error) {
|
|
2001
|
+
errors.push(error);
|
|
2002
|
+
} finally {
|
|
2003
|
+
this.#removing.delete(entry.plugin.name);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
if (errors.length > 0) {
|
|
2007
|
+
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");
|
|
2008
|
+
}
|
|
2009
|
+
})()
|
|
2010
|
+
);
|
|
2011
|
+
await cleanup;
|
|
2012
|
+
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Объединяет конструкторские хуки с хуками подключаемых плагинов.
|
|
2015
|
+
*
|
|
2016
|
+
* Возвращённый объект динамический: подключение и отключение плагина начинает действовать
|
|
2017
|
+
* со следующего логического запроса без пересоздания транспорта.
|
|
2018
|
+
*/
|
|
2019
|
+
hooks(base) {
|
|
2020
|
+
const hooks = {};
|
|
2021
|
+
REQUEST_HOOK_DISPATCHERS.set(hooks, ((field, context, request) => this.#runHook(field, context, request, base)));
|
|
2022
|
+
return hooks;
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Прогоняет запрос через цепочку обёрток.
|
|
2026
|
+
*
|
|
2027
|
+
* Снимок цепочки берётся в начале: `unuse()` влияет на новые запросы, но не обрывает
|
|
2028
|
+
* уже выполняющийся посередине.
|
|
2029
|
+
*
|
|
2030
|
+
* @param execute настоящий запрос, вызывается самой внутренней обёрткой
|
|
2031
|
+
*/
|
|
2032
|
+
async run(request, execute) {
|
|
2033
|
+
const entries = [...this.#ordered];
|
|
2034
|
+
for (const entry of entries) entry.activeRequests += 1;
|
|
2035
|
+
const hookScope = entries.flatMap((entry) => entry.hooks);
|
|
2036
|
+
const scoped = (current) => current[PLUGIN_HOOK_SCOPE] === hookScope ? current : { ...current, [PLUGIN_HOOK_SCOPE]: hookScope };
|
|
2037
|
+
const chain = entries.flatMap((entry) => entry.transformers).reduceRight(
|
|
2038
|
+
(next, transformer) => (current) => transformer(scoped(current), (prepared) => next(scoped(prepared))),
|
|
2039
|
+
(current) => execute(scoped(current))
|
|
2040
|
+
);
|
|
2041
|
+
try {
|
|
2042
|
+
return await chain(scoped(request));
|
|
2043
|
+
} finally {
|
|
2044
|
+
for (const entry of entries) {
|
|
2045
|
+
entry.activeRequests -= 1;
|
|
2046
|
+
if (entry.activeRequests === 0) {
|
|
2047
|
+
entry.finishDrain?.();
|
|
2048
|
+
entry.finishDrain = void 0;
|
|
2049
|
+
entry.drain = void 0;
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
#rebuildOptionKeys() {
|
|
2055
|
+
this.#optionKeys.clear();
|
|
2056
|
+
for (const { plugin } of this.#ordered) {
|
|
2057
|
+
for (const key of plugin.optionKeys ?? []) this.#optionKeys.add(key);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
#waitForDrain(entry) {
|
|
2061
|
+
if (entry.activeRequests === 0) return Promise.resolve();
|
|
2062
|
+
entry.drain ??= new Promise((resolve) => {
|
|
2063
|
+
entry.finishDrain = resolve;
|
|
2064
|
+
});
|
|
2065
|
+
return entry.drain;
|
|
2066
|
+
}
|
|
2067
|
+
#trackCleanup(cleanup) {
|
|
2068
|
+
this.#cleanups.add(cleanup);
|
|
2069
|
+
void cleanup.then(
|
|
2070
|
+
() => this.#cleanups.delete(cleanup),
|
|
2071
|
+
() => this.#cleanups.delete(cleanup)
|
|
2072
|
+
);
|
|
2073
|
+
return cleanup;
|
|
2074
|
+
}
|
|
2075
|
+
async #runHook(field, context, request, base) {
|
|
2076
|
+
const baseHook = base[field];
|
|
2077
|
+
await baseHook?.(context);
|
|
2078
|
+
const scope = request[PLUGIN_HOOK_SCOPE] ?? [];
|
|
2079
|
+
for (const hooks of scope) {
|
|
2080
|
+
const hook = hooks[field];
|
|
2081
|
+
await hook?.(context);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
|
|
1672
2086
|
// src/core/retry.ts
|
|
1673
2087
|
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
1674
2088
|
function isRetryable(error, method, retryWrites) {
|
|
@@ -1724,10 +2138,7 @@ function createQueueMiddleware(schedule) {
|
|
|
1724
2138
|
return (request, next) => request.skipQueue ? next(request) : schedule(request, () => next(request));
|
|
1725
2139
|
}
|
|
1726
2140
|
function createPluginsMiddleware(plugins) {
|
|
1727
|
-
return (request, next) =>
|
|
1728
|
-
if (plugins.size === 0) return next(request);
|
|
1729
|
-
return plugins.run(request, next);
|
|
1730
|
-
};
|
|
2141
|
+
return (request, next) => plugins.run(request, next);
|
|
1731
2142
|
}
|
|
1732
2143
|
function createServicesMiddleware(registry) {
|
|
1733
2144
|
return async (request, next) => {
|
|
@@ -1799,16 +2210,21 @@ function createRetryMiddleware(deps) {
|
|
|
1799
2210
|
} catch (error) {
|
|
1800
2211
|
const delay = nextDelay(error, attempt, request, method, backoff);
|
|
1801
2212
|
if (delay === void 0) throw error;
|
|
1802
|
-
await
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
2213
|
+
await dispatchRequestHook(
|
|
2214
|
+
deps.hooks,
|
|
2215
|
+
"onRetry",
|
|
2216
|
+
{
|
|
2217
|
+
method,
|
|
2218
|
+
path: request.path,
|
|
2219
|
+
url: deps.buildUrl(request),
|
|
2220
|
+
// Умолчания транспорта добавляются после слоя повторов и сюда не входят.
|
|
2221
|
+
headers: new Headers({ ...request.layerHeaders, ...request.headers }),
|
|
2222
|
+
attempt,
|
|
2223
|
+
error,
|
|
2224
|
+
delay
|
|
2225
|
+
},
|
|
2226
|
+
request
|
|
2227
|
+
);
|
|
1812
2228
|
deps.logger?.debug(
|
|
1813
2229
|
`\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`
|
|
1814
2230
|
);
|
|
@@ -1818,104 +2234,6 @@ function createRetryMiddleware(deps) {
|
|
|
1818
2234
|
};
|
|
1819
2235
|
}
|
|
1820
2236
|
|
|
1821
|
-
// src/core/plugins.ts
|
|
1822
|
-
var NO_KEYS = /* @__PURE__ */ new Set();
|
|
1823
|
-
var RESERVED_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
1824
|
-
"signal",
|
|
1825
|
-
"timeout",
|
|
1826
|
-
"headers",
|
|
1827
|
-
"retry",
|
|
1828
|
-
"method",
|
|
1829
|
-
"path",
|
|
1830
|
-
"service",
|
|
1831
|
-
"baseUrl",
|
|
1832
|
-
"query",
|
|
1833
|
-
"body",
|
|
1834
|
-
"skipAuth",
|
|
1835
|
-
"skipAuthRefresh",
|
|
1836
|
-
"skipQueue",
|
|
1837
|
-
"raw"
|
|
1838
|
-
]);
|
|
1839
|
-
function validatePluginDefinition(plugin) {
|
|
1840
|
-
if (typeof plugin?.install !== "function") {
|
|
1841
|
-
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()");
|
|
1842
|
-
}
|
|
1843
|
-
const name = plugin.name;
|
|
1844
|
-
if (typeof name !== "string" || name.trim() === "") {
|
|
1845
|
-
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");
|
|
1846
|
-
}
|
|
1847
|
-
const keys = plugin.optionKeys ?? [];
|
|
1848
|
-
for (const key of keys) {
|
|
1849
|
-
if (typeof key !== "string" || key.trim() === "") {
|
|
1850
|
-
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`);
|
|
1851
|
-
}
|
|
1852
|
-
if (RESERVED_OPTION_KEYS.has(key)) {
|
|
1853
|
-
throw new ItdConfigError(
|
|
1854
|
-
`\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(", ")}`
|
|
1855
|
-
);
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
var PluginRegistry = class {
|
|
1860
|
-
#transformers = [];
|
|
1861
|
-
#optionKeys = /* @__PURE__ */ new Set();
|
|
1862
|
-
#names = /* @__PURE__ */ new Set();
|
|
1863
|
-
/** Сколько обёрток подключено. Ноль означает, что запрос идёт прежним путём. */
|
|
1864
|
-
get size() {
|
|
1865
|
-
return this.#transformers.length;
|
|
1866
|
-
}
|
|
1867
|
-
/** Имена опций запроса, заявленные плагинами. */
|
|
1868
|
-
get optionKeys() {
|
|
1869
|
-
return this.#optionKeys.size === 0 ? NO_KEYS : this.#optionKeys;
|
|
1870
|
-
}
|
|
1871
|
-
/**
|
|
1872
|
-
* Подключает плагин.
|
|
1873
|
-
*
|
|
1874
|
-
* @throws {ItdConfigError} если плагин задан неверно, уже подключён или заявил занятое
|
|
1875
|
-
* имя опции
|
|
1876
|
-
*/
|
|
1877
|
-
add(plugin, context) {
|
|
1878
|
-
validatePluginDefinition(plugin);
|
|
1879
|
-
const name = plugin.name;
|
|
1880
|
-
if (this.#names.has(name)) {
|
|
1881
|
-
throw new ItdConfigError(`\u041F\u043B\u0430\u0433\u0438\u043D \xAB${name}\xBB \u0443\u0436\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D`);
|
|
1882
|
-
}
|
|
1883
|
-
const keys = plugin.optionKeys ?? [];
|
|
1884
|
-
const before = this.#transformers.length;
|
|
1885
|
-
try {
|
|
1886
|
-
plugin.install({
|
|
1887
|
-
...context,
|
|
1888
|
-
use: (transformer) => {
|
|
1889
|
-
if (typeof transformer !== "function") {
|
|
1890
|
-
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`);
|
|
1891
|
-
}
|
|
1892
|
-
this.#transformers.push(transformer);
|
|
1893
|
-
}
|
|
1894
|
-
});
|
|
1895
|
-
} catch (error) {
|
|
1896
|
-
this.#transformers.length = before;
|
|
1897
|
-
throw error;
|
|
1898
|
-
}
|
|
1899
|
-
this.#names.add(name);
|
|
1900
|
-
for (const key of keys) this.#optionKeys.add(key);
|
|
1901
|
-
}
|
|
1902
|
-
/**
|
|
1903
|
-
* Прогоняет запрос через цепочку обёрток.
|
|
1904
|
-
*
|
|
1905
|
-
* Цепочка собирается на каждый запрос заново: плагин можно подключить в любой момент,
|
|
1906
|
-
* а обёрток единицы — экономить тут не на чем.
|
|
1907
|
-
*
|
|
1908
|
-
* @param execute настоящий запрос, вызывается самой внутренней обёрткой
|
|
1909
|
-
*/
|
|
1910
|
-
run(request, execute) {
|
|
1911
|
-
const chain = this.#transformers.reduceRight(
|
|
1912
|
-
(next, transformer) => (current) => transformer(current, next),
|
|
1913
|
-
execute
|
|
1914
|
-
);
|
|
1915
|
-
return chain(request);
|
|
1916
|
-
}
|
|
1917
|
-
};
|
|
1918
|
-
|
|
1919
2237
|
// src/core/rate-limit.ts
|
|
1920
2238
|
function queueAbortError() {
|
|
1921
2239
|
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");
|
|
@@ -2513,7 +2831,7 @@ var Transport = class {
|
|
|
2513
2831
|
}
|
|
2514
2832
|
}
|
|
2515
2833
|
const context = { method, path: request.path, url, headers, attempt };
|
|
2516
|
-
await this.#config.hooks
|
|
2834
|
+
await dispatchRequestHook(this.#config.hooks, "onRequest", context, request);
|
|
2517
2835
|
const timeout = request.timeout ?? this.#config.timeout;
|
|
2518
2836
|
const abort = createAbortBundle(request.signal, timeout);
|
|
2519
2837
|
const startedAt = Date.now();
|
|
@@ -2534,7 +2852,12 @@ var Transport = class {
|
|
|
2534
2852
|
} catch (error) {
|
|
2535
2853
|
const duration2 = Date.now() - startedAt;
|
|
2536
2854
|
const failure = this.#toTransportError(error, abort, request, method, timeout);
|
|
2537
|
-
await
|
|
2855
|
+
await dispatchRequestHook(
|
|
2856
|
+
this.#config.hooks,
|
|
2857
|
+
"onError",
|
|
2858
|
+
{ ...context, duration: duration2, error: failure },
|
|
2859
|
+
request
|
|
2860
|
+
);
|
|
2538
2861
|
this.#config.logger?.warn(
|
|
2539
2862
|
`\xD7 ${method} ${request.path} (${duration2} \u043C\u0441): ${failure.message}`
|
|
2540
2863
|
);
|
|
@@ -2546,12 +2869,17 @@ var Transport = class {
|
|
|
2546
2869
|
}
|
|
2547
2870
|
if (this.#config.useCookieJar) this.#deps.cookies?.setFromResponse(url, response);
|
|
2548
2871
|
if (response.ok) {
|
|
2549
|
-
await
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2872
|
+
await dispatchRequestHook(
|
|
2873
|
+
this.#config.hooks,
|
|
2874
|
+
"onResponse",
|
|
2875
|
+
{
|
|
2876
|
+
...context,
|
|
2877
|
+
status: response.status,
|
|
2878
|
+
duration: Date.now() - startedAt,
|
|
2879
|
+
response
|
|
2880
|
+
},
|
|
2881
|
+
request
|
|
2882
|
+
);
|
|
2555
2883
|
}
|
|
2556
2884
|
const payload = await this.#readBodyOrFail(
|
|
2557
2885
|
response,
|
|
@@ -2572,7 +2900,12 @@ var Transport = class {
|
|
|
2572
2900
|
response,
|
|
2573
2901
|
body: payload
|
|
2574
2902
|
});
|
|
2575
|
-
await
|
|
2903
|
+
await dispatchRequestHook(
|
|
2904
|
+
this.#config.hooks,
|
|
2905
|
+
"onError",
|
|
2906
|
+
{ ...context, duration, error },
|
|
2907
|
+
request
|
|
2908
|
+
);
|
|
2576
2909
|
this.#config.logger?.warn(
|
|
2577
2910
|
`\u2190 ${response.status} ${method} ${request.path} (${duration} \u043C\u0441): ${error.message}`
|
|
2578
2911
|
);
|
|
@@ -2593,11 +2926,16 @@ var Transport = class {
|
|
|
2593
2926
|
await response.body?.cancel().catch(() => {
|
|
2594
2927
|
});
|
|
2595
2928
|
const failure = this.#toTransportError(error, abort, request, method, timeout);
|
|
2596
|
-
await
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2929
|
+
await dispatchRequestHook(
|
|
2930
|
+
this.#config.hooks,
|
|
2931
|
+
"onError",
|
|
2932
|
+
{
|
|
2933
|
+
...context,
|
|
2934
|
+
duration: Date.now() - startedAt,
|
|
2935
|
+
error: failure
|
|
2936
|
+
},
|
|
2937
|
+
request
|
|
2938
|
+
);
|
|
2601
2939
|
this.#config.logger?.warn(
|
|
2602
2940
|
`\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}`
|
|
2603
2941
|
);
|
|
@@ -2648,12 +2986,16 @@ var Transport = class {
|
|
|
2648
2986
|
}
|
|
2649
2987
|
/** Превращает исключение `fetch` в понятную ошибку библиотеки. */
|
|
2650
2988
|
#toTransportError(error, abort, request, method, timeout) {
|
|
2651
|
-
const aborted = error instanceof Error && error.name === "AbortError";
|
|
2989
|
+
const aborted = abort.signal.aborted || error instanceof Error && error.name === "AbortError";
|
|
2652
2990
|
if (aborted && abort.timedOut()) {
|
|
2653
2991
|
return new ItdTimeoutError({ timeout, method, path: request.path });
|
|
2654
2992
|
}
|
|
2655
2993
|
if (aborted) {
|
|
2656
|
-
|
|
2994
|
+
const reason = request.signal?.reason;
|
|
2995
|
+
return new ItdAbortError(
|
|
2996
|
+
`\u0417\u0430\u043F\u0440\u043E\u0441 ${method} ${request.path} \u043E\u0442\u043C\u0435\u043D\u0451\u043D`,
|
|
2997
|
+
reason !== void 0 ? { cause: reason } : void 0
|
|
2998
|
+
);
|
|
2657
2999
|
}
|
|
2658
3000
|
return new ItdNetworkError(
|
|
2659
3001
|
`\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)}`,
|
|
@@ -2951,7 +3293,6 @@ var PollTransport = class {
|
|
|
2951
3293
|
const seen = /* @__PURE__ */ new Set();
|
|
2952
3294
|
let firstRun = true;
|
|
2953
3295
|
let lastUnreadCount;
|
|
2954
|
-
context.onOpen();
|
|
2955
3296
|
while (!context.signal.aborted) {
|
|
2956
3297
|
const token = await context.getToken();
|
|
2957
3298
|
if (!token) throw new UnauthorizedStreamError();
|
|
@@ -2966,6 +3307,7 @@ var PollTransport = class {
|
|
|
2966
3307
|
});
|
|
2967
3308
|
if (response.status === 401) throw new UnauthorizedStreamError();
|
|
2968
3309
|
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}`);
|
|
3310
|
+
context.onOpen();
|
|
2969
3311
|
const body = await response.json();
|
|
2970
3312
|
const payload = typeof body === "object" && body !== null && "data" in body ? body.data : body;
|
|
2971
3313
|
const items = pickArray(payload, "notifications");
|
|
@@ -3211,10 +3553,12 @@ var STREAM_PATH = "/api/notifications/stream";
|
|
|
3211
3553
|
var SseTransport = class {
|
|
3212
3554
|
name = "sse";
|
|
3213
3555
|
#idleTimeout;
|
|
3556
|
+
#handshakeTimeout;
|
|
3214
3557
|
/** Идентификатор последнего события — отправляется при переподключении. */
|
|
3215
3558
|
#lastEventId;
|
|
3216
3559
|
constructor(options = {}) {
|
|
3217
3560
|
this.#idleTimeout = options.idleTimeout ?? 9e4;
|
|
3561
|
+
this.#handshakeTimeout = options.handshakeTimeout ?? 2e4;
|
|
3218
3562
|
}
|
|
3219
3563
|
async connect(context) {
|
|
3220
3564
|
const token = await context.getToken();
|
|
@@ -3225,16 +3569,36 @@ var SseTransport = class {
|
|
|
3225
3569
|
headers.set("Authorization", `Bearer ${token}`);
|
|
3226
3570
|
headers.set("Cache-Control", "no-cache");
|
|
3227
3571
|
if (this.#lastEventId) headers.set("Last-Event-ID", this.#lastEventId);
|
|
3228
|
-
const
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3572
|
+
const controller = new AbortController();
|
|
3573
|
+
const relayAbort = () => controller.abort(context.signal.reason);
|
|
3574
|
+
if (context.signal.aborted) controller.abort(context.signal.reason);
|
|
3575
|
+
else context.signal.addEventListener("abort", relayAbort, { once: true });
|
|
3576
|
+
try {
|
|
3577
|
+
const response = await this.#handshake(url, headers, context, controller);
|
|
3578
|
+
if (response.status === 401) throw new UnauthorizedStreamError();
|
|
3579
|
+
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}`);
|
|
3580
|
+
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");
|
|
3581
|
+
context.onOpen();
|
|
3582
|
+
await this.#read(response.body, context);
|
|
3583
|
+
} finally {
|
|
3584
|
+
context.signal.removeEventListener("abort", relayAbort);
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
/** Выполняет запрос потока, обрывая его, если ответ не пришёл за отведённое время. */
|
|
3588
|
+
async #handshake(url, headers, context, controller) {
|
|
3589
|
+
let expired = false;
|
|
3590
|
+
const timer = this.#handshakeTimeout > 0 ? setTimeout(() => {
|
|
3591
|
+
expired = true;
|
|
3592
|
+
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"));
|
|
3593
|
+
}, this.#handshakeTimeout) : void 0;
|
|
3594
|
+
try {
|
|
3595
|
+
return await context.fetch(url, { method: "GET", headers, signal: controller.signal });
|
|
3596
|
+
} catch (error) {
|
|
3597
|
+
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");
|
|
3598
|
+
throw error;
|
|
3599
|
+
} finally {
|
|
3600
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
3601
|
+
}
|
|
3238
3602
|
}
|
|
3239
3603
|
async #read(body, context) {
|
|
3240
3604
|
const reader = body.getReader();
|
|
@@ -3306,6 +3670,7 @@ function validateRealtimeOptions(options) {
|
|
|
3306
3670
|
positiveInteger(options.maxAttempts, "maxAttempts");
|
|
3307
3671
|
duration(options.pollInterval, "pollInterval", 1);
|
|
3308
3672
|
duration(options.idleTimeout, "idleTimeout", 0);
|
|
3673
|
+
duration(options.handshakeTimeout, "handshakeTimeout", 0);
|
|
3309
3674
|
if (options.jitter !== void 0 && !(options.jitter >= 0 && options.jitter <= 1)) {
|
|
3310
3675
|
throw new ItdConfigError(
|
|
3311
3676
|
`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}`
|
|
@@ -3427,7 +3792,8 @@ var ItdRealtime = class {
|
|
|
3427
3792
|
});
|
|
3428
3793
|
}
|
|
3429
3794
|
return new SseTransport({
|
|
3430
|
-
...this.#options.idleTimeout !== void 0 ? { idleTimeout: this.#options.idleTimeout } : {}
|
|
3795
|
+
...this.#options.idleTimeout !== void 0 ? { idleTimeout: this.#options.idleTimeout } : {},
|
|
3796
|
+
...this.#options.handshakeTimeout !== void 0 ? { handshakeTimeout: this.#options.handshakeTimeout } : {}
|
|
3431
3797
|
});
|
|
3432
3798
|
}
|
|
3433
3799
|
/** Запускает попытку подключения; повторы планирует сам. */
|
|
@@ -3642,6 +4008,11 @@ var Paginator = class {
|
|
|
3642
4008
|
#finished = false;
|
|
3643
4009
|
#pagesLoaded = 0;
|
|
3644
4010
|
constructor(options) {
|
|
4011
|
+
if (options.maxPages !== void 0 && (!Number.isInteger(options.maxPages) || options.maxPages < 1)) {
|
|
4012
|
+
throw new ItdConfigError(
|
|
4013
|
+
`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}`
|
|
4014
|
+
);
|
|
4015
|
+
}
|
|
3645
4016
|
this.#options = options;
|
|
3646
4017
|
this.#maxPages = options.maxPages ?? 1e3;
|
|
3647
4018
|
this.#state = options.start ?? {};
|
|
@@ -3691,6 +4062,7 @@ var Paginator = class {
|
|
|
3691
4062
|
* @param max сколько элементов достаточно; без него перебираются все страницы
|
|
3692
4063
|
*/
|
|
3693
4064
|
async collect(max) {
|
|
4065
|
+
if (max !== void 0 && max <= 0) return [];
|
|
3694
4066
|
const result = [];
|
|
3695
4067
|
for await (const item of this) {
|
|
3696
4068
|
result.push(item);
|
|
@@ -6166,6 +6538,13 @@ var VerificationResource = class extends BaseResource {
|
|
|
6166
6538
|
};
|
|
6167
6539
|
|
|
6168
6540
|
// src/client.ts
|
|
6541
|
+
var CLIENT_PLUGIN_REGISTRIES = /* @__PURE__ */ new WeakMap();
|
|
6542
|
+
function assertClientCanUsePlugin(client, plugin) {
|
|
6543
|
+
CLIENT_PLUGIN_REGISTRIES.get(client)?.assertCanAdd(plugin);
|
|
6544
|
+
}
|
|
6545
|
+
function assertClientCanUnusePlugin(client, name) {
|
|
6546
|
+
CLIENT_PLUGIN_REGISTRIES.get(client)?.assertCanRemove(name);
|
|
6547
|
+
}
|
|
6169
6548
|
var ItdClient = class _ItdClient {
|
|
6170
6549
|
#config;
|
|
6171
6550
|
#http;
|
|
@@ -6203,13 +6582,10 @@ var ItdClient = class _ItdClient {
|
|
|
6203
6582
|
subscription;
|
|
6204
6583
|
/** Сведения о платформе: изменения, анонсы, баннер события. */
|
|
6205
6584
|
platform;
|
|
6206
|
-
/**
|
|
6207
|
-
* Телеметрия просмотров.
|
|
6208
|
-
*
|
|
6209
|
-
* @experimental Недокументированные эндпоинты. Библиотека никогда не отправляет их сама.
|
|
6210
|
-
*/
|
|
6585
|
+
/** Телеметрия просмотров. */
|
|
6211
6586
|
telemetry;
|
|
6212
6587
|
constructor(options = {}, internals = {}) {
|
|
6588
|
+
CLIENT_PLUGIN_REGISTRIES.set(this, this.#plugins);
|
|
6213
6589
|
const config = resolveConfig(options);
|
|
6214
6590
|
this.#config = config;
|
|
6215
6591
|
this.#jar = new CookieJar();
|
|
@@ -6221,18 +6597,22 @@ var ItdClient = class _ItdClient {
|
|
|
6221
6597
|
this.#queues = queues;
|
|
6222
6598
|
this.#ownsQueues = shared === void 0;
|
|
6223
6599
|
let authManager;
|
|
6224
|
-
const
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6600
|
+
const hooks = this.#plugins.hooks(config.hooks);
|
|
6601
|
+
const transport = new Transport(
|
|
6602
|
+
{ ...config, hooks },
|
|
6603
|
+
{
|
|
6604
|
+
cookies: config.useCookieJar ? this.#jar : void 0,
|
|
6605
|
+
getDeviceId: () => authManager.getDeviceId(),
|
|
6606
|
+
onRateLimit: queues && config.rateLimit?.respectHeaders ? (limit, remaining, request) => this.#throttleByHeaders(limit, remaining, request) : void 0
|
|
6607
|
+
}
|
|
6608
|
+
);
|
|
6229
6609
|
this.#transport = transport;
|
|
6230
6610
|
const pluginsLayer = createPluginsMiddleware(this.#plugins);
|
|
6231
6611
|
const retriesLayer = createRetryMiddleware({
|
|
6232
6612
|
retry: config.retry,
|
|
6233
6613
|
rateLimitDelays: config.rateLimit?.retryDelays ?? [],
|
|
6234
6614
|
pauseQueue: queues ? (ms, request) => queues.for(request.service).pause(ms) : void 0,
|
|
6235
|
-
hooks
|
|
6615
|
+
hooks,
|
|
6236
6616
|
logger: config.logger,
|
|
6237
6617
|
buildUrl: (request) => transport.buildUrl(request)
|
|
6238
6618
|
});
|
|
@@ -6332,6 +6712,26 @@ var ItdClient = class _ItdClient {
|
|
|
6332
6712
|
});
|
|
6333
6713
|
return this;
|
|
6334
6714
|
}
|
|
6715
|
+
/** Имена подключённых плагинов в фактическом порядке выполнения обёрток. */
|
|
6716
|
+
pluginNames() {
|
|
6717
|
+
return this.#plugins.names();
|
|
6718
|
+
}
|
|
6719
|
+
/** Подключён ли плагин с таким именем. */
|
|
6720
|
+
hasPlugin(name) {
|
|
6721
|
+
return this.#plugins.has(name);
|
|
6722
|
+
}
|
|
6723
|
+
/**
|
|
6724
|
+
* Отключает плагин и освобождает заведённые им ресурсы.
|
|
6725
|
+
*
|
|
6726
|
+
* Новые запросы перестают видеть плагин сразу. Очистка дождётся логического запроса,
|
|
6727
|
+
* который уже проходил через его обёртку.
|
|
6728
|
+
*
|
|
6729
|
+
* @returns `false`, если такого плагина не было
|
|
6730
|
+
* @throws {ItdConfigError} если от плагина зависит другой подключённый плагин
|
|
6731
|
+
*/
|
|
6732
|
+
unuse(name) {
|
|
6733
|
+
return this.#plugins.remove(name);
|
|
6734
|
+
}
|
|
6335
6735
|
/**
|
|
6336
6736
|
* Регистрирует сервис платформы — домен, отличный от основного.
|
|
6337
6737
|
*
|
|
@@ -6437,13 +6837,25 @@ var ItdClient = class _ItdClient {
|
|
|
6437
6837
|
* ```ts
|
|
6438
6838
|
* await using itd = new ItdClient({ auth: token });
|
|
6439
6839
|
* // …работа…
|
|
6440
|
-
* //
|
|
6840
|
+
* // dispose() вызовется сам на выходе из блока
|
|
6441
6841
|
* ```
|
|
6442
6842
|
*/
|
|
6443
6843
|
async close() {
|
|
6444
6844
|
this.#disconnectStreams();
|
|
6445
6845
|
if (this.#ownsQueues) this.#queues?.stop();
|
|
6446
6846
|
}
|
|
6847
|
+
/**
|
|
6848
|
+
* Окончательно освобождает клиент: выполняет {@link close} и отключает все плагины.
|
|
6849
|
+
*
|
|
6850
|
+
* В отличие от `close()`, после `dispose()` плагины не восстанавливаются автоматически.
|
|
6851
|
+
* Сам клиент остаётся пригоден для обычных запросов; при необходимости плагины можно
|
|
6852
|
+
* подключить заново через {@link use}.
|
|
6853
|
+
*/
|
|
6854
|
+
async dispose() {
|
|
6855
|
+
const results = await Promise.allSettled([this.close(), this.#plugins.dispose()]);
|
|
6856
|
+
const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
6857
|
+
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");
|
|
6858
|
+
}
|
|
6447
6859
|
/** Завершает потоки до того, как запросы начнут использовать другой аккаунт. */
|
|
6448
6860
|
#disconnectStreams() {
|
|
6449
6861
|
for (const stream of [...this.#streams]) stream.disconnect();
|
|
@@ -6451,7 +6863,7 @@ var ItdClient = class _ItdClient {
|
|
|
6451
6863
|
}
|
|
6452
6864
|
/** Позволяет использовать клиент с `await using`. */
|
|
6453
6865
|
[Symbol.asyncDispose]() {
|
|
6454
|
-
return this.
|
|
6866
|
+
return this.dispose();
|
|
6455
6867
|
}
|
|
6456
6868
|
static {
|
|
6457
6869
|
if (typeof Symbol.asyncDispose !== "symbol") {
|
|
@@ -6650,10 +7062,13 @@ var ItdAccounts = class _ItdAccounts {
|
|
|
6650
7062
|
#eventUnsubscribers = /* @__PURE__ */ new Map();
|
|
6651
7063
|
/** Плагины для всех: и для уже заведённых аккаунтов, и для будущих. */
|
|
6652
7064
|
#plugins;
|
|
7065
|
+
/** Имена плагинов, чья асинхронная очистка ещё не завершилась. */
|
|
7066
|
+
#removingPlugins = /* @__PURE__ */ new Set();
|
|
6653
7067
|
/** Общая очередь. `undefined`, когда у каждого аккаунта своя. */
|
|
6654
7068
|
#queues;
|
|
6655
7069
|
#rateLimitScope;
|
|
6656
7070
|
#emitter;
|
|
7071
|
+
#logger;
|
|
6657
7072
|
#createClient;
|
|
6658
7073
|
constructor(options = {}, internals = {}) {
|
|
6659
7074
|
const { storage, plugins, rateLimitScope, ...base } = options;
|
|
@@ -6662,19 +7077,13 @@ var ItdAccounts = class _ItdAccounts {
|
|
|
6662
7077
|
}
|
|
6663
7078
|
this.#base = base;
|
|
6664
7079
|
this.#storage = storage ?? new MemoryMultiTokenStorage();
|
|
6665
|
-
this.#plugins = [];
|
|
6666
|
-
for (const plugin of plugins ?? []) {
|
|
6667
|
-
validatePluginDefinition(plugin);
|
|
6668
|
-
if (this.#plugins.some((added) => added.name === plugin.name)) {
|
|
6669
|
-
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`);
|
|
6670
|
-
}
|
|
6671
|
-
this.#plugins.push(plugin);
|
|
6672
|
-
}
|
|
7080
|
+
this.#plugins = orderPluginDefinitions(plugins ?? []);
|
|
6673
7081
|
this.#rateLimitScope = rateLimitScope ?? "account";
|
|
6674
7082
|
this.#createClient = internals.createClient ?? ((clientOptions, clientInternals) => new ItdClient(clientOptions, clientInternals));
|
|
6675
7083
|
const rateLimit = this.#rateLimitScope === "shared" ? resolveRateLimit(base.rateLimit) : void 0;
|
|
6676
7084
|
this.#queues = rateLimit ? new RequestQueuePool(rateLimit) : void 0;
|
|
6677
7085
|
const logger = typeof base.logger === "object" ? base.logger : void 0;
|
|
7086
|
+
this.#logger = logger;
|
|
6678
7087
|
this.#emitter = new Emitter(
|
|
6679
7088
|
(error) => reportListenerError(logger, "\u0430\u043A\u043A\u0430\u0443\u043D\u0442\u043E\u0432", error)
|
|
6680
7089
|
);
|
|
@@ -6740,6 +7149,11 @@ var ItdAccounts = class _ItdAccounts {
|
|
|
6740
7149
|
for (const plugin of this.#plugins) client.use(plugin);
|
|
6741
7150
|
} catch (error) {
|
|
6742
7151
|
storageControl.revoke();
|
|
7152
|
+
if (client) {
|
|
7153
|
+
void client.dispose().catch((cleanupError) => {
|
|
7154
|
+
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);
|
|
7155
|
+
});
|
|
7156
|
+
}
|
|
6743
7157
|
throw error;
|
|
6744
7158
|
}
|
|
6745
7159
|
const unsubscribers = this.#forwardEvents(name, client);
|
|
@@ -6821,7 +7235,7 @@ var ItdAccounts = class _ItdAccounts {
|
|
|
6821
7235
|
try {
|
|
6822
7236
|
const errors = [];
|
|
6823
7237
|
const closing = await Promise.allSettled([
|
|
6824
|
-
client.
|
|
7238
|
+
client.dispose(),
|
|
6825
7239
|
storageControl?.drain() ?? Promise.resolve()
|
|
6826
7240
|
]);
|
|
6827
7241
|
for (const result of closing) {
|
|
@@ -6849,14 +7263,64 @@ var ItdAccounts = class _ItdAccounts {
|
|
|
6849
7263
|
* ```
|
|
6850
7264
|
*/
|
|
6851
7265
|
use(plugin) {
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
7266
|
+
if (this.#removingPlugins.has(plugin?.name)) {
|
|
7267
|
+
throw new ItdConfigError(
|
|
7268
|
+
`\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()`
|
|
7269
|
+
);
|
|
6855
7270
|
}
|
|
6856
|
-
|
|
6857
|
-
this.#
|
|
7271
|
+
const ordered = orderPluginDefinitions([...this.#plugins, plugin]);
|
|
7272
|
+
const clients = [...this.#clients.values()];
|
|
7273
|
+
for (const client of clients) assertClientCanUsePlugin(client, plugin);
|
|
7274
|
+
const installed = [];
|
|
7275
|
+
try {
|
|
7276
|
+
for (const client of clients) {
|
|
7277
|
+
client.use(plugin);
|
|
7278
|
+
installed.push(client);
|
|
7279
|
+
}
|
|
7280
|
+
} catch (error) {
|
|
7281
|
+
for (const client of installed.reverse()) {
|
|
7282
|
+
void client.unuse(plugin.name).catch((cleanupError) => {
|
|
7283
|
+
this.#reportPluginCleanup(`\u043E\u0442\u043A\u0430\u0442\u0430 \u043F\u043B\u0430\u0433\u0438\u043D\u0430 \xAB${plugin.name}\xBB`, cleanupError);
|
|
7284
|
+
});
|
|
7285
|
+
}
|
|
7286
|
+
throw error;
|
|
7287
|
+
}
|
|
7288
|
+
this.#plugins.splice(0, this.#plugins.length, ...ordered);
|
|
6858
7289
|
return this;
|
|
6859
7290
|
}
|
|
7291
|
+
/** Имена общих плагинов в фактическом порядке выполнения обёрток. */
|
|
7292
|
+
pluginNames() {
|
|
7293
|
+
return this.#plugins.map((plugin) => plugin.name);
|
|
7294
|
+
}
|
|
7295
|
+
/** Подключён ли общий плагин с таким именем. */
|
|
7296
|
+
hasPlugin(name) {
|
|
7297
|
+
return this.#plugins.some((plugin) => plugin.name === name);
|
|
7298
|
+
}
|
|
7299
|
+
/**
|
|
7300
|
+
* Отключает общий плагин у существующих клиентов и не применяет его к будущим.
|
|
7301
|
+
*
|
|
7302
|
+
* @returns `false`, если такого плагина не было
|
|
7303
|
+
* @throws {ItdConfigError} если от плагина зависит другой общий плагин
|
|
7304
|
+
*/
|
|
7305
|
+
async unuse(name) {
|
|
7306
|
+
const index = this.#plugins.findIndex((plugin) => plugin.name === name);
|
|
7307
|
+
if (index < 0) return false;
|
|
7308
|
+
assertPluginRemovable(this.#plugins, name);
|
|
7309
|
+
const clients = [...this.#clients.values()];
|
|
7310
|
+
for (const client of clients) assertClientCanUnusePlugin(client, name);
|
|
7311
|
+
this.#plugins.splice(index, 1);
|
|
7312
|
+
this.#removingPlugins.add(name);
|
|
7313
|
+
try {
|
|
7314
|
+
const results = await Promise.allSettled(clients.map((client) => client.unuse(name)));
|
|
7315
|
+
const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
7316
|
+
if (errors.length > 0) {
|
|
7317
|
+
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`);
|
|
7318
|
+
}
|
|
7319
|
+
return true;
|
|
7320
|
+
} finally {
|
|
7321
|
+
this.#removingPlugins.delete(name);
|
|
7322
|
+
}
|
|
7323
|
+
}
|
|
6860
7324
|
/**
|
|
6861
7325
|
* Подписывается на события авторизации всех аккаунтов сразу.
|
|
6862
7326
|
*
|
|
@@ -6894,16 +7358,32 @@ var ItdAccounts = class _ItdAccounts {
|
|
|
6894
7358
|
* ```ts
|
|
6895
7359
|
* await using accounts = new ItdAccounts({ storage });
|
|
6896
7360
|
* // …работа…
|
|
6897
|
-
* //
|
|
7361
|
+
* // dispose() вызовется сам на выходе из блока
|
|
6898
7362
|
* ```
|
|
6899
7363
|
*/
|
|
6900
7364
|
async close() {
|
|
6901
7365
|
await Promise.all([...this.#clients.values()].map((client) => client.close()));
|
|
6902
7366
|
this.#queues?.stop();
|
|
6903
7367
|
}
|
|
7368
|
+
/**
|
|
7369
|
+
* Окончательно освобождает контейнер и отключает общие плагины у всех аккаунтов.
|
|
7370
|
+
*
|
|
7371
|
+
* Для временной остановки потоков и очереди без отключения плагинов используйте
|
|
7372
|
+
* {@link close}.
|
|
7373
|
+
*/
|
|
7374
|
+
async dispose() {
|
|
7375
|
+
const results = await Promise.allSettled([
|
|
7376
|
+
...[...this.#clients.values()].map((client) => client.dispose()),
|
|
7377
|
+
Promise.resolve().then(() => this.#queues?.stop())
|
|
7378
|
+
]);
|
|
7379
|
+
this.#plugins.splice(0);
|
|
7380
|
+
this.#removingPlugins.clear();
|
|
7381
|
+
const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
7382
|
+
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");
|
|
7383
|
+
}
|
|
6904
7384
|
/** Позволяет использовать контейнер с `await using`. */
|
|
6905
7385
|
[Symbol.asyncDispose]() {
|
|
6906
|
-
return this.
|
|
7386
|
+
return this.dispose();
|
|
6907
7387
|
}
|
|
6908
7388
|
static {
|
|
6909
7389
|
if (typeof Symbol.asyncDispose !== "symbol") {
|
|
@@ -6929,6 +7409,12 @@ var ItdAccounts = class _ItdAccounts {
|
|
|
6929
7409
|
storage
|
|
6930
7410
|
};
|
|
6931
7411
|
}
|
|
7412
|
+
/** Не теряет ошибку фонового teardown, который синхронный API не может await-нуть. */
|
|
7413
|
+
#reportPluginCleanup(scope, error) {
|
|
7414
|
+
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}`;
|
|
7415
|
+
if (this.#logger) this.#logger.error(message, error);
|
|
7416
|
+
else console.error(`[itd-api] ${message}`, error);
|
|
7417
|
+
}
|
|
6932
7418
|
/** Ретранслирует события клиента наружу, добавляя к ним имя аккаунта. */
|
|
6933
7419
|
#forwardEvents(account, client) {
|
|
6934
7420
|
return [
|
|
@@ -7383,5 +7869,5 @@ exports.scopedTokenStorage = scopedTokenStorage;
|
|
|
7383
7869
|
exports.statusDays = statusDays;
|
|
7384
7870
|
exports.toDate = toDate;
|
|
7385
7871
|
exports.utcStampToIso = utcStampToIso;
|
|
7386
|
-
//# sourceMappingURL=chunk-
|
|
7387
|
-
//# sourceMappingURL=chunk-
|
|
7872
|
+
//# sourceMappingURL=chunk-73CISRBG.cjs.map
|
|
7873
|
+
//# sourceMappingURL=chunk-73CISRBG.cjs.map
|