itd-api 0.5.0 → 0.6.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 +6 -4
- package/dist/index.cjs +216 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -179
- package/dist/index.d.ts +55 -179
- package/dist/index.js +217 -80
- package/dist/index.js.map +1 -1
- package/dist/{multi-storage-B0r0AInH.js → multi-storage-BUZaLAPO.js} +13 -9
- package/dist/{multi-storage-B0r0AInH.js.map → multi-storage-BUZaLAPO.js.map} +1 -1
- package/dist/{multi-storage-CLqmzq07.d.ts → multi-storage-BUoEoW8f.d.cts} +3 -7
- package/dist/{multi-storage-mBuCOVZY.cjs → multi-storage-Bf84xiO8.cjs} +12 -14
- package/dist/{multi-storage-mBuCOVZY.cjs.map → multi-storage-Bf84xiO8.cjs.map} +1 -1
- package/dist/{multi-storage-s_PcWPGH.d.cts → multi-storage-CQSlI_kn.d.ts} +3 -7
- package/dist/node.cjs +2 -2
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/dist/{storage-Doe3lpFQ.d.ts → storage-BqMxs76Y.d.cts} +4 -4
- package/dist/{storage-Doe3lpFQ.d.cts → storage-BqMxs76Y.d.ts} +4 -4
- package/dist/{storage-dF8Tio5y.cjs → storage-DnzZPS_9.cjs} +36 -27
- package/dist/{storage-dF8Tio5y.cjs.map → storage-DnzZPS_9.cjs.map} +1 -1
- package/dist/{storage-DWrK3Z4M.js → storage-IHdXw52v.js} +36 -27
- package/dist/{storage-DWrK3Z4M.js.map → storage-IHdXw52v.js.map} +1 -1
- package/dist/web.cjs +1 -1
- package/dist/web.d.cts +1 -1
- package/dist/web.d.ts +1 -1
- package/dist/web.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
import { A as isItdApiError, B as isItdStateError, C as ItdNotFoundError, D as ItdStateError, E as ItdServerError, F as isItdForbiddenError, I as isItdNotFoundError, L as isItdPhoneVerificationError, M as isItdConflictError, N as isItdError, O as ItdTimeoutError, P as isItdFileError, R as isItdRateLimitError, S as ItdNetworkError, T as ItdRateLimitError, V as isItdValidationError, _ as ItdError, b as ItdFileErrorReason, c as isEnumerableKeyValueStore, d as ItdAbortError, f as ItdApiError, g as ItdConflictError, h as ItdConfigError, i as MemoryKeyValueStore, j as isItdAuthError, k as ItdValidationError, l as withCodec, m as ItdAuthError, n as copySession, o as createKeyValueStore, p as ItdApiErrorKind, r as createTokenStorage, s as createRecordKeyValueStore, t as MemoryTokenStorage, u as withNamespace, v as ItdErrorKind, w as ItdPhoneVerificationError, x as ItdForbiddenError, y as ItdFileError, z as isItdServerError } from "./storage-
|
|
1
|
+
import { C as normalizeBaseUrl, S as joinUrl, _ as REFRESH_COOKIE_PATH, a as scopedTokenStorage, b as hostOf, c as isBoundedFileStream, d as optionalBytes, f as DEFAULT_URL_FILE_MAX_BYTES, g as REFRESH_COOKIE, h as CookieJar, i as isRestorableSession, l as isReadableByteStream, n as controlledTokenStorage, o as resolveFileStreamOptions, p as FileTransferMode, r as createMultiTokenStorage, s as boundedFileStream, t as MemoryMultiTokenStorage, u as fileTooLarge, v as buildQuery, w as originOf, x as isSameSite, y as encodePathSegment } from "./multi-storage-BUZaLAPO.js";
|
|
2
|
+
import { A as isItdApiError, B as isItdStateError, C as ItdNotFoundError, D as ItdStateError, E as ItdServerError, F as isItdForbiddenError, I as isItdNotFoundError, L as isItdPhoneVerificationError, M as isItdConflictError, N as isItdError, O as ItdTimeoutError, P as isItdFileError, R as isItdRateLimitError, S as ItdNetworkError, T as ItdRateLimitError, V as isItdValidationError, _ as ItdError, b as ItdFileErrorReason, c as isEnumerableKeyValueStore, d as ItdAbortError, f as ItdApiError, g as ItdConflictError, h as ItdConfigError, i as MemoryKeyValueStore, j as isItdAuthError, k as ItdValidationError, l as withCodec, m as ItdAuthError, n as copySession, o as createKeyValueStore, p as ItdApiErrorKind, r as createTokenStorage, s as createRecordKeyValueStore, t as MemoryTokenStorage, u as withNamespace, v as ItdErrorKind, w as ItdPhoneVerificationError, x as ItdForbiddenError, y as ItdFileError, z as isItdServerError } from "./storage-IHdXw52v.js";
|
|
3
3
|
//#region src/core/emitter.ts
|
|
4
4
|
/**
|
|
5
5
|
* Минимальный типизированный источник событий.
|
|
@@ -434,6 +434,14 @@ const OPERATIONS = freezeOperations({
|
|
|
434
434
|
method: "PUT",
|
|
435
435
|
retrySafety: RetrySafety.Idempotent
|
|
436
436
|
},
|
|
437
|
+
"realtime.poll.updates": {
|
|
438
|
+
method: "GET",
|
|
439
|
+
retrySafety: RetrySafety.Safe
|
|
440
|
+
},
|
|
441
|
+
"realtime.poll.unread": {
|
|
442
|
+
method: "GET",
|
|
443
|
+
retrySafety: RetrySafety.Safe
|
|
444
|
+
},
|
|
437
445
|
"hashtags.search": {
|
|
438
446
|
method: "GET",
|
|
439
447
|
retrySafety: RetrySafety.Safe
|
|
@@ -667,6 +675,11 @@ const AUTH_PATHS = {
|
|
|
667
675
|
* Нужен, чтобы отрисовать виджет капчи и получить токен для `signIn`, `signUp`
|
|
668
676
|
* и `forgotPassword`.
|
|
669
677
|
*
|
|
678
|
+
* Ключ привязан к домену: на чужом origin Cloudflare отказывает виджету с кодом `110200`.
|
|
679
|
+
* Поэтому отрисовать его может только код, выполняемый на самом итд.com. Остальным
|
|
680
|
+
* подходит `@itd-api/turnstile`, готовый токен из другого источника или вовсе вход
|
|
681
|
+
* без капчи — по сохранённой сессии либо по токенам, взятым в браузере.
|
|
682
|
+
*
|
|
670
683
|
* @example
|
|
671
684
|
* ```ts
|
|
672
685
|
* turnstile.render('#captcha', {
|
|
@@ -676,8 +689,6 @@ const AUTH_PATHS = {
|
|
|
676
689
|
* ```
|
|
677
690
|
*/
|
|
678
691
|
const TURNSTILE_SITE_KEY = "0x4AAAAAACHhxczw6fJGwPBg";
|
|
679
|
-
/** Заголовок с идентификатором устройства. Сервер связывает с ним запись в списке сессий. */
|
|
680
|
-
const DEVICE_ID_HEADER = "X-Device-Id";
|
|
681
692
|
function readAccessToken(payload) {
|
|
682
693
|
if (typeof payload !== "object" || payload === null) return void 0;
|
|
683
694
|
const token = payload.accessToken;
|
|
@@ -1182,10 +1193,33 @@ const systemClock = Object.freeze({
|
|
|
1182
1193
|
return () => clearTimeout(timer);
|
|
1183
1194
|
}
|
|
1184
1195
|
});
|
|
1196
|
+
/**
|
|
1197
|
+
* Заводит срок, общий на все ожидания: от ожидания к ожиданию он не продлевается.
|
|
1198
|
+
*
|
|
1199
|
+
* @param timeout срок в миллисекундах; `0` — ждать без ограничения
|
|
1200
|
+
* @internal
|
|
1201
|
+
*/
|
|
1202
|
+
function createDeadline(timeout, clock = systemClock) {
|
|
1203
|
+
if (timeout <= 0) return {
|
|
1204
|
+
wait: async (promise) => {
|
|
1205
|
+
await promise;
|
|
1206
|
+
return true;
|
|
1207
|
+
},
|
|
1208
|
+
cancel: () => {}
|
|
1209
|
+
};
|
|
1210
|
+
let expire;
|
|
1211
|
+
const expired = new Promise((resolve) => {
|
|
1212
|
+
expire = resolve;
|
|
1213
|
+
});
|
|
1214
|
+
return {
|
|
1215
|
+
wait: (promise) => Promise.race([promise.then(() => true), expired.then(() => false)]),
|
|
1216
|
+
cancel: clock.schedule(() => expire(), timeout)
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1185
1219
|
//#endregion
|
|
1186
1220
|
//#region src/core/version.ts
|
|
1187
1221
|
/** Версия библиотеки. Попадает в `User-Agent`. */
|
|
1188
|
-
const LIBRARY_VERSION = "0.
|
|
1222
|
+
const LIBRARY_VERSION = "0.6.0";
|
|
1189
1223
|
//#endregion
|
|
1190
1224
|
//#region src/core/config.ts
|
|
1191
1225
|
/** Базовый URL API итд.com. Домен записан в punycode: `итд.com`. */
|
|
@@ -1200,8 +1234,6 @@ const BUILT_IN_SERVICES = Object.freeze([Object.freeze({
|
|
|
1200
1234
|
baseUrl: DEFAULT_STATUS_BASE_URL,
|
|
1201
1235
|
auth: false
|
|
1202
1236
|
})]);
|
|
1203
|
-
/** Таймаут запроса по умолчанию — 30 секунд. */
|
|
1204
|
-
const DEFAULT_TIMEOUT = 3e4;
|
|
1205
1237
|
/**
|
|
1206
1238
|
* `User-Agent` по умолчанию.
|
|
1207
1239
|
*
|
|
@@ -1411,6 +1443,7 @@ function resolveConfig(options = {}) {
|
|
|
1411
1443
|
const mode = options.mode ?? RuntimeMode.Auto;
|
|
1412
1444
|
if (!Object.values(RuntimeMode).includes(mode)) throw new ItdConfigError(`mode должен быть одним из ${Object.values(RuntimeMode).join(", ")}, получено: ${mode}`);
|
|
1413
1445
|
const timeout = requirePositive(options.timeout ?? 3e4, "timeout");
|
|
1446
|
+
const shutdownTimeout = requirePositive(options.shutdownTimeout ?? 1e4, "shutdownTimeout");
|
|
1414
1447
|
if (options.clock !== void 0 && (typeof options.clock !== "object" || options.clock === null || typeof options.clock.now !== "function" || typeof options.clock.schedule !== "function")) throw new ItdConfigError("clock должен предоставлять методы now() и schedule()");
|
|
1415
1448
|
requireOptionalBoolean(options.autoRefresh, "autoRefresh");
|
|
1416
1449
|
requireOptionalBoolean(options.reloginOnRefreshFailure, "reloginOnRefreshFailure");
|
|
@@ -1426,6 +1459,7 @@ function resolveConfig(options = {}) {
|
|
|
1426
1459
|
fetch: resolveFetch(options.fetch),
|
|
1427
1460
|
clock: options.clock ?? systemClock,
|
|
1428
1461
|
timeout,
|
|
1462
|
+
shutdownTimeout,
|
|
1429
1463
|
retry: resolveRetry(options.retry),
|
|
1430
1464
|
rateLimit: resolveRateLimit(options.rateLimit),
|
|
1431
1465
|
hooks: resolveHooks(options.hooks),
|
|
@@ -1994,7 +2028,11 @@ var PluginRegistry = class {
|
|
|
1994
2028
|
#entries = /* @__PURE__ */ new Map();
|
|
1995
2029
|
#removing = /* @__PURE__ */ new Set();
|
|
1996
2030
|
#cleanups = /* @__PURE__ */ new Set();
|
|
2031
|
+
#options;
|
|
1997
2032
|
#ordered = [];
|
|
2033
|
+
constructor(options) {
|
|
2034
|
+
this.#options = options;
|
|
2035
|
+
}
|
|
1998
2036
|
/** Сколько плагинов подключено. */
|
|
1999
2037
|
get size() {
|
|
2000
2038
|
return this.#entries.size;
|
|
@@ -2068,9 +2106,11 @@ var PluginRegistry = class {
|
|
|
2068
2106
|
* Отключает плагин и вызывает его функцию очистки.
|
|
2069
2107
|
*
|
|
2070
2108
|
* Новые запросы перестают видеть расширения плагина сразу. Снимок уже начавшейся операции,
|
|
2071
|
-
* включая её будущие retry, остаётся неизменным; очистка дождётся завершения
|
|
2109
|
+
* включая её будущие retry, остаётся неизменным; очистка дождётся завершения операции,
|
|
2110
|
+
* но не дольше отведённого срока.
|
|
2072
2111
|
*
|
|
2073
2112
|
* @returns `false`, если такого плагина не было
|
|
2113
|
+
* @throws {ItdStateError} если операции плагина не завершились за отведённый срок
|
|
2074
2114
|
*/
|
|
2075
2115
|
async remove(name) {
|
|
2076
2116
|
const entry = this.#entries.get(name);
|
|
@@ -2079,21 +2119,24 @@ var PluginRegistry = class {
|
|
|
2079
2119
|
this.#entries.delete(name);
|
|
2080
2120
|
this.#ordered = this.#ordered.filter((current) => current !== entry);
|
|
2081
2121
|
this.#removing.add(name);
|
|
2122
|
+
const deadline = createDeadline(this.#options.shutdownTimeout, this.#options.clock);
|
|
2082
2123
|
const cleanup = this.#trackCleanup((async () => {
|
|
2083
|
-
await this.#
|
|
2084
|
-
|
|
2124
|
+
const expired = await this.#release(entry, deadline);
|
|
2125
|
+
if (expired) throw expired;
|
|
2085
2126
|
})());
|
|
2086
2127
|
try {
|
|
2087
2128
|
await cleanup;
|
|
2088
2129
|
return true;
|
|
2089
2130
|
} finally {
|
|
2131
|
+
deadline.cancel();
|
|
2090
2132
|
this.#removing.delete(name);
|
|
2091
2133
|
}
|
|
2092
2134
|
}
|
|
2093
2135
|
/**
|
|
2094
2136
|
* Отключает все плагины окончательно.
|
|
2095
2137
|
*
|
|
2096
|
-
* Очистка идёт изнутри наружу — в порядке, обратном выполнению расширений.
|
|
2138
|
+
* Очистка идёт изнутри наружу — в порядке, обратном выполнению расширений. Срок ожидания
|
|
2139
|
+
* общий на все плагины.
|
|
2097
2140
|
*/
|
|
2098
2141
|
async dispose() {
|
|
2099
2142
|
const entries = [...this.#ordered].reverse();
|
|
@@ -2101,13 +2144,14 @@ var PluginRegistry = class {
|
|
|
2101
2144
|
this.#entries.clear();
|
|
2102
2145
|
this.#ordered = [];
|
|
2103
2146
|
for (const { plugin } of entries) this.#removing.add(plugin.name);
|
|
2104
|
-
|
|
2147
|
+
const deadline = createDeadline(this.#options.shutdownTimeout, this.#options.clock);
|
|
2148
|
+
const cleanup = this.#trackCleanup((async () => {
|
|
2105
2149
|
const errors = [];
|
|
2106
2150
|
const previous = await Promise.allSettled(previousCleanups);
|
|
2107
2151
|
for (const result of previous) if (result.status === "rejected") errors.push(result.reason);
|
|
2108
2152
|
for (const entry of entries) try {
|
|
2109
|
-
await this.#
|
|
2110
|
-
|
|
2153
|
+
const expired = await this.#release(entry, deadline);
|
|
2154
|
+
if (expired) errors.push(expired);
|
|
2111
2155
|
} catch (error) {
|
|
2112
2156
|
errors.push(error);
|
|
2113
2157
|
} finally {
|
|
@@ -2115,6 +2159,11 @@ var PluginRegistry = class {
|
|
|
2115
2159
|
}
|
|
2116
2160
|
if (errors.length > 0) throw new AggregateError(errors, "Не удалось освободить ресурсы плагинов");
|
|
2117
2161
|
})());
|
|
2162
|
+
try {
|
|
2163
|
+
await cleanup;
|
|
2164
|
+
} finally {
|
|
2165
|
+
deadline.cancel();
|
|
2166
|
+
}
|
|
2118
2167
|
}
|
|
2119
2168
|
/**
|
|
2120
2169
|
* Прогоняет запрос через operation transformers и прикрепляет attempt interceptors.
|
|
@@ -2122,6 +2171,9 @@ var PluginRegistry = class {
|
|
|
2122
2171
|
* Снимки обеих цепочек берутся в начале: `unuse()` влияет на новые операции, но не меняет
|
|
2123
2172
|
* уже выполняющуюся и не удаляет interceptors из её последующих retry.
|
|
2124
2173
|
*
|
|
2174
|
+
* Каждый `next` одноразовый: transformer может завершить операцию сам, но не может породить
|
|
2175
|
+
* вторую — для `posts.create` это была бы вторая публикация.
|
|
2176
|
+
*
|
|
2125
2177
|
* @param execute выполнение логической операции, вызываемое самым внутренним transformer
|
|
2126
2178
|
*/
|
|
2127
2179
|
async run(request, execute) {
|
|
@@ -2132,7 +2184,17 @@ var PluginRegistry = class {
|
|
|
2132
2184
|
interceptor
|
|
2133
2185
|
})));
|
|
2134
2186
|
const scoped = (current) => withAttemptInterceptorScope(current, interceptorScope);
|
|
2135
|
-
const chain = entries.flatMap((entry) => entry.transformers
|
|
2187
|
+
const chain = entries.flatMap((entry) => entry.transformers.map((transformer) => ({
|
|
2188
|
+
plugin: entry.plugin.name,
|
|
2189
|
+
transformer
|
|
2190
|
+
}))).reduceRight((next, { plugin, transformer }) => (current) => {
|
|
2191
|
+
let called = false;
|
|
2192
|
+
return transformer(scoped(current), (prepared) => {
|
|
2193
|
+
if (called) throw new ItdConfigError(`operation transformer плагина «${plugin}» вызвал next() больше одного раза`);
|
|
2194
|
+
called = true;
|
|
2195
|
+
return next(scoped(prepared));
|
|
2196
|
+
});
|
|
2197
|
+
}, (current) => execute(scoped(current)));
|
|
2136
2198
|
try {
|
|
2137
2199
|
return await chain(scoped(request));
|
|
2138
2200
|
} finally {
|
|
@@ -2146,6 +2208,18 @@ var PluginRegistry = class {
|
|
|
2146
2208
|
}
|
|
2147
2209
|
}
|
|
2148
2210
|
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Дожидается операций плагина и освобождает его ресурсы.
|
|
2213
|
+
*
|
|
2214
|
+
* `teardown` выполняется в любом случае, в том числе после истечения срока.
|
|
2215
|
+
*
|
|
2216
|
+
* @returns ошибка истёкшего срока, если ждать пришлось дольше отведённого
|
|
2217
|
+
*/
|
|
2218
|
+
async #release(entry, deadline) {
|
|
2219
|
+
const finished = await deadline.wait(this.#waitForDrain(entry));
|
|
2220
|
+
await entry.teardown?.();
|
|
2221
|
+
return finished ? void 0 : new ItdStateError(`плагин «${entry.plugin.name}» не завершил операции за ${this.#options.shutdownTimeout} мс; ожидание прекращено, ресурсы плагина освобождены`);
|
|
2222
|
+
}
|
|
2149
2223
|
#waitForDrain(entry) {
|
|
2150
2224
|
if (entry.activeRequests === 0) return Promise.resolve();
|
|
2151
2225
|
entry.drain ??= new Promise((resolve) => {
|
|
@@ -2318,6 +2392,7 @@ var RequestQueuePool = class {
|
|
|
2318
2392
|
stop() {
|
|
2319
2393
|
this.#main.stop();
|
|
2320
2394
|
for (const queue of this.#byDestination.values()) queue.stop();
|
|
2395
|
+
this.#byDestination.clear();
|
|
2321
2396
|
}
|
|
2322
2397
|
};
|
|
2323
2398
|
//#endregion
|
|
@@ -2842,17 +2917,25 @@ function abortable(promise, signal) {
|
|
|
2842
2917
|
});
|
|
2843
2918
|
}
|
|
2844
2919
|
/**
|
|
2845
|
-
* Объединяет пользовательский `AbortSignal
|
|
2920
|
+
* Объединяет пользовательский `AbortSignal`, сигнал жизни клиента и таймаут.
|
|
2846
2921
|
*
|
|
2847
2922
|
* Реализовано вручную, а не через `AbortSignal.any`: последний появился только в Node 20,
|
|
2848
|
-
* а библиотека поддерживает Node 18.
|
|
2923
|
+
* а библиотека поддерживает Node 18. Причина отмены переносится от источника как есть.
|
|
2849
2924
|
*/
|
|
2850
|
-
function createAbortBundle(userSignal, timeout, clock) {
|
|
2925
|
+
function createAbortBundle(userSignal, lifetimeSignal, timeout, clock) {
|
|
2851
2926
|
const controller = new AbortController();
|
|
2852
2927
|
let timedOut = false;
|
|
2853
|
-
const
|
|
2854
|
-
|
|
2855
|
-
|
|
2928
|
+
const link = (source) => {
|
|
2929
|
+
if (!source) return void 0;
|
|
2930
|
+
if (source.aborted) {
|
|
2931
|
+
controller.abort(source.reason);
|
|
2932
|
+
return;
|
|
2933
|
+
}
|
|
2934
|
+
const onAbort = () => controller.abort(source.reason);
|
|
2935
|
+
source.addEventListener("abort", onAbort, { once: true });
|
|
2936
|
+
return () => source.removeEventListener("abort", onAbort);
|
|
2937
|
+
};
|
|
2938
|
+
const unlink = [link(userSignal), link(lifetimeSignal)];
|
|
2856
2939
|
const cancelTimer = timeout > 0 ? clock.schedule(() => {
|
|
2857
2940
|
timedOut = true;
|
|
2858
2941
|
controller.abort();
|
|
@@ -2862,7 +2945,7 @@ function createAbortBundle(userSignal, timeout, clock) {
|
|
|
2862
2945
|
timedOut: () => timedOut,
|
|
2863
2946
|
cleanup: () => {
|
|
2864
2947
|
cancelTimer?.();
|
|
2865
|
-
|
|
2948
|
+
for (const detach of unlink) detach?.();
|
|
2866
2949
|
}
|
|
2867
2950
|
};
|
|
2868
2951
|
}
|
|
@@ -2899,7 +2982,7 @@ var Transport = class {
|
|
|
2899
2982
|
const headers = await this.#buildHeaders(request, url);
|
|
2900
2983
|
const attempt = request.attempt ?? 1;
|
|
2901
2984
|
const timeout = request.timeout ?? this.#config.timeout;
|
|
2902
|
-
const abort = createAbortBundle(request.signal, timeout, this.#config.clock);
|
|
2985
|
+
const abort = createAbortBundle(request.signal, this.#deps.lifetimeSignal, timeout, this.#config.clock);
|
|
2903
2986
|
const startedAt = this.#config.clock.now();
|
|
2904
2987
|
let cleanupBody;
|
|
2905
2988
|
try {
|
|
@@ -3116,7 +3199,7 @@ var Transport = class {
|
|
|
3116
3199
|
path: request.path
|
|
3117
3200
|
});
|
|
3118
3201
|
if (aborted) {
|
|
3119
|
-
const reason =
|
|
3202
|
+
const reason = abort.signal.reason;
|
|
3120
3203
|
return new ItdAbortError(`Запрос ${method} ${request.path} отменён`, reason !== void 0 ? { cause: reason } : void 0);
|
|
3121
3204
|
}
|
|
3122
3205
|
if (error instanceof ItdError) return error;
|
|
@@ -3157,8 +3240,12 @@ function createServiceRegistry(config) {
|
|
|
3157
3240
|
function createClientRuntime(options = {}, internals = {}) {
|
|
3158
3241
|
const config = resolveConfig(options);
|
|
3159
3242
|
const jar = new CookieJar();
|
|
3160
|
-
const plugins = new PluginRegistry(
|
|
3243
|
+
const plugins = new PluginRegistry({
|
|
3244
|
+
shutdownTimeout: config.shutdownTimeout,
|
|
3245
|
+
clock: config.clock
|
|
3246
|
+
});
|
|
3161
3247
|
const services = createServiceRegistry(config);
|
|
3248
|
+
const lifetime = new AbortController();
|
|
3162
3249
|
const sharedQueues = config.rateLimit ? internals.queues : void 0;
|
|
3163
3250
|
const queues = sharedQueues ?? (config.rateLimit ? new RequestQueuePool(config.rateLimit, config.clock) : void 0);
|
|
3164
3251
|
const ownsQueues = sharedQueues === void 0;
|
|
@@ -3188,7 +3275,8 @@ function createClientRuntime(options = {}, internals = {}) {
|
|
|
3188
3275
|
}, {
|
|
3189
3276
|
cookies: config.useCookieJar ? jar : void 0,
|
|
3190
3277
|
getDeviceId: () => auth.getDeviceId(),
|
|
3191
|
-
onRateLimit: queues && config.rateLimit?.respectHeaders ? throttleByHeaders : void 0
|
|
3278
|
+
onRateLimit: queues && config.rateLimit?.respectHeaders ? throttleByHeaders : void 0,
|
|
3279
|
+
lifetimeSignal: lifetime.signal
|
|
3192
3280
|
});
|
|
3193
3281
|
const stages = [
|
|
3194
3282
|
{
|
|
@@ -3265,9 +3353,10 @@ function createClientRuntime(options = {}, internals = {}) {
|
|
|
3265
3353
|
close: () => {
|
|
3266
3354
|
if (ownsQueues) queues?.stop();
|
|
3267
3355
|
},
|
|
3268
|
-
dispose: () => {
|
|
3356
|
+
dispose: async () => {
|
|
3357
|
+
lifetime.abort(new ItdAbortError("Клиент освобождён через dispose(), запрос отменён"));
|
|
3269
3358
|
auth.dispose();
|
|
3270
|
-
|
|
3359
|
+
await plugins.dispose();
|
|
3271
3360
|
}
|
|
3272
3361
|
};
|
|
3273
3362
|
}
|
|
@@ -3625,7 +3714,12 @@ var RealtimeDispatcher = class {
|
|
|
3625
3714
|
if (index >= 0) this.#handlers.splice(index, 1);
|
|
3626
3715
|
};
|
|
3627
3716
|
}
|
|
3628
|
-
|
|
3717
|
+
/**
|
|
3718
|
+
* Принимает обновление к обработке.
|
|
3719
|
+
*
|
|
3720
|
+
* @param coalesceKey ожидающая работа с тем же ключом заменяется новой
|
|
3721
|
+
*/
|
|
3722
|
+
dispatch(context, coalesceKey) {
|
|
3629
3723
|
let keys;
|
|
3630
3724
|
let middleware;
|
|
3631
3725
|
try {
|
|
@@ -3635,12 +3729,20 @@ var RealtimeDispatcher = class {
|
|
|
3635
3729
|
this.#hooks.middlewareError(error, context);
|
|
3636
3730
|
return;
|
|
3637
3731
|
}
|
|
3638
|
-
this.#queue.
|
|
3732
|
+
const replaced = coalesceKey === void 0 ? -1 : this.#queue.findIndex((pending) => pending.coalesceKey === coalesceKey);
|
|
3733
|
+
if (replaced < 0 && this.#queue.length >= 256) {
|
|
3734
|
+
this.#hooks.overflow();
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3737
|
+
const work = {
|
|
3639
3738
|
context,
|
|
3640
3739
|
middleware,
|
|
3641
3740
|
handlers: [...this.#handlers],
|
|
3642
|
-
keys
|
|
3643
|
-
|
|
3741
|
+
keys,
|
|
3742
|
+
coalesceKey
|
|
3743
|
+
};
|
|
3744
|
+
if (replaced >= 0) this.#queue[replaced] = work;
|
|
3745
|
+
else this.#queue.push(work);
|
|
3644
3746
|
this.#pump();
|
|
3645
3747
|
}
|
|
3646
3748
|
/** Отбрасывает обновления, обработка которых ещё не началась. */
|
|
@@ -3717,14 +3819,6 @@ const RECONNECT_BACKOFF = Object.freeze([
|
|
|
3717
3819
|
16e3,
|
|
3718
3820
|
3e4
|
|
3719
3821
|
]);
|
|
3720
|
-
/** Доля случайного разброса паузы. */
|
|
3721
|
-
const RECONNECT_JITTER = .3;
|
|
3722
|
-
/**
|
|
3723
|
-
* Сколько раз пытаться переподключиться подряд.
|
|
3724
|
-
*
|
|
3725
|
-
* После исчерпания поток сообщает `giveup` и ждёт ручного `connect()`.
|
|
3726
|
-
*/
|
|
3727
|
-
const MAX_RECONNECT_ATTEMPTS = 15;
|
|
3728
3822
|
/**
|
|
3729
3823
|
* Вычисляет паузу перед попыткой переподключения.
|
|
3730
3824
|
*
|
|
@@ -4025,7 +4119,8 @@ var RealtimeEngine = class {
|
|
|
4025
4119
|
}, {
|
|
4026
4120
|
deliver: (context) => this.#deps.deliver(context.update),
|
|
4027
4121
|
middlewareError: (error, context) => this.#reportDispatchError("middlewareError", error, context),
|
|
4028
|
-
handlerError: (error, context) => this.#reportDispatchError("handlerError", error, context)
|
|
4122
|
+
handlerError: (error, context) => this.#reportDispatchError("handlerError", error, context),
|
|
4123
|
+
overflow: () => this.#handleOverflow()
|
|
4029
4124
|
});
|
|
4030
4125
|
}
|
|
4031
4126
|
/** Текущее состояние соединения. */
|
|
@@ -4106,7 +4201,23 @@ var RealtimeEngine = class {
|
|
|
4106
4201
|
/** Пропускает актуальное обновление через цепочку обработчиков. */
|
|
4107
4202
|
#dispatch(update, raw, origin) {
|
|
4108
4203
|
if (!this.#wanted) return;
|
|
4109
|
-
this.#dispatcher.dispatch(this.#deps.createContext(update, raw, origin));
|
|
4204
|
+
this.#dispatcher.dispatch(this.#deps.createContext(update, raw, origin), this.#deps.coalesceKey?.(update));
|
|
4205
|
+
}
|
|
4206
|
+
/**
|
|
4207
|
+
* Очередь обновлений достигла предела: закрывает соединение и переподключается,
|
|
4208
|
+
* когда обработчики разберут очередь.
|
|
4209
|
+
*/
|
|
4210
|
+
#handleOverflow() {
|
|
4211
|
+
const controller = this.#controller;
|
|
4212
|
+
if (!this.#wanted || !controller) return;
|
|
4213
|
+
const generation = this.#generation;
|
|
4214
|
+
controller.abort();
|
|
4215
|
+
this.#controller = void 0;
|
|
4216
|
+
this.#setStatus(RealtimeStatus.Error);
|
|
4217
|
+
const error = /* @__PURE__ */ new Error("Обработчики не успевают за потоком: очередь обновлений переполнена");
|
|
4218
|
+
this.#dispatcher.drain().then(() => {
|
|
4219
|
+
if (this.#isCurrentGeneration(generation) && !this.#controller) this.#scheduleReconnect(error);
|
|
4220
|
+
});
|
|
4110
4221
|
}
|
|
4111
4222
|
/** Выполняет доменную синхронизацию, не роняя подключение из-за её ошибки. */
|
|
4112
4223
|
async #sync(reason, generation, starting) {
|
|
@@ -4131,6 +4242,7 @@ var RealtimeEngine = class {
|
|
|
4131
4242
|
baseUrl: this.#deps.baseUrl,
|
|
4132
4243
|
authorize: this.#deps.authorize,
|
|
4133
4244
|
fetch: this.#deps.fetch,
|
|
4245
|
+
request: this.#deps.request,
|
|
4134
4246
|
baseHeaders: this.#deps.baseHeaders,
|
|
4135
4247
|
getToken: this.#deps.getToken,
|
|
4136
4248
|
signal: controller.signal,
|
|
@@ -4229,7 +4341,12 @@ var RealtimeEngine = class {
|
|
|
4229
4341
|
this.#starting = void 0;
|
|
4230
4342
|
if (this.#isCurrentGeneration(generation)) this.#run(generation);
|
|
4231
4343
|
}
|
|
4232
|
-
/**
|
|
4344
|
+
/**
|
|
4345
|
+
* Завершает автоматические попытки переподключения.
|
|
4346
|
+
*
|
|
4347
|
+
* Владелец узнаёт об этом так же, как при {@link disconnect}. `onClose` вызывается
|
|
4348
|
+
* до событий: обработчик `giveup` может тут же вызвать `connect()`.
|
|
4349
|
+
*/
|
|
4233
4350
|
#giveUp(error) {
|
|
4234
4351
|
this.#wanted = false;
|
|
4235
4352
|
this.#generation += 1;
|
|
@@ -4237,6 +4354,7 @@ var RealtimeEngine = class {
|
|
|
4237
4354
|
this.#attempt = 0;
|
|
4238
4355
|
this.#detachEnvironment?.();
|
|
4239
4356
|
this.#detachEnvironment = void 0;
|
|
4357
|
+
this.#deps.onClose?.();
|
|
4240
4358
|
this.#emitEngine("error", {
|
|
4241
4359
|
error,
|
|
4242
4360
|
willReconnect: false
|
|
@@ -4309,26 +4427,15 @@ var PollTransport = class {
|
|
|
4309
4427
|
this.#limit = options.limit ?? 20;
|
|
4310
4428
|
}
|
|
4311
4429
|
async connect(context) {
|
|
4430
|
+
const request = context.request;
|
|
4431
|
+
if (!request) throw new ItdConfigError("опрос уведомлений выполняется через конвейер клиента; создайте поток вызовом itd.realtime()");
|
|
4312
4432
|
const seen = /* @__PURE__ */ new Set();
|
|
4313
4433
|
let firstRun = true;
|
|
4314
4434
|
let lastUnreadCount;
|
|
4315
4435
|
while (!context.signal.aborted) {
|
|
4316
|
-
const
|
|
4317
|
-
if (context.authorize && !token) throw new UnauthorizedStreamError();
|
|
4318
|
-
const url = `${joinUrl(context.baseUrl, "/api/notifications/")}?limit=${this.#limit}&offset=0`;
|
|
4319
|
-
const headers = await context.baseHeaders(url);
|
|
4320
|
-
headers.set("Accept", "application/json");
|
|
4321
|
-
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
4322
|
-
const response = await context.fetch(url, {
|
|
4323
|
-
method: "GET",
|
|
4324
|
-
headers,
|
|
4325
|
-
signal: context.signal
|
|
4326
|
-
});
|
|
4327
|
-
if (context.authorize && response.status === 401) throw new UnauthorizedStreamError();
|
|
4328
|
-
if (!response.ok) throw new Error(`Опрос уведомлений вернул статус ${response.status}`);
|
|
4436
|
+
const payload = await this.#readUpdates(request, context.signal);
|
|
4329
4437
|
context.onOpen();
|
|
4330
|
-
const
|
|
4331
|
-
const items = pickArray(typeof body === "object" && body !== null && "data" in body ? body.data : body, "notifications");
|
|
4438
|
+
const items = pickArray(payload, "notifications");
|
|
4332
4439
|
for (const item of [...items].reverse()) {
|
|
4333
4440
|
const id = typeof item.id === "string" ? item.id : void 0;
|
|
4334
4441
|
if (!id || seen.has(id)) continue;
|
|
@@ -4342,7 +4449,7 @@ var PollTransport = class {
|
|
|
4342
4449
|
const excess = [...seen].slice(0, seen.size - this.#limit * 2);
|
|
4343
4450
|
for (const id of excess) seen.delete(id);
|
|
4344
4451
|
}
|
|
4345
|
-
const count = await this.#readCount(
|
|
4452
|
+
const count = await this.#readCount(request, context.signal);
|
|
4346
4453
|
if (count !== void 0 && count !== lastUnreadCount) {
|
|
4347
4454
|
lastUnreadCount = count;
|
|
4348
4455
|
context.onEvent({
|
|
@@ -4354,16 +4461,29 @@ var PollTransport = class {
|
|
|
4354
4461
|
await this.#wait(context.signal);
|
|
4355
4462
|
}
|
|
4356
4463
|
}
|
|
4357
|
-
async #
|
|
4464
|
+
async #readUpdates(request, signal) {
|
|
4358
4465
|
try {
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4466
|
+
return await request({
|
|
4467
|
+
operationId: "realtime.poll.updates",
|
|
4468
|
+
path: "/api/notifications/",
|
|
4469
|
+
query: {
|
|
4470
|
+
limit: this.#limit,
|
|
4471
|
+
offset: 0
|
|
4472
|
+
},
|
|
4473
|
+
signal
|
|
4363
4474
|
});
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4475
|
+
} catch (error) {
|
|
4476
|
+
if (isItdAuthError(error)) throw new UnauthorizedStreamError();
|
|
4477
|
+
throw error;
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
async #readCount(request, signal) {
|
|
4481
|
+
try {
|
|
4482
|
+
return pickNumber(await request({
|
|
4483
|
+
operationId: "realtime.poll.unread",
|
|
4484
|
+
path: "/api/notifications/count",
|
|
4485
|
+
signal
|
|
4486
|
+
}), "count", 0);
|
|
4367
4487
|
} catch {
|
|
4368
4488
|
return;
|
|
4369
4489
|
}
|
|
@@ -4723,6 +4843,7 @@ var ItdRealtime = class {
|
|
|
4723
4843
|
baseUrl: deps.baseUrl,
|
|
4724
4844
|
authorize: deps.authorize ?? true,
|
|
4725
4845
|
fetch: deps.fetch,
|
|
4846
|
+
request: deps.request,
|
|
4726
4847
|
clock: deps.clock,
|
|
4727
4848
|
baseHeaders: deps.baseHeaders,
|
|
4728
4849
|
getToken: deps.getToken,
|
|
@@ -4733,6 +4854,7 @@ var ItdRealtime = class {
|
|
|
4733
4854
|
transport: createTransport(deps, options),
|
|
4734
4855
|
handleFrame: (event) => this.#handleFrame(event),
|
|
4735
4856
|
readUpdate: readRealtimeUpdate,
|
|
4857
|
+
coalesceKey: (update) => update.type === RealtimeUpdateType.UnreadCount ? update.type : void 0,
|
|
4736
4858
|
createContext: (update, raw, origin) => ({
|
|
4737
4859
|
update,
|
|
4738
4860
|
stream: this,
|
|
@@ -6009,10 +6131,6 @@ function createMultipartFileBody(file) {
|
|
|
6009
6131
|
cancel
|
|
6010
6132
|
};
|
|
6011
6133
|
}
|
|
6012
|
-
//#endregion
|
|
6013
|
-
//#region src/resources/files.ts
|
|
6014
|
-
/** Таймаут одной попытки загрузки файла — 5 минут. */
|
|
6015
|
-
const DEFAULT_UPLOAD_TIMEOUT = 3e5;
|
|
6016
6134
|
/** Файлы и медиа. */
|
|
6017
6135
|
var FilesResource = class extends BaseResource {
|
|
6018
6136
|
#fetch;
|
|
@@ -9094,6 +9212,11 @@ var ItdClient = class ItdClient {
|
|
|
9094
9212
|
stream = new ItdRealtime({
|
|
9095
9213
|
baseUrl: this.#runtime.config.baseUrl,
|
|
9096
9214
|
fetch: this.#runtime.config.fetch,
|
|
9215
|
+
request: ({ operationId, path, query, signal }) => this.#runtime.http.operation(operationId, {
|
|
9216
|
+
path,
|
|
9217
|
+
query,
|
|
9218
|
+
signal
|
|
9219
|
+
}),
|
|
9097
9220
|
baseHeaders: (url) => this.#runtime.platformHeaders(url),
|
|
9098
9221
|
getAuthIdentity: () => this.#runtime.auth.getCurrentAuthIdentity(),
|
|
9099
9222
|
getAuthScope: () => this.#runtime.auth.getAuthScope(),
|
|
@@ -9113,13 +9236,16 @@ var ItdClient = class ItdClient {
|
|
|
9113
9236
|
* Освобождает ресурсы клиента: закрывает все потоки уведомлений, отправляет открытые
|
|
9114
9237
|
* накопители {@link telemetry}, затем останавливает очередь запросов.
|
|
9115
9238
|
*
|
|
9116
|
-
* Метод дожидается активных обработчиков
|
|
9117
|
-
* снова; ранее созданный поток можно запустить
|
|
9239
|
+
* Метод дожидается активных обработчиков потока, но не дольше `shutdownTimeout`. После
|
|
9240
|
+
* вызова клиентом можно пользоваться снова; ранее созданный поток можно запустить
|
|
9241
|
+
* повторным `connect()`.
|
|
9118
9242
|
*
|
|
9119
9243
|
* Общая очередь, полученная от {@link ItdAccounts}, не останавливается: её гасит сам
|
|
9120
9244
|
* контейнер, когда закрывает все аккаунты разом.
|
|
9121
9245
|
*
|
|
9122
9246
|
* Терминальное освобождение — это {@link dispose}.
|
|
9247
|
+
*
|
|
9248
|
+
* @throws {ItdStateError} если обработчики потока не завершились за отведённый срок
|
|
9123
9249
|
*/
|
|
9124
9250
|
async close() {
|
|
9125
9251
|
return this.#close(false);
|
|
@@ -9127,22 +9253,30 @@ var ItdClient = class ItdClient {
|
|
|
9127
9253
|
async #close(disposeCleanup) {
|
|
9128
9254
|
const streams = this.#disconnectStreams();
|
|
9129
9255
|
if (disposeCleanup) prepareTelemetryForDispose(this.#telemetry);
|
|
9256
|
+
const { shutdownTimeout, clock } = this.#runtime.config;
|
|
9257
|
+
const deadline = createDeadline(shutdownTimeout, clock);
|
|
9258
|
+
let stuck = [];
|
|
9130
9259
|
try {
|
|
9131
|
-
await Promise.all(streams.map((stream) => stream.drain()));
|
|
9260
|
+
stuck = (await Promise.all(streams.map(async (stream) => await deadline.wait(stream.drain()) ? void 0 : stream))).filter((stream) => stream !== void 0);
|
|
9132
9261
|
if (disposeCleanup) await closeTelemetryForDispose(this.#telemetry);
|
|
9133
9262
|
else await this.#telemetry?.close();
|
|
9134
9263
|
} finally {
|
|
9264
|
+
deadline.cancel();
|
|
9135
9265
|
this.#runtime.close();
|
|
9136
9266
|
}
|
|
9267
|
+
if (stuck.length > 0) throw new ItdStateError(`обработчики потоков (${stuck.map((stream) => stream.transport).join(", ")}) не завершились за ${shutdownTimeout} мс; ожидание прекращено`);
|
|
9137
9268
|
}
|
|
9138
9269
|
/**
|
|
9139
|
-
* Окончательно освобождает клиент: выполняет {@link close}
|
|
9270
|
+
* Окончательно освобождает клиент: выполняет {@link close}, отменяет незавершённые
|
|
9271
|
+
* запросы и отключает все плагины.
|
|
9140
9272
|
*
|
|
9141
9273
|
* Терминальное состояние устанавливается сразу при первом вызове. После этого новые
|
|
9142
9274
|
* запросы, подключение плагинов, регистрация сервисов и создание или повторный запуск
|
|
9143
9275
|
* realtime-потоков завершаются с {@link ItdStateError}. Повторные вызовы возвращают
|
|
9144
9276
|
* тот же результат очистки.
|
|
9145
9277
|
*
|
|
9278
|
+
* Ожидание обработчиков потока и операций плагинов ограничено `shutdownTimeout`.
|
|
9279
|
+
*
|
|
9146
9280
|
* @example
|
|
9147
9281
|
* ```ts
|
|
9148
9282
|
* await using itd = new ItdClient({ auth: token });
|
|
@@ -10121,10 +10255,6 @@ var RealtimeComposer = class RealtimeComposer {
|
|
|
10121
10255
|
return (context, next) => runRealtimeMiddleware(snapshot, context, next);
|
|
10122
10256
|
}
|
|
10123
10257
|
};
|
|
10124
|
-
//#endregion
|
|
10125
|
-
//#region src/realtime/websocket.ts
|
|
10126
|
-
/** Стандартный путь WebSocket-подключения. */
|
|
10127
|
-
const WEBSOCKET_PATH = "/api/ws";
|
|
10128
10258
|
const CONNECTING = 0;
|
|
10129
10259
|
const OPEN = 1;
|
|
10130
10260
|
const NORMAL_CLOSURE = 1e3;
|
|
@@ -10262,6 +10392,7 @@ var WebSocketTransport = class {
|
|
|
10262
10392
|
let discardMessages = false;
|
|
10263
10393
|
let socketError;
|
|
10264
10394
|
let messageQueue = Promise.resolve();
|
|
10395
|
+
let pendingFrames = 0;
|
|
10265
10396
|
let cancelHandshake;
|
|
10266
10397
|
let cancelIdle;
|
|
10267
10398
|
let cancelKeepAlive;
|
|
@@ -10340,7 +10471,13 @@ var WebSocketTransport = class {
|
|
|
10340
10471
|
pending = Promise.resolve({ error });
|
|
10341
10472
|
}
|
|
10342
10473
|
if (pending === void 0) return;
|
|
10474
|
+
if (pendingFrames >= 256) {
|
|
10475
|
+
closeWithError(/* @__PURE__ */ new Error(`WebSocket ${redactUrl(url)}: очередь кадров переполнена`), 4e3, "queue overflow");
|
|
10476
|
+
return;
|
|
10477
|
+
}
|
|
10478
|
+
pendingFrames += 1;
|
|
10343
10479
|
messageQueue = messageQueue.then(async () => {
|
|
10480
|
+
pendingFrames -= 1;
|
|
10344
10481
|
if (settled || discardMessages) return;
|
|
10345
10482
|
const decoded = await pending;
|
|
10346
10483
|
if ("error" in decoded) {
|
|
@@ -10633,6 +10770,6 @@ function renderSpans(content, spans = [], options = {}) {
|
|
|
10633
10770
|
return result;
|
|
10634
10771
|
}
|
|
10635
10772
|
//#endregion
|
|
10636
|
-
export { ALLOWED_MIME_TYPES, AUDIO_MIME_TYPES,
|
|
10773
|
+
export { ALLOWED_MIME_TYPES, AUDIO_MIME_TYPES, AccessType, AttachmentType, CommentSort, DEFAULT_BASE_URL, FeedTab, FileTransferMode, IMAGE_MIME_TYPES, IncidentKind, InteractionType, ItdAbortError, ItdAccounts, ItdApiError, ItdApiErrorKind, ItdAuthError, ItdClient, ItdConfigError, ItdConflictError, ItdError, ItdErrorCode, ItdErrorKind, ItdFileError, ItdFileErrorReason, ItdForbiddenError, ItdNetworkError, ItdNotFoundError, ItdPhoneVerificationError, ItdRateLimitError, ItdRealtime, ItdServerError, ItdStateError, ItdTimeoutError, ItdValidationError, LIBRARY_VERSION, LikesVisibility, MemoryKeyValueStore, MemoryMultiTokenStorage, MemoryTokenStorage, NotificationType, OPERATIONS, PaginationMode, Paginator, RealtimeComposer, RealtimeRouter, RealtimeStatus, RealtimeTransportKind, RealtimeUpdateOrigin, RealtimeUpdateType, ReportReason, ReportTargetType, RetrySafety, RuntimeMode, STATUS_SERVICE, ServiceState, SignInStatus, SpanRenderFormat, SpanType, TURNSTILE_SITE_KEY, UnauthorizedStreamError, VIDEO_MIME_TYPES, ViewReason, ViewSource, WallAccess, WebSocketTransport, autoSpans, canonicalNotificationType, comment, createAccounts, createClient, createKeyValueStore, createMultiTokenStorage, createRecordKeyValueStore, createTokenStorage, formatNotificationText, fromStream, fromUrl, isBuilder, isBuiltInOperationId, isEnumerableKeyValueStore, isItdApiError, isItdAuthError, isItdConflictError, isItdError, isItdFileError, isItdForbiddenError, isItdNotFoundError, isItdPhoneVerificationError, isItdRateLimitError, isItdServerError, isItdStateError, isItdValidationError, isKnownNotificationType, isMyProfile, mapPage, markup, normalizeNotification, operationMethod, operationRetrySafety, parseHtml, parseMarkdown, poll, post, renderSpans, report, resolveNotificationUrl, runRealtimeMiddleware, scopedTokenStorage, statusDays, systemClock, toDate, utcStampToIso, withCodec, withNamespace };
|
|
10637
10774
|
|
|
10638
10775
|
//# sourceMappingURL=index.js.map
|