itd-api 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/events/index.cjs +3 -4
- package/dist/events/index.d.cts +3 -3
- package/dist/events/index.d.ts +3 -3
- package/dist/events/index.js +4 -4
- package/dist/index.cjs +283 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -72
- package/dist/index.d.ts +16 -72
- package/dist/index.js +284 -90
- package/dist/index.js.map +1 -1
- package/dist/node/index.cjs +3 -3
- package/dist/node/index.js +3 -3
- package/dist/rest/index.cjs +4 -5
- package/dist/rest/index.d.cts +3 -3
- package/dist/rest/index.d.ts +3 -3
- package/dist/rest/index.js +5 -5
- package/dist/shared/{events-BDdJyH2p.d.ts → events-BPxBF0gQ.d.cts} +3 -5
- package/dist/shared/{events-D6y0Hxyz.js → events-BzjI6pn5.js} +4 -4
- package/dist/shared/{events-D6y0Hxyz.js.map → events-BzjI6pn5.js.map} +1 -1
- package/dist/shared/{events-BqFkwwTw.cjs → events-C1FudmSe.cjs} +3 -9
- package/dist/shared/{events-BqFkwwTw.cjs.map → events-C1FudmSe.cjs.map} +1 -1
- package/dist/shared/{events-BQyH5-S0.d.cts → events-DnwfcLxS.d.ts} +3 -5
- package/dist/shared/{multi-storage-SoW05uzO.js → multi-storage-C3Ngtop1.js} +13 -8
- package/dist/shared/multi-storage-C3Ngtop1.js.map +1 -0
- package/dist/shared/{multi-storage-CDKLa956.cjs → multi-storage-s-fPtjoy.cjs} +13 -8
- package/dist/shared/multi-storage-s-fPtjoy.cjs.map +1 -0
- package/dist/shared/{options-DOJYtoti.cjs → options-DjEtbVaf.cjs} +2 -2
- package/dist/shared/{options-DOJYtoti.cjs.map → options-DjEtbVaf.cjs.map} +1 -1
- package/dist/shared/{options-DhgGg7Ms.js → options-i4LrKfqx.js} +2 -2
- package/dist/shared/{options-DhgGg7Ms.js.map → options-i4LrKfqx.js.map} +1 -1
- package/dist/shared/{redact-Ba5iSVYZ.js → redact-DJV0XM3n.js} +40 -15
- package/dist/shared/redact-DJV0XM3n.js.map +1 -0
- package/dist/shared/{redact-BP00URmQ.cjs → redact-lAmck1Z6.cjs} +45 -14
- package/dist/shared/redact-lAmck1Z6.cjs.map +1 -0
- package/dist/shared/{render-BU1_wex8.d.cts → render-C1F_QWYD.d.cts} +82 -19
- package/dist/shared/{render-sb1VJhUA.js → render-CtOpp9Pm.js} +127 -124
- package/dist/shared/render-CtOpp9Pm.js.map +1 -0
- package/dist/shared/{render-DAEeCMk9.cjs → render-Omy1drH1.cjs} +125 -128
- package/dist/shared/render-Omy1drH1.cjs.map +1 -0
- package/dist/shared/{render-CZ_-F2Tl.d.ts → render-uFW2kDxp.d.ts} +82 -19
- package/dist/shared/{storage-QNVxzpDB.cjs → storage-CZJZoaz9.cjs} +4 -2
- package/dist/shared/storage-CZJZoaz9.cjs.map +1 -0
- package/dist/shared/{storage-CfHKzVHf.js → storage-DiDe0PZM.js} +4 -2
- package/dist/shared/storage-DiDe0PZM.js.map +1 -0
- package/dist/shared/{url-Dsr4c5r5.js → url-3Ts6EEHQ.js} +301 -96
- package/dist/shared/url-3Ts6EEHQ.js.map +1 -0
- package/dist/shared/{url-CzHqE-Fg.cjs → url-D6wsXm1T.cjs} +301 -96
- package/dist/shared/url-D6wsXm1T.cjs.map +1 -0
- package/dist/shared/{url-CZZcyPI0.d.cts → url-DubjkRR9.d.cts} +10 -16
- package/dist/shared/{url-CZZcyPI0.d.ts → url-DubjkRR9.d.ts} +10 -16
- package/dist/web/index.cjs +1 -1
- package/dist/web/index.js +1 -1
- package/package.json +5 -5
- package/dist/shared/multi-storage-CDKLa956.cjs.map +0 -1
- package/dist/shared/multi-storage-SoW05uzO.js.map +0 -1
- package/dist/shared/redact-BP00URmQ.cjs.map +0 -1
- package/dist/shared/redact-Ba5iSVYZ.js.map +0 -1
- package/dist/shared/render-DAEeCMk9.cjs.map +0 -1
- package/dist/shared/render-sb1VJhUA.js.map +0 -1
- package/dist/shared/storage-CfHKzVHf.js.map +0 -1
- package/dist/shared/storage-QNVxzpDB.cjs.map +0 -1
- package/dist/shared/url-CzHqE-Fg.cjs.map +0 -1
- package/dist/shared/url-Dsr4c5r5.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { O as isItdRateLimitError, _ as ItdStateError, a as ItdConfigError, b as isItdApiError, d as ItdForbiddenError, f as ItdNetworkError, g as ItdServerError, h as ItdRateLimitError, i as ItdAuthError, l as ItdFileError, m as ItdPhoneVerificationError, n as ItdApiError, o as ItdConflictError, p as ItdNotFoundError, s as ItdError, t as ItdAbortError, v as ItdTimeoutError, y as ItdValidationError } from "./errors-GI10kZxk.js";
|
|
2
|
-
import { C as
|
|
2
|
+
import { C as shouldSendCredentials, S as resolveFetch, _ as originOf, b as isBlob, d as buildQuery, g as normalizeBaseUrl, h as joinUrl, i as redactHeaders, m as isSameSite, n as redactBody, p as hostOf, s as CookieJar, v as RuntimeMode, w as shouldUseCookieJar, y as createDeviceId } from "./redact-DJV0XM3n.js";
|
|
3
3
|
//#region src/core/async-dispose.ts
|
|
4
4
|
/**
|
|
5
5
|
* Подставляет метод `await using` там, где `Symbol.asyncDispose` отсутствует.
|
|
@@ -85,7 +85,7 @@ function requireOptionalBoolean(value, name) {
|
|
|
85
85
|
//#endregion
|
|
86
86
|
//#region src/core/version.ts
|
|
87
87
|
/** Версия библиотеки. Попадает в `User-Agent`. */
|
|
88
|
-
const LIBRARY_VERSION = "0.9.
|
|
88
|
+
const LIBRARY_VERSION = "0.9.3";
|
|
89
89
|
//#endregion
|
|
90
90
|
//#region src/core/config.ts
|
|
91
91
|
/** Базовый URL API итд.com. Домен записан в punycode: `итд.com`. */
|
|
@@ -386,6 +386,12 @@ const RELATION_FIELDS = [
|
|
|
386
386
|
"before",
|
|
387
387
|
"after"
|
|
388
388
|
];
|
|
389
|
+
const PLUGIN_NAME_PART = "[a-z][a-z0-9]*(?:[-_.][a-z0-9]+)*";
|
|
390
|
+
const PLUGIN_NAME_PATTERN = new RegExp(`^(?:${PLUGIN_NAME_PART}|@${PLUGIN_NAME_PART}(?:/${PLUGIN_NAME_PART})?(?::${PLUGIN_NAME_PART})?)$`);
|
|
391
|
+
const PLUGIN_NAME_FORMAT = "name, @package[:addon] или @scope/package[:addon] с разделителями -, _ и .";
|
|
392
|
+
function isPluginName(value) {
|
|
393
|
+
return PLUGIN_NAME_PATTERN.test(value);
|
|
394
|
+
}
|
|
389
395
|
function validateNameList(plugin, field) {
|
|
390
396
|
const values = plugin[field];
|
|
391
397
|
if (values === void 0) return;
|
|
@@ -393,6 +399,7 @@ function validateNameList(plugin, field) {
|
|
|
393
399
|
const seen = /* @__PURE__ */ new Set();
|
|
394
400
|
for (const value of values) {
|
|
395
401
|
if (typeof value !== "string" || value.trim() === "") throw new ItdConfigError(`плагин «${plugin.name}»: ${field} содержит пустое имя плагина`);
|
|
402
|
+
if (!isPluginName(value)) throw new ItdConfigError(`плагин «${plugin.name}»: имя «${value}» в ${field} должно соответствовать ${PLUGIN_NAME_FORMAT}`);
|
|
396
403
|
if (value === plugin.name) throw new ItdConfigError(`плагин «${plugin.name}» не может указать себя в ${field}`);
|
|
397
404
|
if (seen.has(value)) throw new ItdConfigError(`плагин «${plugin.name}»: ${field} повторяет имя «${value}»`);
|
|
398
405
|
seen.add(value);
|
|
@@ -411,6 +418,7 @@ function validatePluginDefinition(plugin) {
|
|
|
411
418
|
if (typeof plugin?.install !== "function") throw new ItdConfigError("Плагин должен быть объектом с методом install()");
|
|
412
419
|
const name = plugin.name;
|
|
413
420
|
if (typeof name !== "string" || name.trim() === "") throw new ItdConfigError("У плагина должно быть непустое имя");
|
|
421
|
+
if (!isPluginName(name)) throw new ItdConfigError(`Имя плагина «${name}» должно соответствовать ${PLUGIN_NAME_FORMAT}`);
|
|
414
422
|
for (const field of RELATION_FIELDS) validateNameList(plugin, field);
|
|
415
423
|
}
|
|
416
424
|
function addEdge(from, to, edges, indegree) {
|
|
@@ -649,7 +657,7 @@ var PluginRegistry = class {
|
|
|
649
657
|
*
|
|
650
658
|
* @param execute выполнение логической операции, вызываемое самым внутренним transformer
|
|
651
659
|
*/
|
|
652
|
-
async run(request, execute) {
|
|
660
|
+
async run(request, execute, signal) {
|
|
653
661
|
const ordered = this.#ordered;
|
|
654
662
|
if (ordered.length === 0) return execute(request);
|
|
655
663
|
const entries = [...ordered];
|
|
@@ -661,6 +669,7 @@ var PluginRegistry = class {
|
|
|
661
669
|
const operationId = request.operationId;
|
|
662
670
|
const method = request.method.toUpperCase();
|
|
663
671
|
const retrySafety = request.retrySafety;
|
|
672
|
+
const context = Object.freeze({ signal });
|
|
664
673
|
const scoped = (current) => {
|
|
665
674
|
const prepared = current.retrySafety === void 0 ? {
|
|
666
675
|
...current,
|
|
@@ -683,7 +692,7 @@ var PluginRegistry = class {
|
|
|
683
692
|
called = true;
|
|
684
693
|
validateContract(prepared);
|
|
685
694
|
return next(scoped(prepared));
|
|
686
|
-
});
|
|
695
|
+
}, context);
|
|
687
696
|
}, (current) => {
|
|
688
697
|
validateContract(current);
|
|
689
698
|
return execute(scoped(current));
|
|
@@ -1067,8 +1076,8 @@ function seedLimit(bucket, options) {
|
|
|
1067
1076
|
* Очереди по парам «направление — серверный счётчик частоты».
|
|
1068
1077
|
*
|
|
1069
1078
|
* Направление — origin уже разрешённого URL: разные локальные имена одного хоста делят
|
|
1070
|
-
* лимит, а запрос с разовым внешним `baseUrl` не попадает в основную очередь.
|
|
1071
|
-
*
|
|
1079
|
+
* лимит, а запрос с разовым внешним `baseUrl` не попадает в основную очередь. Созданные
|
|
1080
|
+
* очереди сохраняются до `clear()`/`dispose()`, чтобы не терять известное состояние лимитов.
|
|
1072
1081
|
*
|
|
1073
1082
|
* @internal
|
|
1074
1083
|
*/
|
|
@@ -1278,10 +1287,117 @@ var ServiceRegistry = class {
|
|
|
1278
1287
|
}
|
|
1279
1288
|
};
|
|
1280
1289
|
//#endregion
|
|
1290
|
+
//#region src/core/plugins/hooks.ts
|
|
1291
|
+
/** Проверяет наличие публичного hook, не создавая дорогой контекст. @internal */
|
|
1292
|
+
function hasRequestHook(hooks, field) {
|
|
1293
|
+
return hooks[field] !== void 0;
|
|
1294
|
+
}
|
|
1295
|
+
/** Последовательно вызывает публичный hook конструктора. @internal */
|
|
1296
|
+
async function dispatchRequestHook(hooks, field, context) {
|
|
1297
|
+
const hook = hooks[field];
|
|
1298
|
+
await hook?.(context);
|
|
1299
|
+
}
|
|
1300
|
+
//#endregion
|
|
1301
|
+
//#region src/core/execution/lifecycle.ts
|
|
1302
|
+
/** Объединяет отмену вызова, завершение клиента и общий таймаут логической операции. @internal */
|
|
1303
|
+
function createRequestAbortScope(signal, lifetimeSignal, timeout, clock) {
|
|
1304
|
+
const controller = new AbortController();
|
|
1305
|
+
let timeoutReached = false;
|
|
1306
|
+
const link = (source) => {
|
|
1307
|
+
if (!source) return void 0;
|
|
1308
|
+
if (source.aborted) {
|
|
1309
|
+
controller.abort(source.reason);
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
const onAbort = () => controller.abort(source.reason);
|
|
1313
|
+
source.addEventListener("abort", onAbort, { once: true });
|
|
1314
|
+
return () => source.removeEventListener("abort", onAbort);
|
|
1315
|
+
};
|
|
1316
|
+
const detach = [link(signal), link(lifetimeSignal)];
|
|
1317
|
+
const cancelTimer = timeout > 0 ? clock.schedule(() => {
|
|
1318
|
+
timeoutReached = true;
|
|
1319
|
+
controller.abort();
|
|
1320
|
+
}, timeout) : void 0;
|
|
1321
|
+
return {
|
|
1322
|
+
signal: controller.signal,
|
|
1323
|
+
timedOut: () => timeoutReached,
|
|
1324
|
+
cleanup: () => {
|
|
1325
|
+
cancelTimer?.();
|
|
1326
|
+
for (const remove of detach) remove?.();
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
/** Прерывает только ожидание; исходный промис может корректно завершить общую фоновую работу. @internal */
|
|
1331
|
+
function waitForRequest(promise, signal) {
|
|
1332
|
+
if (signal.aborted) {
|
|
1333
|
+
promise.catch(() => {});
|
|
1334
|
+
return Promise.reject(new ItdAbortError(void 0, { cause: signal.reason }));
|
|
1335
|
+
}
|
|
1336
|
+
let onAbort;
|
|
1337
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
1338
|
+
onAbort = () => reject(new ItdAbortError(void 0, { cause: signal.reason }));
|
|
1339
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1340
|
+
});
|
|
1341
|
+
return Promise.race([promise, aborted]).finally(() => {
|
|
1342
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
/** Возвращает публичную ошибку, соответствующую причине остановки операции. @internal */
|
|
1346
|
+
function requestAbortError(scope, request, fallback) {
|
|
1347
|
+
if (scope.timedOut()) return new ItdTimeoutError({
|
|
1348
|
+
timeout: request.timeout,
|
|
1349
|
+
method: request.method.toUpperCase(),
|
|
1350
|
+
path: request.path
|
|
1351
|
+
});
|
|
1352
|
+
if (scope.signal.aborted && !(fallback instanceof ItdAbortError)) return new ItdAbortError(`Запрос ${request.method.toUpperCase()} ${request.path} отменён`, { cause: scope.signal.reason });
|
|
1353
|
+
return fallback;
|
|
1354
|
+
}
|
|
1355
|
+
//#endregion
|
|
1281
1356
|
//#region src/core/execution/pipeline.ts
|
|
1282
1357
|
const REQUEST_ATTEMPT_STATE = Symbol("itd-api.request-attempt-state");
|
|
1283
1358
|
const REQUEST_QUEUE_KEY = Symbol("itd-api.request-queue-key");
|
|
1359
|
+
const REQUEST_AUTH_RECOVERY_STATE = Symbol("itd-api.request-auth-recovery-state");
|
|
1360
|
+
const REQUEST_ERROR_OBSERVATION_STATE = Symbol("itd-api.request-error-observation-state");
|
|
1284
1361
|
const DISPOSE_CLEANUP_REQUEST = Symbol("itd-api.dispose-cleanup-request");
|
|
1362
|
+
/** Возвращает общее для всех retry состояние восстановления авторизации. @internal */
|
|
1363
|
+
function requestAuthRecoveryState(request) {
|
|
1364
|
+
const internal = request;
|
|
1365
|
+
const current = internal[REQUEST_AUTH_RECOVERY_STATE];
|
|
1366
|
+
if (current) return current;
|
|
1367
|
+
const state = {
|
|
1368
|
+
recovered: false,
|
|
1369
|
+
preparationErrors: /* @__PURE__ */ new Set()
|
|
1370
|
+
};
|
|
1371
|
+
internal[REQUEST_AUTH_RECOVERY_STATE] = state;
|
|
1372
|
+
return state;
|
|
1373
|
+
}
|
|
1374
|
+
/** Создаёт общую для копий логического запроса отметку вызова `onError`. @internal */
|
|
1375
|
+
function trackRequestErrorObservation(request) {
|
|
1376
|
+
const internal = request;
|
|
1377
|
+
internal[REQUEST_ERROR_OBSERVATION_STATE] ??= {
|
|
1378
|
+
errors: /* @__PURE__ */ new Set(),
|
|
1379
|
+
lifecycleAbortedDuringNotification: false
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
/** Отмечает, что ошибка логического запроса уже была передана в `onError`. @internal */
|
|
1383
|
+
function markRequestErrorObserved(request, error) {
|
|
1384
|
+
trackRequestErrorObservation(request);
|
|
1385
|
+
request[REQUEST_ERROR_OBSERVATION_STATE]?.errors.add(error);
|
|
1386
|
+
}
|
|
1387
|
+
/** Была ли конкретная ошибка этой логической операции уже передана в `onError`. @internal */
|
|
1388
|
+
function wasRequestErrorObserved(request, error) {
|
|
1389
|
+
return request[REQUEST_ERROR_OBSERVATION_STATE]?.errors.has(error) ?? false;
|
|
1390
|
+
}
|
|
1391
|
+
/** Отмечает отмену lifecycle во время уже начатого локального `onError`. @internal */
|
|
1392
|
+
function markRequestErrorNotificationAborted(request) {
|
|
1393
|
+
trackRequestErrorObservation(request);
|
|
1394
|
+
const state = request[REQUEST_ERROR_OBSERVATION_STATE];
|
|
1395
|
+
if (state) state.lifecycleAbortedDuringNotification = true;
|
|
1396
|
+
}
|
|
1397
|
+
/** Нужно ли верхней границе не запускать тот же `onError` повторно после отмены. @internal */
|
|
1398
|
+
function wasRequestErrorNotificationAborted(request) {
|
|
1399
|
+
return request[REQUEST_ERROR_OBSERVATION_STATE]?.lifecycleAbortedDuringNotification ?? false;
|
|
1400
|
+
}
|
|
1285
1401
|
/** Один раз присваивает низкоуровневому запросу семантический ID до входа в middleware. */
|
|
1286
1402
|
function identifyRequest(request) {
|
|
1287
1403
|
return request.operationId === void 0 ? {
|
|
@@ -1378,10 +1494,20 @@ var HttpClient = class {
|
|
|
1378
1494
|
#handler;
|
|
1379
1495
|
#plugins;
|
|
1380
1496
|
#baseUrl;
|
|
1497
|
+
#timeout;
|
|
1498
|
+
#clock;
|
|
1499
|
+
#lifetimeSignal;
|
|
1500
|
+
#hooks;
|
|
1501
|
+
#assertActive;
|
|
1381
1502
|
constructor(deps) {
|
|
1382
1503
|
this.#handler = deps.handler;
|
|
1383
1504
|
this.#plugins = deps.plugins;
|
|
1384
1505
|
this.#baseUrl = deps.baseUrl;
|
|
1506
|
+
this.#timeout = deps.timeout;
|
|
1507
|
+
this.#clock = deps.clock;
|
|
1508
|
+
this.#lifetimeSignal = deps.lifetimeSignal;
|
|
1509
|
+
this.#hooks = deps.hooks;
|
|
1510
|
+
this.#assertActive = deps.assertActive;
|
|
1385
1511
|
}
|
|
1386
1512
|
/** Базовый URL, к которому обращается клиент. */
|
|
1387
1513
|
get baseUrl() {
|
|
@@ -1398,7 +1524,7 @@ var HttpClient = class {
|
|
|
1398
1524
|
*/
|
|
1399
1525
|
request(options) {
|
|
1400
1526
|
const request = identifyRequest(options);
|
|
1401
|
-
return this.#
|
|
1527
|
+
return this.#runWithLifecycle(request, (prepared) => this.#handler(prepared));
|
|
1402
1528
|
}
|
|
1403
1529
|
/** Выполняет контракт операции; `next()` плагина возвращает результат после `read`. */
|
|
1404
1530
|
execute(operation, options) {
|
|
@@ -1411,7 +1537,7 @@ var HttpClient = class {
|
|
|
1411
1537
|
method: operation.method,
|
|
1412
1538
|
retrySafety: options.retrySafety ?? operation.retrySafety
|
|
1413
1539
|
});
|
|
1414
|
-
return this.#
|
|
1540
|
+
return this.#runWithLifecycle(request, async (prepared) => operation.read(await this.#handler(prepared), prepared));
|
|
1415
1541
|
}
|
|
1416
1542
|
/** Выполняет внутреннюю операцию финализации после начала `ItdClient.dispose()`. @internal */
|
|
1417
1543
|
cleanupOperation(operation, options) {
|
|
@@ -1421,24 +1547,65 @@ var HttpClient = class {
|
|
|
1421
1547
|
method: operation.method,
|
|
1422
1548
|
retrySafety: options.retrySafety ?? operation.retrySafety
|
|
1423
1549
|
});
|
|
1424
|
-
return this.#
|
|
1550
|
+
return this.#runWithLifecycle(request, async (prepared) => {
|
|
1425
1551
|
const cleanupRequest = markDisposeCleanupRequest(prepared);
|
|
1426
1552
|
return operation.read(await this.#handler(cleanupRequest), prepared);
|
|
1427
|
-
});
|
|
1553
|
+
}, true);
|
|
1554
|
+
}
|
|
1555
|
+
async #runWithLifecycle(request, execute, allowDisposed = false) {
|
|
1556
|
+
if (!allowDisposed) this.#assertActive?.();
|
|
1557
|
+
const timeout = request.timeout ?? this.#timeout;
|
|
1558
|
+
const scope = createRequestAbortScope(request.signal, this.#lifetimeSignal, timeout, this.#clock);
|
|
1559
|
+
const startedAt = this.#clock.now();
|
|
1560
|
+
trackRequestErrorObservation(request);
|
|
1561
|
+
try {
|
|
1562
|
+
const pending = this.#plugins.run(request, (prepared) => execute({
|
|
1563
|
+
...prepared,
|
|
1564
|
+
signal: scope.signal,
|
|
1565
|
+
timeout: 0
|
|
1566
|
+
}), scope.signal);
|
|
1567
|
+
return await waitForRequest(Promise.resolve(pending), scope.signal);
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
const failure = requestAbortError(scope, {
|
|
1570
|
+
timeout,
|
|
1571
|
+
method: request.method,
|
|
1572
|
+
path: request.path
|
|
1573
|
+
}, error);
|
|
1574
|
+
if (!(wasRequestErrorObserved(request, error) || scope.signal.aborted && wasRequestErrorNotificationAborted(request))) {
|
|
1575
|
+
markRequestErrorObserved(request, error);
|
|
1576
|
+
let headers;
|
|
1577
|
+
try {
|
|
1578
|
+
headers = new Headers({
|
|
1579
|
+
...request.layerHeaders,
|
|
1580
|
+
...request.headers
|
|
1581
|
+
});
|
|
1582
|
+
} catch {
|
|
1583
|
+
headers = new Headers();
|
|
1584
|
+
}
|
|
1585
|
+
const notification = dispatchRequestHook(this.#hooks, "onError", {
|
|
1586
|
+
operationId: request.operationId,
|
|
1587
|
+
signal: scope.signal,
|
|
1588
|
+
method: request.method.toUpperCase(),
|
|
1589
|
+
path: request.path,
|
|
1590
|
+
url: joinUrl(request.baseUrl ?? this.#baseUrl, request.path) + buildQuery(request.query),
|
|
1591
|
+
headers,
|
|
1592
|
+
attempt: request.attempt ?? 1,
|
|
1593
|
+
duration: this.#clock.now() - startedAt,
|
|
1594
|
+
error: failure
|
|
1595
|
+
});
|
|
1596
|
+
try {
|
|
1597
|
+
await waitForRequest(notification, scope.signal);
|
|
1598
|
+
} catch (hookError) {
|
|
1599
|
+
if (!scope.signal.aborted) throw hookError;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
throw failure;
|
|
1603
|
+
} finally {
|
|
1604
|
+
scope.cleanup();
|
|
1605
|
+
}
|
|
1428
1606
|
}
|
|
1429
1607
|
};
|
|
1430
1608
|
//#endregion
|
|
1431
|
-
//#region src/core/plugins/hooks.ts
|
|
1432
|
-
/** Проверяет наличие публичного hook, не создавая дорогой контекст. @internal */
|
|
1433
|
-
function hasRequestHook(hooks, field) {
|
|
1434
|
-
return hooks[field] !== void 0;
|
|
1435
|
-
}
|
|
1436
|
-
/** Последовательно вызывает публичный hook конструктора. @internal */
|
|
1437
|
-
async function dispatchRequestHook(hooks, field, context) {
|
|
1438
|
-
const hook = hooks[field];
|
|
1439
|
-
await hook?.(context);
|
|
1440
|
-
}
|
|
1441
|
-
//#endregion
|
|
1442
1609
|
//#region src/core/operation.ts
|
|
1443
1610
|
/** Семантическая безопасность автоматического повтора операции. */
|
|
1444
1611
|
const RetrySafety = Object.freeze({
|
|
@@ -1638,6 +1805,19 @@ function createServicesMiddleware(registry) {
|
|
|
1638
1805
|
return next(prepared);
|
|
1639
1806
|
};
|
|
1640
1807
|
}
|
|
1808
|
+
/** Однократная auth-подготовка вне retry исходного запроса. */
|
|
1809
|
+
function createAuthPreflightMiddleware(getAuth) {
|
|
1810
|
+
return async (request, next) => {
|
|
1811
|
+
if (!request.skipAuth) {
|
|
1812
|
+
const auth = getAuth();
|
|
1813
|
+
if (auth.preflight) {
|
|
1814
|
+
const pending = auth.preflight(request.skipAuthRefresh !== true);
|
|
1815
|
+
await (request.signal ? waitForRequest(pending, request.signal) : pending);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
return next(request);
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1641
1821
|
/**
|
|
1642
1822
|
* Подготавливает auth state до входа транспортной попытки в очередь.
|
|
1643
1823
|
*
|
|
@@ -1646,7 +1826,15 @@ function createServicesMiddleware(registry) {
|
|
|
1646
1826
|
*/
|
|
1647
1827
|
function createAuthPreparationMiddleware(auth) {
|
|
1648
1828
|
return async (request, next) => {
|
|
1649
|
-
if (!request.skipAuth)
|
|
1829
|
+
if (!request.skipAuth) {
|
|
1830
|
+
const pending = auth.prepare();
|
|
1831
|
+
try {
|
|
1832
|
+
await (request.signal ? waitForRequest(pending, request.signal) : pending);
|
|
1833
|
+
} catch (error) {
|
|
1834
|
+
requestAuthRecoveryState(request).preparationErrors.add(error);
|
|
1835
|
+
throw error;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1650
1838
|
return next(request);
|
|
1651
1839
|
};
|
|
1652
1840
|
}
|
|
@@ -1681,11 +1869,15 @@ function createAttemptMiddleware() {
|
|
|
1681
1869
|
*/
|
|
1682
1870
|
function createAuthRecoveryMiddleware(auth) {
|
|
1683
1871
|
return async (request, next) => {
|
|
1872
|
+
const recovery = requestAuthRecoveryState(request);
|
|
1684
1873
|
try {
|
|
1685
1874
|
return await next(request);
|
|
1686
1875
|
} catch (error) {
|
|
1687
|
-
|
|
1688
|
-
if (
|
|
1876
|
+
const preparationFailed = recovery.preparationErrors.delete(error);
|
|
1877
|
+
if (request.skipAuthRefresh || recovery.recovered || preparationFailed || !isItdApiError(error) || error.status !== 401) throw error;
|
|
1878
|
+
const pending = auth.recover();
|
|
1879
|
+
if (!await (request.signal ? waitForRequest(pending, request.signal) : pending)) throw error;
|
|
1880
|
+
recovery.recovered = true;
|
|
1689
1881
|
return next({
|
|
1690
1882
|
...request,
|
|
1691
1883
|
skipAuthRefresh: true
|
|
@@ -1740,8 +1932,9 @@ function createRetryMiddleware(deps) {
|
|
|
1740
1932
|
else retryAttempt += 1;
|
|
1741
1933
|
const delay = nextDelay(error, retryAttempt, rateLimitAttempt, trackedRequest, policy, backoff);
|
|
1742
1934
|
if (delay === void 0) throw error;
|
|
1743
|
-
|
|
1935
|
+
const notification = dispatchRequestHook(deps.hooks, "onRetry", {
|
|
1744
1936
|
operationId: request.operationId,
|
|
1937
|
+
signal: request.signal,
|
|
1745
1938
|
method,
|
|
1746
1939
|
path: request.path,
|
|
1747
1940
|
url: deps.buildUrl(request),
|
|
@@ -1753,6 +1946,7 @@ function createRetryMiddleware(deps) {
|
|
|
1753
1946
|
error,
|
|
1754
1947
|
delay
|
|
1755
1948
|
});
|
|
1949
|
+
await (request.signal ? waitForRequest(notification, request.signal) : notification);
|
|
1756
1950
|
deps.logger?.debug(`повтор ${method} ${request.path}, попытка ${transportAttempt + 1} через ${delay} мс`);
|
|
1757
1951
|
await sleep(deps.clock ?? systemClock, delay, request.signal);
|
|
1758
1952
|
}
|
|
@@ -2099,39 +2293,6 @@ function abortable(promise, signal) {
|
|
|
2099
2293
|
});
|
|
2100
2294
|
}
|
|
2101
2295
|
/**
|
|
2102
|
-
* Объединяет пользовательский `AbortSignal`, сигнал жизни клиента и таймаут.
|
|
2103
|
-
*
|
|
2104
|
-
* Реализовано вручную, а не через `AbortSignal.any`: последний появился только в Node 20,
|
|
2105
|
-
* а библиотека поддерживает Node 18. Причина отмены переносится от источника как есть.
|
|
2106
|
-
*/
|
|
2107
|
-
function createAbortBundle(userSignal, lifetimeSignal, timeout, clock) {
|
|
2108
|
-
const controller = new AbortController();
|
|
2109
|
-
let timedOut = false;
|
|
2110
|
-
const link = (source) => {
|
|
2111
|
-
if (!source) return void 0;
|
|
2112
|
-
if (source.aborted) {
|
|
2113
|
-
controller.abort(source.reason);
|
|
2114
|
-
return;
|
|
2115
|
-
}
|
|
2116
|
-
const onAbort = () => controller.abort(source.reason);
|
|
2117
|
-
source.addEventListener("abort", onAbort, { once: true });
|
|
2118
|
-
return () => source.removeEventListener("abort", onAbort);
|
|
2119
|
-
};
|
|
2120
|
-
const unlink = [link(userSignal), link(lifetimeSignal)];
|
|
2121
|
-
const cancelTimer = timeout > 0 ? clock.schedule(() => {
|
|
2122
|
-
timedOut = true;
|
|
2123
|
-
controller.abort();
|
|
2124
|
-
}, timeout) : void 0;
|
|
2125
|
-
return {
|
|
2126
|
-
signal: controller.signal,
|
|
2127
|
-
timedOut: () => timedOut,
|
|
2128
|
-
cleanup: () => {
|
|
2129
|
-
cancelTimer?.();
|
|
2130
|
-
for (const detach of unlink) detach?.();
|
|
2131
|
-
}
|
|
2132
|
-
};
|
|
2133
|
-
}
|
|
2134
|
-
/**
|
|
2135
2296
|
* Единственное место, откуда библиотека ходит в сеть.
|
|
2136
2297
|
*
|
|
2137
2298
|
* Отвечает за сборку URL, заголовки, cookie, таймауты, разбор ответа и превращение любой
|
|
@@ -2161,44 +2322,58 @@ var Transport = class {
|
|
|
2161
2322
|
const request = identifyRequest(input);
|
|
2162
2323
|
const method = request.method.toUpperCase();
|
|
2163
2324
|
const url = this.buildUrl(request);
|
|
2164
|
-
const headers = await this.#buildHeaders(request, url);
|
|
2165
2325
|
const attempt = request.attempt ?? 1;
|
|
2166
2326
|
const timeout = request.timeout ?? this.#config.timeout;
|
|
2167
|
-
const abort =
|
|
2327
|
+
const abort = createRequestAbortScope(request.signal, this.#deps.lifetimeSignal, timeout, this.#config.clock);
|
|
2168
2328
|
const startedAt = this.#config.clock.now();
|
|
2169
2329
|
let cleanupBody;
|
|
2170
2330
|
try {
|
|
2331
|
+
const headers = await abortable(this.#buildHeaders(request, url), abort.signal).catch((error) => {
|
|
2332
|
+
throw this.#toTransportError(error, abort, request, method, timeout);
|
|
2333
|
+
});
|
|
2171
2334
|
let body;
|
|
2172
2335
|
try {
|
|
2173
2336
|
const prepared = await this.#prepareBody(request, headers, abort.signal, attempt);
|
|
2174
2337
|
body = prepared.body;
|
|
2175
2338
|
cleanupBody = prepared.cleanup;
|
|
2176
2339
|
} catch (error) {
|
|
2177
|
-
const failure = this.#toTransportError(error, abort, request, method, timeout);
|
|
2340
|
+
const failure = abort.signal.aborted || error instanceof ItdError ? this.#toTransportError(error, abort, request, method, timeout) : new ItdConfigError(`Не удалось подготовить тело ${method} ${request.path}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
2178
2341
|
const context = {
|
|
2179
2342
|
operationId: request.operationId,
|
|
2343
|
+
signal: abort.signal,
|
|
2180
2344
|
method,
|
|
2181
2345
|
path: request.path,
|
|
2182
2346
|
url,
|
|
2183
2347
|
headers,
|
|
2184
2348
|
attempt
|
|
2185
2349
|
};
|
|
2186
|
-
await
|
|
2350
|
+
await this.#dispatchErrorHook(request, {
|
|
2187
2351
|
...context,
|
|
2188
2352
|
duration: this.#config.clock.now() - startedAt,
|
|
2189
2353
|
error: failure
|
|
2190
|
-
});
|
|
2354
|
+
}, abort, timeout);
|
|
2191
2355
|
throw failure;
|
|
2192
2356
|
}
|
|
2193
2357
|
const context = {
|
|
2194
2358
|
operationId: request.operationId,
|
|
2359
|
+
signal: abort.signal,
|
|
2195
2360
|
method,
|
|
2196
2361
|
path: request.path,
|
|
2197
2362
|
url,
|
|
2198
2363
|
headers,
|
|
2199
2364
|
attempt
|
|
2200
2365
|
};
|
|
2201
|
-
|
|
2366
|
+
try {
|
|
2367
|
+
await abortable(dispatchRequestHook(this.#config.hooks, "onRequest", context), abort.signal);
|
|
2368
|
+
} catch (error) {
|
|
2369
|
+
const failure = abort.signal.aborted ? this.#toTransportError(error, abort, request, method, timeout) : error;
|
|
2370
|
+
await this.#dispatchErrorHook(request, {
|
|
2371
|
+
...context,
|
|
2372
|
+
duration: this.#config.clock.now() - startedAt,
|
|
2373
|
+
error: failure
|
|
2374
|
+
}, abort, timeout);
|
|
2375
|
+
throw failure;
|
|
2376
|
+
}
|
|
2202
2377
|
this.#config.logger?.debug(`→ ${method} ${request.path}`, {
|
|
2203
2378
|
headers: redactHeaders(headers),
|
|
2204
2379
|
body: request.bodyFactory ? "[повторяемое тело]" : redactBody(request.body)
|
|
@@ -2226,11 +2401,11 @@ var Transport = class {
|
|
|
2226
2401
|
});
|
|
2227
2402
|
} catch (error) {
|
|
2228
2403
|
const duration = this.#config.clock.now() - startedAt;
|
|
2229
|
-
await
|
|
2404
|
+
await this.#dispatchErrorHook(request, {
|
|
2230
2405
|
...context,
|
|
2231
2406
|
duration,
|
|
2232
2407
|
error
|
|
2233
|
-
});
|
|
2408
|
+
}, abort, timeout);
|
|
2234
2409
|
this.#config.logger?.warn(`× ${method} ${request.path} (${duration} мс): ${error instanceof Error ? error.message : String(error)}`);
|
|
2235
2410
|
throw error;
|
|
2236
2411
|
}
|
|
@@ -2238,14 +2413,30 @@ var Transport = class {
|
|
|
2238
2413
|
const { limit, remaining } = readRateLimit(response.headers);
|
|
2239
2414
|
this.#deps.onRateLimit(limit, remaining, request);
|
|
2240
2415
|
}
|
|
2241
|
-
if (this.#config.useCookieJar) this.#deps.cookies?.setFromResponse(url, response);
|
|
2242
|
-
if (response.ok && hasRequestHook(this.#config.hooks, "onResponse"))
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2416
|
+
if (this.#config.useCookieJar) (request.cookieJar ?? this.#deps.cookies)?.setFromResponse(response.url || url, response);
|
|
2417
|
+
if (response.ok && hasRequestHook(this.#config.hooks, "onResponse")) {
|
|
2418
|
+
const hookResponse = response.clone();
|
|
2419
|
+
try {
|
|
2420
|
+
await abortable(dispatchRequestHook(this.#config.hooks, "onResponse", {
|
|
2421
|
+
...context,
|
|
2422
|
+
status: response.status,
|
|
2423
|
+
duration: this.#config.clock.now() - startedAt,
|
|
2424
|
+
response: hookResponse
|
|
2425
|
+
}), abort.signal);
|
|
2426
|
+
} catch (error) {
|
|
2427
|
+
response.body?.cancel().catch(() => {});
|
|
2428
|
+
const failure = abort.signal.aborted ? this.#toTransportError(error, abort, request, method, timeout) : error;
|
|
2429
|
+
await this.#dispatchErrorHook(request, {
|
|
2430
|
+
...context,
|
|
2431
|
+
duration: this.#config.clock.now() - startedAt,
|
|
2432
|
+
error: failure
|
|
2433
|
+
}, abort, timeout);
|
|
2434
|
+
throw failure;
|
|
2435
|
+
} finally {
|
|
2436
|
+
if (!hookResponse.bodyUsed) hookResponse.body?.cancel().catch(() => {});
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
const payload = await this.#readBodyOrFail(response, context, request, method, abort, timeout, startedAt);
|
|
2249
2440
|
const duration = this.#config.clock.now() - startedAt;
|
|
2250
2441
|
if (!response.ok) {
|
|
2251
2442
|
const error = createApiError({
|
|
@@ -2258,11 +2449,11 @@ var Transport = class {
|
|
|
2258
2449
|
response,
|
|
2259
2450
|
body: payload
|
|
2260
2451
|
});
|
|
2261
|
-
await
|
|
2452
|
+
await this.#dispatchErrorHook(request, {
|
|
2262
2453
|
...context,
|
|
2263
2454
|
duration,
|
|
2264
2455
|
error
|
|
2265
|
-
});
|
|
2456
|
+
}, abort, timeout);
|
|
2266
2457
|
this.#config.logger?.warn(`← ${response.status} ${method} ${request.path} (${duration} мс): ${error.message}`);
|
|
2267
2458
|
throw error;
|
|
2268
2459
|
}
|
|
@@ -2278,6 +2469,21 @@ var Transport = class {
|
|
|
2278
2469
|
}
|
|
2279
2470
|
}
|
|
2280
2471
|
};
|
|
2472
|
+
async #dispatchErrorHook(request, context, abort, timeout) {
|
|
2473
|
+
markRequestErrorObserved(request, context.error);
|
|
2474
|
+
const onAbort = () => markRequestErrorNotificationAborted(request);
|
|
2475
|
+
if (abort.signal.aborted) onAbort();
|
|
2476
|
+
else abort.signal.addEventListener("abort", onAbort, { once: true });
|
|
2477
|
+
try {
|
|
2478
|
+
await abortable(dispatchRequestHook(this.#config.hooks, "onError", context), abort.signal);
|
|
2479
|
+
} catch (error) {
|
|
2480
|
+
const failure = abort.signal.aborted ? this.#toTransportError(error, abort, request, context.method, timeout) : error;
|
|
2481
|
+
markRequestErrorObserved(request, failure);
|
|
2482
|
+
throw failure;
|
|
2483
|
+
} finally {
|
|
2484
|
+
abort.signal.removeEventListener("abort", onAbort);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2281
2487
|
/** Подготавливает тело внутри попытки, чтобы поток можно было открыть заново при retry. */
|
|
2282
2488
|
async #prepareBody(request, headers, signal, attempt) {
|
|
2283
2489
|
if (request.bodyFactory) {
|
|
@@ -2320,18 +2526,17 @@ var Transport = class {
|
|
|
2320
2526
|
};
|
|
2321
2527
|
}
|
|
2322
2528
|
/** Читает тело и преобразует ошибку чтения в транспортную ошибку библиотеки. */
|
|
2323
|
-
async #readBodyOrFail(response, context, request, method, abort, timeout) {
|
|
2324
|
-
const startedAt = this.#config.clock.now();
|
|
2529
|
+
async #readBodyOrFail(response, context, request, method, abort, timeout, startedAt) {
|
|
2325
2530
|
try {
|
|
2326
2531
|
return await abortable(readBody(response), abort.signal);
|
|
2327
2532
|
} catch (error) {
|
|
2328
2533
|
await response.body?.cancel().catch(() => {});
|
|
2329
2534
|
const failure = this.#toTransportError(error, abort, request, method, timeout);
|
|
2330
|
-
await
|
|
2535
|
+
await this.#dispatchErrorHook(request, {
|
|
2331
2536
|
...context,
|
|
2332
2537
|
duration: this.#config.clock.now() - startedAt,
|
|
2333
2538
|
error: failure
|
|
2334
|
-
});
|
|
2539
|
+
}, abort, timeout);
|
|
2335
2540
|
this.#config.logger?.warn(`× ${method} ${request.path}: не удалось прочитать тело ответа — ${failure.message}`);
|
|
2336
2541
|
throw failure;
|
|
2337
2542
|
}
|
|
@@ -2349,14 +2554,14 @@ var Transport = class {
|
|
|
2349
2554
|
* Собирает общие заголовки клиента: `User-Agent`, идентификатор устройства,
|
|
2350
2555
|
* заголовки конфигурации и cookie для указанного адреса.
|
|
2351
2556
|
*/
|
|
2352
|
-
async platformHeaders(url) {
|
|
2557
|
+
async platformHeaders(url, cookieJar = this.#deps.cookies) {
|
|
2353
2558
|
const headers = new Headers();
|
|
2354
2559
|
headers.set("X-Requested-With", "XMLHttpRequest");
|
|
2355
2560
|
if (this.#config.userAgent) setHeader(headers, "User-Agent", this.#config.userAgent);
|
|
2356
2561
|
if (this.#deps.getDeviceId) setHeader(headers, "X-Device-Id", await this.#deps.getDeviceId());
|
|
2357
2562
|
for (const [name, value] of Object.entries(this.#config.headers)) setHeader(headers, name, value);
|
|
2358
|
-
if (this.#config.useCookieJar &&
|
|
2359
|
-
const cookie =
|
|
2563
|
+
if (this.#config.useCookieJar && cookieJar) {
|
|
2564
|
+
const cookie = cookieJar.getHeader(url);
|
|
2360
2565
|
if (cookie) setHeader(headers, "Cookie", cookie);
|
|
2361
2566
|
}
|
|
2362
2567
|
return headers;
|
|
@@ -2366,7 +2571,7 @@ var Transport = class {
|
|
|
2366
2571
|
* Заголовки вызова применяются последними.
|
|
2367
2572
|
*/
|
|
2368
2573
|
async #buildHeaders(request, url) {
|
|
2369
|
-
const headers = await this.platformHeaders(url);
|
|
2574
|
+
const headers = await this.platformHeaders(url, request.cookieJar);
|
|
2370
2575
|
if (!headers.has("Accept")) headers.set("Accept", "application/json");
|
|
2371
2576
|
for (const [name, value] of Object.entries(request.layerHeaders ?? {})) setHeader(headers, name, value);
|
|
2372
2577
|
for (const [name, value] of Object.entries(request.headers ?? {})) setHeader(headers, name, value);
|
|
@@ -2398,6 +2603,7 @@ var Transport = class {
|
|
|
2398
2603
|
const ClientRuntimeStage = Object.freeze({
|
|
2399
2604
|
OperationPlugins: "operation_plugins",
|
|
2400
2605
|
Services: "services",
|
|
2606
|
+
AuthPreflight: "auth_preflight",
|
|
2401
2607
|
Retry: "retry",
|
|
2402
2608
|
AuthRecovery: "auth_recovery",
|
|
2403
2609
|
AuthPreparation: "auth_preparation",
|
|
@@ -2473,6 +2679,10 @@ function createClientRuntime(options, internals) {
|
|
|
2473
2679
|
name: ClientRuntimeStage.Services,
|
|
2474
2680
|
middleware: createServicesMiddleware(services)
|
|
2475
2681
|
},
|
|
2682
|
+
{
|
|
2683
|
+
name: ClientRuntimeStage.AuthPreflight,
|
|
2684
|
+
middleware: createAuthPreflightMiddleware(() => auth)
|
|
2685
|
+
},
|
|
2476
2686
|
{
|
|
2477
2687
|
name: ClientRuntimeStage.Retry,
|
|
2478
2688
|
middleware: createRetryMiddleware({
|
|
@@ -2523,7 +2733,12 @@ function createClientRuntime(options, internals) {
|
|
|
2523
2733
|
const http = new HttpClient({
|
|
2524
2734
|
handler: clientHandler,
|
|
2525
2735
|
plugins,
|
|
2526
|
-
baseUrl: config.baseUrl
|
|
2736
|
+
baseUrl: config.baseUrl,
|
|
2737
|
+
timeout: config.timeout,
|
|
2738
|
+
clock: config.clock,
|
|
2739
|
+
lifetimeSignal: lifetime.signal,
|
|
2740
|
+
hooks: config.hooks,
|
|
2741
|
+
assertActive: () => internals.assertActive?.("выполнить новый запрос")
|
|
2527
2742
|
});
|
|
2528
2743
|
auth = internals.auth({
|
|
2529
2744
|
config,
|
|
@@ -2741,11 +2956,6 @@ const OPERATIONS = freezeOperations({
|
|
|
2741
2956
|
retrySafety: RetrySafety.Safe,
|
|
2742
2957
|
bucket: "users"
|
|
2743
2958
|
},
|
|
2744
|
-
"users.search": {
|
|
2745
|
-
method: "GET",
|
|
2746
|
-
retrySafety: RetrySafety.Safe,
|
|
2747
|
-
bucket: "users"
|
|
2748
|
-
},
|
|
2749
2959
|
"users.whoToFollow": {
|
|
2750
2960
|
method: "GET",
|
|
2751
2961
|
retrySafety: RetrySafety.Safe,
|
|
@@ -2982,11 +3192,6 @@ const OPERATIONS = freezeOperations({
|
|
|
2982
3192
|
retrySafety: RetrySafety.Safe,
|
|
2983
3193
|
bucket: "notifications"
|
|
2984
3194
|
},
|
|
2985
|
-
"hashtags.search": {
|
|
2986
|
-
method: "GET",
|
|
2987
|
-
retrySafety: RetrySafety.Safe,
|
|
2988
|
-
bucket: "hashtags"
|
|
2989
|
-
},
|
|
2990
3195
|
"hashtags.trending": {
|
|
2991
3196
|
method: "GET",
|
|
2992
3197
|
retrySafety: RetrySafety.Safe,
|
|
@@ -3809,4 +4014,4 @@ function resolveNotificationUrl(notification) {
|
|
|
3809
4014
|
//#endregion
|
|
3810
4015
|
export { orderPluginDefinitions as $, ITD_CATALOG as A, isRecord as B, ReportReason as C, ViewReason as D, SpanType as E, operationMethod as F, pickString as G, pickBoolean as H, operationRetrySafety as I, identityResult as J, RetrySafety as K, BUCKET_LIMITS as L, defineBuiltInOperation as M, isBuiltInOperationId as N, ViewSource as O, operationBucket as P, assertPluginRemovable as Q, DEFAULT_RATE_LIMIT_BUCKET as R, NotificationType as S, ServiceState as T, pickNumber as U, pickArray as V, pickObject as W, mergeService as X, voidResult as Y, RequestQueuePool as Z, FeedTab as _, tokenProvider as a, isRecord$1 as at, ItdErrorCode as b, normalizeNotification as c, createDeadline as ct, canonicalNotificationType as d, DEFAULT_BASE_URL as et, isKnownNotificationType as f, EventChannelStatus as g, CommentSort as h, bearerToken as i, LIBRARY_VERSION as it, OPERATIONS as j, WallAccess as k, readNotificationEvent as l, systemClock as lt, AttachmentType as m, formatNotificationText as n, STATUS_SERVICE as nt, passthroughOperation as o, requireOptionalBoolean as ot, AccessType as p, defineOperation as q, anonymousAuth as r, resolveRateLimit as rt, voidOperation as s, RateLimitPacing as st, resolveNotificationUrl as t, DEFAULT_STATUS_BASE_URL as tt, readUnreadCountEvent as u, installAsyncDisposeFallback as ut, IncidentKind as v, ReportTargetType as w, LikesVisibility as x, InteractionType as y, createClientRuntime as z };
|
|
3811
4016
|
|
|
3812
|
-
//# sourceMappingURL=url-
|
|
4017
|
+
//# sourceMappingURL=url-3Ts6EEHQ.js.map
|