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.
@@ -674,18 +674,24 @@ function decodeBase64Url(segment) {
674
674
  const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
675
675
  return new TextDecoder().decode(bytes);
676
676
  }
677
- function readTokenSubject(token) {
677
+ function readTokenIdentity(token) {
678
678
  try {
679
679
  const payload = token.split(".")[1];
680
- if (!payload) return void 0;
680
+ if (!payload) return {};
681
681
  const parsed = JSON.parse(decodeBase64Url(payload));
682
- if (typeof parsed !== "object" || parsed === null) return void 0;
683
- const subject = parsed.sub;
684
- return typeof subject === "string" && subject.length > 0 ? subject : void 0;
682
+ if (typeof parsed !== "object" || parsed === null) return {};
683
+ const { sub, sid } = parsed;
684
+ return {
685
+ ...typeof sub === "string" && sub.length > 0 ? { subject: sub } : {},
686
+ ...typeof sid === "string" && sid.length > 0 ? { sessionId: sid } : {}
687
+ };
685
688
  } catch {
686
- return void 0;
689
+ return {};
687
690
  }
688
691
  }
692
+ function readTokenSubject(token) {
693
+ return readTokenIdentity(token).subject;
694
+ }
689
695
 
690
696
  // src/core/runtime.ts
691
697
  var RuntimeMode = Object.freeze({
@@ -878,6 +884,7 @@ var AuthManager = class {
878
884
  #send;
879
885
  #jar;
880
886
  #emitter;
887
+ #hooks;
881
888
  /** `undefined` — сессия ещё не читалась из хранилища. */
882
889
  #session;
883
890
  /**
@@ -889,8 +896,12 @@ var AuthManager = class {
889
896
  #refreshing = null;
890
897
  /** Общий промис входа по логину и паролю. */
891
898
  #signingIn = null;
892
- /** Непрозрачная версия владельца клиента для разделения состояния плагинов. */
899
+ /** Fallback-область для изоляции состояния плагинов, когда JWT-идентичность недоступна. */
893
900
  #authScope = nextAuthScope();
901
+ /** Последний токен внешнего источника; `undefined` означает, что источник ещё не читался. */
902
+ #externalToken;
903
+ /** Идентификаторы последнего токена внешнего источника для синхронных потребителей. */
904
+ #externalIdentity;
894
905
  /**
895
906
  * Идентификатор устройства.
896
907
  *
@@ -900,10 +911,11 @@ var AuthManager = class {
900
911
  #deviceId;
901
912
  /** Общий промис первичной выдачи `deviceId` — чтобы параллельные запросы получили один. */
902
913
  #deviceIdLoading = null;
903
- constructor(config, send, jar) {
914
+ constructor(config, send, jar, hooks = {}) {
904
915
  this.#config = config;
905
916
  this.#send = send;
906
917
  this.#jar = jar;
918
+ this.#hooks = hooks;
907
919
  this.#emitter = new Emitter(
908
920
  (error) => reportListenerError(config.logger, "\u0430\u0432\u0442\u043E\u0440\u0438\u0437\u0430\u0446\u0438\u0438", error)
909
921
  );
@@ -916,13 +928,63 @@ var AuthManager = class {
916
928
  get once() {
917
929
  return this.#emitter.once.bind(this.#emitter);
918
930
  }
919
- /** Непрозрачная область авторизации; токен и идентификатор пользователя не раскрываются. */
931
+ /**
932
+ * Непрозрачная fallback-область авторизации.
933
+ *
934
+ * Изолирует состояние плагинов, когда идентичность аккаунта из JWT недоступна (непрозрачный
935
+ * токен): значение уникально для владельца и сменяется при замене или очистке сессии.
936
+ */
920
937
  getAuthScope() {
921
938
  return this.#authScope;
922
939
  }
940
+ /** Загружает сессию и возвращает идентификаторы аккаунта и сессии для плагинов. */
941
+ async getAuthIdentity() {
942
+ const session = await this.#loadSession();
943
+ if (session?.accessToken) return this.#identityForToken(session.accessToken);
944
+ const auth = this.#config.auth;
945
+ if (typeof auth === "object" && auth !== null && "getToken" in auth) {
946
+ const token = await this.#readExternalToken(() => auth.getToken());
947
+ return this.#identityForToken(token ?? void 0);
948
+ }
949
+ return {};
950
+ }
951
+ /** Идентификаторы аккаунта и сессии без чтения хранилища. @internal */
952
+ getCurrentAuthIdentity() {
953
+ const session = this.#session === void 0 ? this.#sessionFromConfig(this.#config.auth) : this.#session;
954
+ return session?.accessToken ? this.#identityForToken(session.accessToken) : this.#externalIdentity ?? {};
955
+ }
923
956
  #rotateAuthScope() {
924
957
  this.#authScope = nextAuthScope();
925
958
  }
959
+ #identityForToken(accessToken) {
960
+ const token = accessToken ? readTokenIdentity(accessToken) : {};
961
+ return {
962
+ ...token.subject ? { userId: token.subject } : {},
963
+ ...token.sessionId ? { sessionId: token.sessionId } : {}
964
+ };
965
+ }
966
+ async #readExternalToken(getToken) {
967
+ const token = await getToken() ?? null;
968
+ const current = this.#identityForToken(token ?? void 0);
969
+ if (this.#externalToken !== void 0 && this.#externalToken !== token) {
970
+ this.#rotateAuthScope();
971
+ const previous = this.#externalIdentity ?? {};
972
+ const changed = previous.userId !== void 0 && current.userId !== void 0 ? previous.userId !== current.userId : true;
973
+ if (changed) this.#hooks.onAccountChange?.();
974
+ }
975
+ this.#externalToken = token;
976
+ this.#externalIdentity = current;
977
+ return token;
978
+ }
979
+ /** Меняет fallback и завершает realtime, только если фактически сменился аккаунт. */
980
+ #transitionAuth(accessToken, rotateFallback = true) {
981
+ const knownPrevious = this.#session !== void 0;
982
+ const previous = this.#identityForToken(this.#session?.accessToken);
983
+ const current = this.#identityForToken(accessToken);
984
+ const changed = previous.userId !== void 0 && current.userId !== void 0 ? previous.userId !== current.userId : previous.userId !== current.userId || rotateFallback;
985
+ if (rotateFallback || changed) this.#rotateAuthScope();
986
+ if (knownPrevious && changed) this.#hooks.onAccountChange?.();
987
+ }
926
988
  /**
927
989
  * Есть ли признак живой refresh-сессии.
928
990
  *
@@ -983,7 +1045,7 @@ var AuthManager = class {
983
1045
  const auth = this.#config.auth;
984
1046
  if (!auth) return null;
985
1047
  if (typeof auth === "object" && "getToken" in auth) {
986
- return await auth.getToken() ?? null;
1048
+ return this.#readExternalToken(() => auth.getToken());
987
1049
  }
988
1050
  if (typeof auth === "object" && "email" in auth) {
989
1051
  return this.#signInWithCredentials(auth);
@@ -1038,7 +1100,8 @@ var AuthManager = class {
1038
1100
  }
1039
1101
  /** Сохраняет токен, полученный извне, — например после подтверждения OTP. */
1040
1102
  async setAccessToken(accessToken) {
1041
- this.#rotateAuthScope();
1103
+ await this.#loadSession();
1104
+ this.#transitionAuth(accessToken);
1042
1105
  await this.#saveSession({ ...this.#session ?? {}, accessToken, obtainedAt: Date.now() });
1043
1106
  this.#emitter.emit("tokens", { accessToken });
1044
1107
  }
@@ -1059,7 +1122,8 @@ var AuthManager = class {
1059
1122
  }
1060
1123
  /** Заменяет сессию и связанные с ней cookie целиком. */
1061
1124
  async setSession(session) {
1062
- this.#rotateAuthScope();
1125
+ await this.#loadSession();
1126
+ this.#transitionAuth(session.accessToken);
1063
1127
  this.#jar.clear();
1064
1128
  this.#jar.deserialize(session.cookies);
1065
1129
  if (session.deviceId) this.#deviceId = session.deviceId;
@@ -1074,8 +1138,9 @@ var AuthManager = class {
1074
1138
  * новую запись в списке сессий.
1075
1139
  */
1076
1140
  async clear() {
1141
+ await this.#loadSession();
1142
+ this.#transitionAuth(void 0);
1077
1143
  this.#session = null;
1078
- this.#rotateAuthScope();
1079
1144
  this.#jar.clear();
1080
1145
  await this.#config.storage.clear();
1081
1146
  if (this.#deviceId) await this.#saveSession({ deviceId: this.#deviceId });
@@ -1172,6 +1237,7 @@ var AuthManager = class {
1172
1237
  REFRESH_COOKIE,
1173
1238
  this.#config.baseUrl + REFRESH_COOKIE_PATH
1174
1239
  );
1240
+ this.#transitionAuth(accessToken, false);
1175
1241
  await this.#saveSession({
1176
1242
  ...this.#session ?? {},
1177
1243
  accessToken,
@@ -1182,8 +1248,8 @@ var AuthManager = class {
1182
1248
  return accessToken;
1183
1249
  } catch (error) {
1184
1250
  if (error instanceof ItdApiError) {
1251
+ this.#transitionAuth(void 0);
1185
1252
  this.#session = null;
1186
- this.#rotateAuthScope();
1187
1253
  this.#jar.clear();
1188
1254
  await this.#config.storage.clear();
1189
1255
  const relogged = await this.#reloginOrNull();
@@ -1251,7 +1317,7 @@ var AuthManager = class {
1251
1317
  "\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."
1252
1318
  );
1253
1319
  }
1254
- this.#rotateAuthScope();
1320
+ this.#transitionAuth(accessToken);
1255
1321
  await this.#saveSession({ accessToken, obtainedAt: Date.now() });
1256
1322
  this.#emitter.emit("tokens", { accessToken });
1257
1323
  this.#emitter.emit("signIn", { accessToken });
@@ -1318,7 +1384,7 @@ function normalizeBaseUrl(baseUrl) {
1318
1384
  }
1319
1385
 
1320
1386
  // src/core/version.ts
1321
- var LIBRARY_VERSION = "0.0.10";
1387
+ var LIBRARY_VERSION = "0.0.11";
1322
1388
 
1323
1389
  // src/core/config.ts
1324
1390
  var DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
@@ -3292,6 +3358,18 @@ var ItdRealtime = class {
3292
3358
  get transport() {
3293
3359
  return this.#transport.name;
3294
3360
  }
3361
+ /** Базовый URL клиента, создавшего поток. @internal */
3362
+ get baseUrl() {
3363
+ return this.#deps.baseUrl;
3364
+ }
3365
+ /** Идентификаторы аккаунта и сессии клиента, создавшего поток. @internal */
3366
+ getAuthIdentity() {
3367
+ return this.#deps.getAuthIdentity?.();
3368
+ }
3369
+ /** Непрозрачная область авторизации создавшего поток клиента. @internal */
3370
+ getAuthScope() {
3371
+ return this.#deps.getAuthScope?.();
3372
+ }
3295
3373
  /** Подписывается на событие потока. @returns функция отписки */
3296
3374
  on(event, listener) {
3297
3375
  return this.#emitter.on(event, listener);
@@ -6168,7 +6246,9 @@ var ItdClient = class _ItdClient {
6168
6246
  } : void 0;
6169
6247
  const authPipeline = composePipeline([pluginsLayer, retriesLayer], transport.send);
6170
6248
  const authHandler = (request) => authRetry && request.retry === void 0 ? authPipeline({ ...request, retry: authRetry }) : authPipeline(request);
6171
- authManager = new AuthManager(config, authHandler, this.#jar);
6249
+ authManager = new AuthManager(config, authHandler, this.#jar, {
6250
+ onAccountChange: () => this.#disconnectStreams()
6251
+ });
6172
6252
  this.#authManager = authManager;
6173
6253
  const middlewares = [];
6174
6254
  if (queues) {
@@ -6247,7 +6327,8 @@ var ItdClient = class _ItdClient {
6247
6327
  this.#plugins.add(plugin, {
6248
6328
  baseUrl: this.#config.baseUrl,
6249
6329
  logger: this.#config.logger,
6250
- getAuthScope: () => this.#authManager.getAuthScope()
6330
+ getAuthScope: () => this.#authManager.getAuthScope(),
6331
+ getAuthIdentity: () => this.#authManager.getAuthIdentity()
6251
6332
  });
6252
6333
  return this;
6253
6334
  }
@@ -6307,7 +6388,8 @@ var ItdClient = class _ItdClient {
6307
6388
  * Создаёт поток уведомлений в реальном времени.
6308
6389
  *
6309
6390
  * Каждый вызов даёт новый независимый поток; обычно он нужен один на приложение.
6310
- * Соединение поднимается методом `connect()` и держится само.
6391
+ * Соединение поднимается методом `connect()` и держится само. Замена авторизации на токен
6392
+ * другого пользователя завершает все потоки клиента; смена только сессии их не затрагивает.
6311
6393
  *
6312
6394
  * @example
6313
6395
  * ```ts
@@ -6328,6 +6410,8 @@ var ItdClient = class _ItdClient {
6328
6410
  baseUrl: this.#config.baseUrl,
6329
6411
  fetch: this.#config.fetch,
6330
6412
  baseHeaders: (url) => this.#transport.platformHeaders(url),
6413
+ getAuthIdentity: () => this.#authManager.getCurrentAuthIdentity(),
6414
+ getAuthScope: () => this.#authManager.getAuthScope(),
6331
6415
  getToken: () => this.#authManager.getAccessToken(),
6332
6416
  refresh: () => this.#authManager.onUnauthorized(),
6333
6417
  fetchUnreadCount: () => this.notifications.count(),
@@ -6357,10 +6441,14 @@ var ItdClient = class _ItdClient {
6357
6441
  * ```
6358
6442
  */
6359
6443
  async close() {
6360
- for (const stream of this.#streams) stream.disconnect();
6361
- this.#streams.clear();
6444
+ this.#disconnectStreams();
6362
6445
  if (this.#ownsQueues) this.#queues?.stop();
6363
6446
  }
6447
+ /** Завершает потоки до того, как запросы начнут использовать другой аккаунт. */
6448
+ #disconnectStreams() {
6449
+ for (const stream of [...this.#streams]) stream.disconnect();
6450
+ this.#streams.clear();
6451
+ }
6364
6452
  /** Позволяет использовать клиент с `await using`. */
6365
6453
  [Symbol.asyncDispose]() {
6366
6454
  return this.close();
@@ -7295,5 +7383,5 @@ exports.scopedTokenStorage = scopedTokenStorage;
7295
7383
  exports.statusDays = statusDays;
7296
7384
  exports.toDate = toDate;
7297
7385
  exports.utcStampToIso = utcStampToIso;
7298
- //# sourceMappingURL=chunk-BN4AC3DP.cjs.map
7299
- //# sourceMappingURL=chunk-BN4AC3DP.cjs.map
7386
+ //# sourceMappingURL=chunk-QD4UHJFF.cjs.map
7387
+ //# sourceMappingURL=chunk-QD4UHJFF.cjs.map