itd-api 0.0.10 → 0.0.11

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.
@@ -672,18 +672,24 @@ function decodeBase64Url(segment) {
672
672
  const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
673
673
  return new TextDecoder().decode(bytes);
674
674
  }
675
- function readTokenSubject(token) {
675
+ function readTokenIdentity(token) {
676
676
  try {
677
677
  const payload = token.split(".")[1];
678
- if (!payload) return void 0;
678
+ if (!payload) return {};
679
679
  const parsed = JSON.parse(decodeBase64Url(payload));
680
- if (typeof parsed !== "object" || parsed === null) return void 0;
681
- const subject = parsed.sub;
682
- return typeof subject === "string" && subject.length > 0 ? subject : void 0;
680
+ if (typeof parsed !== "object" || parsed === null) return {};
681
+ const { sub, sid } = parsed;
682
+ return {
683
+ ...typeof sub === "string" && sub.length > 0 ? { subject: sub } : {},
684
+ ...typeof sid === "string" && sid.length > 0 ? { sessionId: sid } : {}
685
+ };
683
686
  } catch {
684
- return void 0;
687
+ return {};
685
688
  }
686
689
  }
690
+ function readTokenSubject(token) {
691
+ return readTokenIdentity(token).subject;
692
+ }
687
693
 
688
694
  // src/core/runtime.ts
689
695
  var RuntimeMode = Object.freeze({
@@ -876,6 +882,7 @@ var AuthManager = class {
876
882
  #send;
877
883
  #jar;
878
884
  #emitter;
885
+ #hooks;
879
886
  /** `undefined` — сессия ещё не читалась из хранилища. */
880
887
  #session;
881
888
  /**
@@ -887,8 +894,12 @@ var AuthManager = class {
887
894
  #refreshing = null;
888
895
  /** Общий промис входа по логину и паролю. */
889
896
  #signingIn = null;
890
- /** Непрозрачная версия владельца клиента для разделения состояния плагинов. */
897
+ /** Fallback-область для изоляции состояния плагинов, когда JWT-идентичность недоступна. */
891
898
  #authScope = nextAuthScope();
899
+ /** Последний токен внешнего источника; `undefined` означает, что источник ещё не читался. */
900
+ #externalToken;
901
+ /** Идентификаторы последнего токена внешнего источника для синхронных потребителей. */
902
+ #externalIdentity;
892
903
  /**
893
904
  * Идентификатор устройства.
894
905
  *
@@ -898,10 +909,11 @@ var AuthManager = class {
898
909
  #deviceId;
899
910
  /** Общий промис первичной выдачи `deviceId` — чтобы параллельные запросы получили один. */
900
911
  #deviceIdLoading = null;
901
- constructor(config, send, jar) {
912
+ constructor(config, send, jar, hooks = {}) {
902
913
  this.#config = config;
903
914
  this.#send = send;
904
915
  this.#jar = jar;
916
+ this.#hooks = hooks;
905
917
  this.#emitter = new Emitter(
906
918
  (error) => reportListenerError(config.logger, "\u0430\u0432\u0442\u043E\u0440\u0438\u0437\u0430\u0446\u0438\u0438", error)
907
919
  );
@@ -914,13 +926,63 @@ var AuthManager = class {
914
926
  get once() {
915
927
  return this.#emitter.once.bind(this.#emitter);
916
928
  }
917
- /** Непрозрачная область авторизации; токен и идентификатор пользователя не раскрываются. */
929
+ /**
930
+ * Непрозрачная fallback-область авторизации.
931
+ *
932
+ * Изолирует состояние плагинов, когда идентичность аккаунта из JWT недоступна (непрозрачный
933
+ * токен): значение уникально для владельца и сменяется при замене или очистке сессии.
934
+ */
918
935
  getAuthScope() {
919
936
  return this.#authScope;
920
937
  }
938
+ /** Загружает сессию и возвращает идентификаторы аккаунта и сессии для плагинов. */
939
+ async getAuthIdentity() {
940
+ const session = await this.#loadSession();
941
+ if (session?.accessToken) return this.#identityForToken(session.accessToken);
942
+ const auth = this.#config.auth;
943
+ if (typeof auth === "object" && auth !== null && "getToken" in auth) {
944
+ const token = await this.#readExternalToken(() => auth.getToken());
945
+ return this.#identityForToken(token ?? void 0);
946
+ }
947
+ return {};
948
+ }
949
+ /** Идентификаторы аккаунта и сессии без чтения хранилища. @internal */
950
+ getCurrentAuthIdentity() {
951
+ const session = this.#session === void 0 ? this.#sessionFromConfig(this.#config.auth) : this.#session;
952
+ return session?.accessToken ? this.#identityForToken(session.accessToken) : this.#externalIdentity ?? {};
953
+ }
921
954
  #rotateAuthScope() {
922
955
  this.#authScope = nextAuthScope();
923
956
  }
957
+ #identityForToken(accessToken) {
958
+ const token = accessToken ? readTokenIdentity(accessToken) : {};
959
+ return {
960
+ ...token.subject ? { userId: token.subject } : {},
961
+ ...token.sessionId ? { sessionId: token.sessionId } : {}
962
+ };
963
+ }
964
+ async #readExternalToken(getToken) {
965
+ const token = await getToken() ?? null;
966
+ const current = this.#identityForToken(token ?? void 0);
967
+ if (this.#externalToken !== void 0 && this.#externalToken !== token) {
968
+ this.#rotateAuthScope();
969
+ const previous = this.#externalIdentity ?? {};
970
+ const changed = previous.userId !== void 0 && current.userId !== void 0 ? previous.userId !== current.userId : true;
971
+ if (changed) this.#hooks.onAccountChange?.();
972
+ }
973
+ this.#externalToken = token;
974
+ this.#externalIdentity = current;
975
+ return token;
976
+ }
977
+ /** Меняет fallback и завершает realtime, только если фактически сменился аккаунт. */
978
+ #transitionAuth(accessToken, rotateFallback = true) {
979
+ const knownPrevious = this.#session !== void 0;
980
+ const previous = this.#identityForToken(this.#session?.accessToken);
981
+ const current = this.#identityForToken(accessToken);
982
+ const changed = previous.userId !== void 0 && current.userId !== void 0 ? previous.userId !== current.userId : previous.userId !== current.userId || rotateFallback;
983
+ if (rotateFallback || changed) this.#rotateAuthScope();
984
+ if (knownPrevious && changed) this.#hooks.onAccountChange?.();
985
+ }
924
986
  /**
925
987
  * Есть ли признак живой refresh-сессии.
926
988
  *
@@ -981,7 +1043,7 @@ var AuthManager = class {
981
1043
  const auth = this.#config.auth;
982
1044
  if (!auth) return null;
983
1045
  if (typeof auth === "object" && "getToken" in auth) {
984
- return await auth.getToken() ?? null;
1046
+ return this.#readExternalToken(() => auth.getToken());
985
1047
  }
986
1048
  if (typeof auth === "object" && "email" in auth) {
987
1049
  return this.#signInWithCredentials(auth);
@@ -1036,7 +1098,8 @@ var AuthManager = class {
1036
1098
  }
1037
1099
  /** Сохраняет токен, полученный извне, — например после подтверждения OTP. */
1038
1100
  async setAccessToken(accessToken) {
1039
- this.#rotateAuthScope();
1101
+ await this.#loadSession();
1102
+ this.#transitionAuth(accessToken);
1040
1103
  await this.#saveSession({ ...this.#session ?? {}, accessToken, obtainedAt: Date.now() });
1041
1104
  this.#emitter.emit("tokens", { accessToken });
1042
1105
  }
@@ -1057,7 +1120,8 @@ var AuthManager = class {
1057
1120
  }
1058
1121
  /** Заменяет сессию и связанные с ней cookie целиком. */
1059
1122
  async setSession(session) {
1060
- this.#rotateAuthScope();
1123
+ await this.#loadSession();
1124
+ this.#transitionAuth(session.accessToken);
1061
1125
  this.#jar.clear();
1062
1126
  this.#jar.deserialize(session.cookies);
1063
1127
  if (session.deviceId) this.#deviceId = session.deviceId;
@@ -1072,8 +1136,9 @@ var AuthManager = class {
1072
1136
  * новую запись в списке сессий.
1073
1137
  */
1074
1138
  async clear() {
1139
+ await this.#loadSession();
1140
+ this.#transitionAuth(void 0);
1075
1141
  this.#session = null;
1076
- this.#rotateAuthScope();
1077
1142
  this.#jar.clear();
1078
1143
  await this.#config.storage.clear();
1079
1144
  if (this.#deviceId) await this.#saveSession({ deviceId: this.#deviceId });
@@ -1170,6 +1235,7 @@ var AuthManager = class {
1170
1235
  REFRESH_COOKIE,
1171
1236
  this.#config.baseUrl + REFRESH_COOKIE_PATH
1172
1237
  );
1238
+ this.#transitionAuth(accessToken, false);
1173
1239
  await this.#saveSession({
1174
1240
  ...this.#session ?? {},
1175
1241
  accessToken,
@@ -1180,8 +1246,8 @@ var AuthManager = class {
1180
1246
  return accessToken;
1181
1247
  } catch (error) {
1182
1248
  if (error instanceof ItdApiError) {
1249
+ this.#transitionAuth(void 0);
1183
1250
  this.#session = null;
1184
- this.#rotateAuthScope();
1185
1251
  this.#jar.clear();
1186
1252
  await this.#config.storage.clear();
1187
1253
  const relogged = await this.#reloginOrNull();
@@ -1249,7 +1315,7 @@ var AuthManager = class {
1249
1315
  "\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."
1250
1316
  );
1251
1317
  }
1252
- this.#rotateAuthScope();
1318
+ this.#transitionAuth(accessToken);
1253
1319
  await this.#saveSession({ accessToken, obtainedAt: Date.now() });
1254
1320
  this.#emitter.emit("tokens", { accessToken });
1255
1321
  this.#emitter.emit("signIn", { accessToken });
@@ -1316,7 +1382,7 @@ function normalizeBaseUrl(baseUrl) {
1316
1382
  }
1317
1383
 
1318
1384
  // src/core/version.ts
1319
- var LIBRARY_VERSION = "0.0.10";
1385
+ var LIBRARY_VERSION = "0.0.11";
1320
1386
 
1321
1387
  // src/core/config.ts
1322
1388
  var DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
@@ -3290,6 +3356,18 @@ var ItdRealtime = class {
3290
3356
  get transport() {
3291
3357
  return this.#transport.name;
3292
3358
  }
3359
+ /** Базовый URL клиента, создавшего поток. @internal */
3360
+ get baseUrl() {
3361
+ return this.#deps.baseUrl;
3362
+ }
3363
+ /** Идентификаторы аккаунта и сессии клиента, создавшего поток. @internal */
3364
+ getAuthIdentity() {
3365
+ return this.#deps.getAuthIdentity?.();
3366
+ }
3367
+ /** Непрозрачная область авторизации создавшего поток клиента. @internal */
3368
+ getAuthScope() {
3369
+ return this.#deps.getAuthScope?.();
3370
+ }
3293
3371
  /** Подписывается на событие потока. @returns функция отписки */
3294
3372
  on(event, listener) {
3295
3373
  return this.#emitter.on(event, listener);
@@ -6166,7 +6244,9 @@ var ItdClient = class _ItdClient {
6166
6244
  } : void 0;
6167
6245
  const authPipeline = composePipeline([pluginsLayer, retriesLayer], transport.send);
6168
6246
  const authHandler = (request) => authRetry && request.retry === void 0 ? authPipeline({ ...request, retry: authRetry }) : authPipeline(request);
6169
- authManager = new AuthManager(config, authHandler, this.#jar);
6247
+ authManager = new AuthManager(config, authHandler, this.#jar, {
6248
+ onAccountChange: () => this.#disconnectStreams()
6249
+ });
6170
6250
  this.#authManager = authManager;
6171
6251
  const middlewares = [];
6172
6252
  if (queues) {
@@ -6245,7 +6325,8 @@ var ItdClient = class _ItdClient {
6245
6325
  this.#plugins.add(plugin, {
6246
6326
  baseUrl: this.#config.baseUrl,
6247
6327
  logger: this.#config.logger,
6248
- getAuthScope: () => this.#authManager.getAuthScope()
6328
+ getAuthScope: () => this.#authManager.getAuthScope(),
6329
+ getAuthIdentity: () => this.#authManager.getAuthIdentity()
6249
6330
  });
6250
6331
  return this;
6251
6332
  }
@@ -6305,7 +6386,8 @@ var ItdClient = class _ItdClient {
6305
6386
  * Создаёт поток уведомлений в реальном времени.
6306
6387
  *
6307
6388
  * Каждый вызов даёт новый независимый поток; обычно он нужен один на приложение.
6308
- * Соединение поднимается методом `connect()` и держится само.
6389
+ * Соединение поднимается методом `connect()` и держится само. Замена авторизации на токен
6390
+ * другого пользователя завершает все потоки клиента; смена только сессии их не затрагивает.
6309
6391
  *
6310
6392
  * @example
6311
6393
  * ```ts
@@ -6326,6 +6408,8 @@ var ItdClient = class _ItdClient {
6326
6408
  baseUrl: this.#config.baseUrl,
6327
6409
  fetch: this.#config.fetch,
6328
6410
  baseHeaders: (url) => this.#transport.platformHeaders(url),
6411
+ getAuthIdentity: () => this.#authManager.getCurrentAuthIdentity(),
6412
+ getAuthScope: () => this.#authManager.getAuthScope(),
6329
6413
  getToken: () => this.#authManager.getAccessToken(),
6330
6414
  refresh: () => this.#authManager.onUnauthorized(),
6331
6415
  fetchUnreadCount: () => this.notifications.count(),
@@ -6355,10 +6439,14 @@ var ItdClient = class _ItdClient {
6355
6439
  * ```
6356
6440
  */
6357
6441
  async close() {
6358
- for (const stream of this.#streams) stream.disconnect();
6359
- this.#streams.clear();
6442
+ this.#disconnectStreams();
6360
6443
  if (this.#ownsQueues) this.#queues?.stop();
6361
6444
  }
6445
+ /** Завершает потоки до того, как запросы начнут использовать другой аккаунт. */
6446
+ #disconnectStreams() {
6447
+ for (const stream of [...this.#streams]) stream.disconnect();
6448
+ this.#streams.clear();
6449
+ }
6362
6450
  /** Позволяет использовать клиент с `await using`. */
6363
6451
  [Symbol.asyncDispose]() {
6364
6452
  return this.close();
@@ -7185,5 +7273,5 @@ function statusDays(service) {
7185
7273
  }
7186
7274
 
7187
7275
  export { ALLOWED_MIME_TYPES, AUDIO_MIME_TYPES, AUTH_FLAG_COOKIE, AUTH_PATHS, AccessType, AttachmentType, BUILT_IN_SERVICES, CommentSort, DEFAULT_BASE_URL, DEFAULT_STATUS_BASE_URL, DEFAULT_TIMEOUT, DEFAULT_UPLOAD_TIMEOUT, DEFAULT_USER_AGENT, DEVICE_ID_HEADER, DetectedRuntime, FeedTab, IMAGE_MIME_TYPES, IncidentKind, InteractionType, ItdAbortError, ItdAccounts, ItdApiError, ItdApiErrorKind, ItdAuthError, ItdClient, ItdConfigError, ItdConflictError, ItdError, ItdErrorCode, ItdErrorKind, ItdForbiddenError, ItdNetworkError, ItdNotFoundError, ItdPhoneVerificationError, ItdRateLimitError, ItdRealtime, ItdServerError, ItdTimeoutError, ItdValidationError, LIBRARY_VERSION, LikesVisibility, LocalStorageTokenStorage, MAX_RECONNECT_ATTEMPTS, MemoryMultiTokenStorage, MemoryTokenStorage, NOTIFICATION_TYPE_ALIASES, NotificationType, OAuthProvider, PaginationMode, Paginator, RECONNECT_BACKOFF, RECONNECT_JITTER, REFRESH_COOKIE, REFRESH_COOKIE_PATH, REQUEST_OPTION_KEYS, RealtimeStatus, RealtimeTransportKind, ReportReason, ReportTargetType, RuntimeMode, STATUS_SERVICE, STREAM_PATH, ServiceRegistry, ServiceState, SignInStatus, SpanType, TURNSTILE_SITE_KEY, UnauthorizedStreamError, VIDEO_MIME_TYPES, ViewReason, ViewSource, WallAccess, autoSpans, canonicalNotificationType, comment, copySession, createAccounts, createClient, createMultiTokenStorage, createRecordMultiStorage, createTokenStorage, formatNotificationText, isBuilder, isItdApiError, isItdAuthError, isItdConflictError, isItdError, isItdForbiddenError, isItdNotFoundError, isItdPhoneVerificationError, isItdRateLimitError, isItdServerError, isItdValidationError, isKnownNotificationType, isMyProfile, mapPage, markup, normalizeNotification, poll, post, readNotificationEvent, readUnreadCountEvent, renderSpans, report, resolveNotificationUrl, scopedTokenStorage, statusDays, toDate, utcStampToIso };
7188
- //# sourceMappingURL=chunk-SMV7TF5P.js.map
7189
- //# sourceMappingURL=chunk-SMV7TF5P.js.map
7276
+ //# sourceMappingURL=chunk-TB7HW3VX.js.map
7277
+ //# sourceMappingURL=chunk-TB7HW3VX.js.map