@torrow/sdk-auth 0.8.0 → 0.9.1

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/index.js CHANGED
@@ -13,8 +13,69 @@ function appCredentialsToAuthIdentityBasic(appCredentials) {
13
13
  return { username: clientId, password: clientSecret };
14
14
  }
15
15
 
16
+ // ../../src/auth-session/otp-errors.ts
17
+ import {
18
+ OtpErrorReason,
19
+ TorrowSdkError,
20
+ TorrowSdkErrorCategory,
21
+ TorrowSdkErrorDomain,
22
+ TorrowSdkRequestState
23
+ } from "@torrow/sdk-client";
24
+ function otpFailure(kind, cleanup, cause) {
25
+ let category;
26
+ let reason;
27
+ switch (kind) {
28
+ case OtpErrorReason.InvalidChallenge:
29
+ category = TorrowSdkErrorCategory.InvalidInput;
30
+ reason = kind;
31
+ break;
32
+ case OtpErrorReason.UnsupportedMethod:
33
+ category = TorrowSdkErrorCategory.UnsupportedOperation;
34
+ reason = kind;
35
+ break;
36
+ case OtpErrorReason.AuthHeaderMissing:
37
+ category = TorrowSdkErrorCategory.AuthenticationRequired;
38
+ reason = kind;
39
+ break;
40
+ case OtpErrorReason.CodeRequestLimitExceeded:
41
+ category = TorrowSdkErrorCategory.RateLimited;
42
+ reason = kind;
43
+ break;
44
+ default:
45
+ category = kind;
46
+ }
47
+ return new TorrowSdkError({ category, detail: { domain: TorrowSdkErrorDomain.Otp, ...reason ? { reason } : {} }, sensitive: { cleanup, cause } });
48
+ }
49
+ function otpHttpFailure(error) {
50
+ if (!(error instanceof TorrowSdkError)) return error;
51
+ const body = error.getSensitiveDetails().body;
52
+ if (body === null || typeof body !== "object") return error;
53
+ const value = body;
54
+ if (value.name === "AUTH_HEADER_MISSING" /* AuthHeaderMissing */) {
55
+ return error.withContext({ category: TorrowSdkErrorCategory.AuthenticationRequired, detail: { domain: TorrowSdkErrorDomain.Otp, reason: OtpErrorReason.AuthHeaderMissing } });
56
+ }
57
+ if (value.name !== "CODE_REQUEST_LIMIT_EXCEEDED" /* CodeRequestLimitExceeded */) return error;
58
+ const minutes = duration(value.parameters?.minutes);
59
+ const seconds = duration(value.parameters?.seconds);
60
+ const wait = minutes === void 0 && seconds === void 0 ? void 0 : (minutes ?? 0) * 60 + (seconds ?? 0);
61
+ return error.withContext({ category: TorrowSdkErrorCategory.RateLimited, detail: {
62
+ domain: TorrowSdkErrorDomain.Otp,
63
+ reason: OtpErrorReason.CodeRequestLimitExceeded,
64
+ ...wait !== void 0 && Number.isFinite(wait) ? { retryAfterSeconds: wait } : {}
65
+ } });
66
+ }
67
+ function duration(value) {
68
+ const number = typeof value === "number" ? value : typeof value === "string" && /^\d+(?:\.\d+)?$/.test(value) ? Number(value) : void 0;
69
+ return number !== void 0 && Number.isFinite(number) && number >= 0 ? number : void 0;
70
+ }
71
+ function throwIfOtpAborted(signal) {
72
+ if (signal?.aborted) throw new TorrowSdkError({ category: TorrowSdkErrorCategory.Cancelled, request: TorrowSdkRequestState.NotStarted, sensitive: { cause: signal.reason } });
73
+ }
74
+
16
75
  // ../../src/auth-session/otp-flow.ts
17
76
  import {
77
+ OtpErrorReason as OtpErrorReason2,
78
+ TorrowSdkErrorCategory as TorrowSdkErrorCategory2,
18
79
  isAuthCaptchaType,
19
80
  isDeviceType,
20
81
  isSendCodeAccountType,
@@ -98,40 +159,24 @@ var OtpPhaseValues = /* @__PURE__ */ ((OtpPhaseValues2) => {
98
159
  OtpPhaseValues2["Pending"] = "Pending";
99
160
  return OtpPhaseValues2;
100
161
  })(OtpPhaseValues || {});
101
- var OtpFlowErrorCodeValues = /* @__PURE__ */ ((OtpFlowErrorCodeValues2) => {
102
- OtpFlowErrorCodeValues2["InvalidArguments"] = "InvalidArguments";
103
- OtpFlowErrorCodeValues2["InvalidChallenge"] = "InvalidChallenge";
104
- OtpFlowErrorCodeValues2["InvalidResponse"] = "InvalidResponse";
105
- OtpFlowErrorCodeValues2["UnsupportedMethod"] = "UnsupportedMethod";
106
- return OtpFlowErrorCodeValues2;
107
- })(OtpFlowErrorCodeValues || {});
108
- var OtpFlowError = class extends Error {
109
- constructor(code, cleanup) {
110
- super(`OTP flow: ${code}`);
111
- this.code = code;
112
- this.#cleanup = cleanup ? Object.freeze({ ...cleanup }) : void 0;
113
- }
114
- code;
115
- name = "OtpFlowError";
116
- #cleanup;
117
- /** Секрет доступен явно; не является перечисляемым полем исключения. */
118
- get cleanup() {
119
- return this.#cleanup;
120
- }
121
- };
122
162
  function createOtpFlow(options) {
123
163
  if (!isRecord(options) || !isRecord(options.deviceInfo) || typeof options.deviceInfo.deviceId !== "string" || !options.deviceInfo.deviceId.trim() || typeof options.deviceInfo.deviceType !== "string" || !isDeviceType(options.deviceInfo.deviceType) || options.scopes !== void 0 && (!Array.isArray(options.scopes) || !options.scopes.every(
124
164
  (scope) => typeof scope === "string" && scope.trim()
125
165
  )) || options.splitTokens !== void 0 && typeof options.splitTokens !== "boolean")
126
- throw new OtpFlowError("InvalidArguments" /* InvalidArguments */);
127
- const client = createIdentityClient(options);
166
+ throw otpFailure(TorrowSdkErrorCategory2.InvalidInput);
167
+ let client;
168
+ try {
169
+ client = createIdentityClient(options);
170
+ } catch (cause) {
171
+ throw otpFailure(TorrowSdkErrorCategory2.InvalidInput, void 0, cause);
172
+ }
128
173
  const deviceInfo = { ...options.deviceInfo };
129
174
  const scopes = options.scopes ? [...options.scopes] : void 0;
130
175
  const splitTokens = options.splitTokens;
131
176
  return {
132
177
  async start(input, requestOptions) {
133
178
  validateStartInput(input);
134
- requestOptions?.signal?.throwIfAborted();
179
+ throwIfOtpAborted(requestOptions?.signal);
135
180
  const startedAt = Date.now();
136
181
  const response = await sendOtpCode(
137
182
  client,
@@ -141,16 +186,18 @@ function createOtpFlow(options) {
141
186
  type: input.account.accountType
142
187
  },
143
188
  requestOptions?.signal
144
- );
189
+ ).catch((error) => {
190
+ throw otpHttpFailure(error);
191
+ });
145
192
  if (!isRecord(response) || typeof response.sessionId !== "string" || !response.sessionId.trim())
146
- throw new OtpFlowError("InvalidResponse" /* InvalidResponse */);
193
+ throw otpFailure(TorrowSdkErrorCategory2.InvalidResponse);
147
194
  if (!supportedMethod(response.usedMethod, input.account.accountType))
148
- throw new OtpFlowError("UnsupportedMethod" /* UnsupportedMethod */);
195
+ throw otpFailure(OtpErrorReason2.UnsupportedMethod);
149
196
  const ttl = response.sessionLifeTimeInSeconds ?? 180;
150
- if (!positive(ttl)) throw new OtpFlowError("InvalidResponse" /* InvalidResponse */);
197
+ if (!positive(ttl)) throw otpFailure(TorrowSdkErrorCategory2.InvalidResponse);
151
198
  const expiresAtMs = startedAt + ttl * 1e3;
152
199
  if (!timestamp(expiresAtMs) || expiresAtMs <= Date.now())
153
- throw new OtpFlowError("InvalidResponse" /* InvalidResponse */);
200
+ throw otpFailure(TorrowSdkErrorCategory2.InvalidResponse);
154
201
  const mobile = response.usedMethod === VerificationMethodValues.MtsMobileId;
155
202
  const nextSendAtMs = response.nextAvailableUsageTime instanceof Date ? response.nextAvailableUsageTime.getTime() : typeof response.nextAvailableUsageTime === "string" ? Date.parse(response.nextAvailableUsageTime) : void 0;
156
203
  return {
@@ -169,11 +216,11 @@ function createOtpFlow(options) {
169
216
  };
170
217
  },
171
218
  async complete(input, requestOptions) {
172
- if (!isRecord(input)) throw new OtpFlowError("InvalidArguments" /* InvalidArguments */);
173
- requestOptions?.signal?.throwIfAborted();
219
+ if (!isRecord(input)) throw otpFailure(TorrowSdkErrorCategory2.InvalidInput);
220
+ throwIfOtpAborted(requestOptions?.signal);
174
221
  const continuation = validateChallenge(input.challenge);
175
222
  if (continuation.phase === "Pending" /* Pending */ && input.code !== void 0 || continuation.phase === "AwaitingCode" /* AwaitingCode */ && (typeof input.code !== "string" || !input.code.trim()))
176
- throw new OtpFlowError("InvalidArguments" /* InvalidArguments */);
223
+ throw otpFailure(TorrowSdkErrorCategory2.InvalidInput);
177
224
  const raw = await requestAuthToken(
178
225
  client,
179
226
  {
@@ -184,8 +231,10 @@ function createOtpFlow(options) {
184
231
  splitTokens
185
232
  },
186
233
  requestOptions?.signal
187
- );
188
- if (!isRecord(raw)) throw new OtpFlowError("InvalidResponse" /* InvalidResponse */);
234
+ ).catch((error) => {
235
+ throw otpHttpFailure(error);
236
+ });
237
+ if (!isRecord(raw)) throw otpFailure(TorrowSdkErrorCategory2.InvalidResponse);
189
238
  const fields = tokenResponseFields(raw);
190
239
  const refreshToken = typeof fields.refresh === "string" && fields.refresh.trim() ? fields.refresh.trim() : void 0;
191
240
  const cleanup = refreshToken ? { refreshToken } : void 0;
@@ -193,7 +242,7 @@ function createOtpFlow(options) {
193
242
  typeof fields.status === "string" ? fields.status.trim() : fields.status
194
243
  );
195
244
  if (!status || fields.access != null && typeof fields.access !== "string" || fields.refresh != null && typeof fields.refresh !== "string" || fields.expiration != null && (typeof fields.expiration !== "number" || !Number.isFinite(fields.expiration) || fields.expiration < 0))
196
- throw new OtpFlowError("InvalidResponse" /* InvalidResponse */, cleanup);
245
+ throw otpFailure(TorrowSdkErrorCategory2.InvalidResponse, cleanup);
197
246
  if (status === TokenStatusValues2.Pending)
198
247
  return {
199
248
  status,
@@ -220,7 +269,7 @@ function createOtpFlow(options) {
220
269
  }
221
270
  if (status === TokenStatusValues2.Failed) return { status, cleanup };
222
271
  if (typeof fields.access !== "string" || !fields.access.trim())
223
- throw new OtpFlowError("InvalidResponse" /* InvalidResponse */, cleanup);
272
+ throw otpFailure(TorrowSdkErrorCategory2.InvalidResponse, cleanup);
224
273
  return {
225
274
  status,
226
275
  tokens: {
@@ -258,19 +307,19 @@ function supportedMethod(value, accountType) {
258
307
  }
259
308
  function validateStartInput(input) {
260
309
  if (!isRecord(input) || !isRecord(input.account) || typeof input.account.accountType !== "string" || !isSendCodeAccountType(input.account.accountType) || typeof input.account.accountName !== "string" || !input.account.accountName.trim())
261
- throw new OtpFlowError("InvalidArguments" /* InvalidArguments */);
310
+ throw otpFailure(TorrowSdkErrorCategory2.InvalidInput);
262
311
  for (const field of ["language", "captcha", "inviteSessionId"]) {
263
312
  if (input[field] !== void 0 && typeof input[field] !== "string")
264
- throw new OtpFlowError("InvalidArguments" /* InvalidArguments */);
313
+ throw otpFailure(TorrowSdkErrorCategory2.InvalidInput);
265
314
  }
266
315
  if (input.regionStorageId !== void 0 && !attempts(input.regionStorageId) || input.captchaType !== void 0 && (typeof input.captchaType !== "string" || !isAuthCaptchaType(input.captchaType)) || input.preferredMethod !== void 0 && (typeof input.preferredMethod !== "string" || !isVerificationMethod(input.preferredMethod)) || input.supportedMethods !== void 0 && !methods(input.supportedMethods))
267
- throw new OtpFlowError("InvalidArguments" /* InvalidArguments */);
316
+ throw otpFailure(TorrowSdkErrorCategory2.InvalidInput);
268
317
  }
269
318
  function validateChallenge(value) {
270
319
  if (!isRecord(value) || typeof value.sessionId !== "string" || !value.sessionId.trim() || typeof value.accountType !== "string" || !isSendCodeAccountType(value.accountType) || !supportedMethod(value.usedMethod, value.accountType) || value.phase !== "Pending" /* Pending */ && value.phase !== "AwaitingCode" /* AwaitingCode */ || !timestamp(value.expiresAtMs) || value.expiresAtMs <= Date.now() || value.phase === "Pending" /* Pending */ !== (value.usedMethod === VerificationMethodValues.MtsMobileId))
271
- throw new OtpFlowError("InvalidChallenge" /* InvalidChallenge */);
320
+ throw otpFailure(OtpErrorReason2.InvalidChallenge);
272
321
  if (value.codeLength !== void 0 && !codeLength(value.codeLength) || value.pollIntervalSeconds !== void 0 && !positive(value.pollIntervalSeconds) || value.attemptsRemaining !== void 0 && !attempts(value.attemptsRemaining) || value.nextSendAtMs !== void 0 && !timestamp(value.nextSendAtMs) || value.availableMethods !== void 0 && !methods(value.availableMethods))
273
- throw new OtpFlowError("InvalidChallenge" /* InvalidChallenge */);
322
+ throw otpFailure(OtpErrorReason2.InvalidChallenge);
274
323
  return {
275
324
  sessionId: value.sessionId.trim(),
276
325
  accountType: value.accountType,
@@ -354,36 +403,92 @@ var LocalStorageTokenStorage = class {
354
403
  }
355
404
  };
356
405
 
357
- // ../../src/auth-session/auth-session-not-ready.ts
358
- import { TorrowHttpError } from "@torrow/sdk-client";
359
- var AuthSessionNotReadyError = class extends Error {
360
- name = "AuthSessionNotReadyError";
361
- cause;
362
- constructor(message = "Auth session is not ready yet", cause) {
363
- super(message);
364
- this.cause = cause;
365
- }
366
- };
367
- function isAuthSessionNotReadyYet(e) {
368
- if (e instanceof AuthSessionNotReadyError) return true;
369
- if (!(e instanceof TorrowHttpError)) return false;
370
- const text = [
371
- e.message,
372
- e.apiErrorName,
373
- typeof e.body === "string" ? e.body : JSON.stringify(e.body ?? "")
374
- ].join(" ").toLowerCase();
375
- return text.includes("session is not ready") || text.includes("unauthorized_client");
376
- }
377
-
378
406
  // ../../src/auth-session/TokenSession.ts
379
407
  import {
380
408
  DefaultAuthScopes as DefaultAuthScopes2,
381
409
  TorrowClient as TorrowClient2,
382
- TorrowHttpError as TorrowHttpError2,
410
+ TorrowSdkError as TorrowSdkError4,
411
+ TorrowSdkErrorCategory as TorrowSdkErrorCategory4,
412
+ TorrowSdkEffectArea,
413
+ TorrowSdkEffectState,
414
+ TorrowSdkRequestState as TorrowSdkRequestState2,
415
+ AuthSessionEffectStep,
416
+ AuthSessionErrorReason as AuthSessionErrorReason2,
383
417
  DeviceTypeValues,
384
418
  TokenStatusValues as TokenStatusValues3
385
419
  } from "@torrow/sdk-client";
386
420
 
421
+ // ../../src/auth-session/auth-session-not-ready.ts
422
+ import { AuthSessionErrorReason, TorrowSdkError as TorrowSdkError2, TorrowSdkErrorDomain as TorrowSdkErrorDomain2 } from "@torrow/sdk-client";
423
+ function isAuthSessionNotReadyYet(error) {
424
+ if (!(error instanceof TorrowSdkError2)) return false;
425
+ if (error.detail?.domain === TorrowSdkErrorDomain2.AuthSession && error.detail.reason === AuthSessionErrorReason.SessionNotReady) return true;
426
+ if (!error.http) return false;
427
+ const body = error.getSensitiveDetails().body;
428
+ const text = [error.http.apiErrorName, typeof body === "string" ? body : JSON.stringify(body ?? "")].join(" ").toLowerCase();
429
+ return text.includes("session is not ready") || text.includes("unauthorized_client");
430
+ }
431
+
432
+ // ../../src/auth-session/session-error.ts
433
+ import {
434
+ TorrowSdkError as TorrowSdkError3,
435
+ TorrowSdkErrorCategory as TorrowSdkErrorCategory3,
436
+ TorrowSdkErrorDomain as TorrowSdkErrorDomain3
437
+ } from "@torrow/sdk-client";
438
+ var SessionFailureStage = /* @__PURE__ */ ((SessionFailureStage2) => {
439
+ SessionFailureStage2["SendCode"] = "SendCode";
440
+ SessionFailureStage2["CreateAuthSession"] = "CreateAuthSession";
441
+ SessionFailureStage2["OnSendCode"] = "OnSendCode";
442
+ SessionFailureStage2["GetCode"] = "GetCode";
443
+ SessionFailureStage2["OnLink"] = "OnLink";
444
+ SessionFailureStage2["PollTokens"] = "PollTokens";
445
+ SessionFailureStage2["ValidateResponse"] = "ValidateResponse";
446
+ SessionFailureStage2["ValidateInput"] = "ValidateInput";
447
+ SessionFailureStage2["ValidateState"] = "ValidateState";
448
+ SessionFailureStage2["LoadStorage"] = "LoadStorage";
449
+ SessionFailureStage2["InitializeDevice"] = "InitializeDevice";
450
+ SessionFailureStage2["BuildClient"] = "BuildClient";
451
+ SessionFailureStage2["IssueTokens"] = "IssueTokens";
452
+ SessionFailureStage2["ValidateTokens"] = "ValidateTokens";
453
+ SessionFailureStage2["ApplyTokens"] = "ApplyTokens";
454
+ SessionFailureStage2["RefreshTokens"] = "RefreshTokens";
455
+ SessionFailureStage2["RefreshFailureCallback"] = "RefreshFailureCallback";
456
+ SessionFailureStage2["ClearTokens"] = "ClearTokens";
457
+ SessionFailureStage2["SaveTokens"] = "SaveTokens";
458
+ SessionFailureStage2["ClearStorage"] = "ClearStorage";
459
+ SessionFailureStage2["OpenSession"] = "OpenSession";
460
+ SessionFailureStage2["SeedStorage"] = "SeedStorage";
461
+ SessionFailureStage2["Connect"] = "Connect";
462
+ return SessionFailureStage2;
463
+ })(SessionFailureStage || {});
464
+ function sessionFailure(error, context) {
465
+ const detail = { domain: TorrowSdkErrorDomain3.AuthSession, ...context.reason ? { reason: context.reason } : {} };
466
+ if (context.category !== void 0) {
467
+ return new TorrowSdkError3({
468
+ ...context,
469
+ category: context.category,
470
+ detail,
471
+ sensitive: { cause: error }
472
+ });
473
+ }
474
+ if (error instanceof TorrowSdkError3) {
475
+ return error.withContext({
476
+ operation: context.operation,
477
+ failureAt: error.operation?.startsWith("TokenSession") ? error.failureAt ?? context.failureAt : context.failureAt,
478
+ effects: context.effects,
479
+ ...context.reason ? { detail } : {}
480
+ });
481
+ }
482
+ return new TorrowSdkError3({
483
+ category: TorrowSdkErrorCategory3.Unknown,
484
+ operation: context.operation,
485
+ failureAt: context.failureAt,
486
+ effects: context.effects,
487
+ detail,
488
+ sensitive: { cause: error }
489
+ });
490
+ }
491
+
387
492
  // ../../src/auth-session/fetch-with-401-retry.ts
388
493
  function requestUrl(input) {
389
494
  if (typeof input === "string") return input;
@@ -500,8 +605,8 @@ function assertTokenRespReadyForStorage(tokens, context) {
500
605
  var DEFAULT_SKEW_MS = 3e4;
501
606
  var SESSION_CLOSED_MESSAGE = "TokenSession: session closed (logout)";
502
607
  function defaultOnRefreshFailure(err) {
503
- if (!(err instanceof TorrowHttpError2)) return "keep";
504
- const s = err.status;
608
+ if (!(err instanceof TorrowSdkError4) || !err.http) return "keep";
609
+ const s = err.http?.status;
505
610
  if (s === 429 || s === 502 || s === 503 || s === 504) return "keep";
506
611
  return "clear";
507
612
  }
@@ -517,22 +622,48 @@ var TokenSession = class _TokenSession {
517
622
  closed = false;
518
623
  /** Set in refreshWithFailurePolicy catch; not cleared on success (see public refresh). */
519
624
  lastRefreshError;
625
+ /** Открыть сессию; storage/refresh ошибки описывают только выполненную инициализацию. */
520
626
  static async open(opts) {
627
+ return (await _TokenSession.openWithEffects(opts)).session;
628
+ }
629
+ /**
630
+ * @internal Инициализация с явными сведениями только текущего вызова для составного login.
631
+ * Не наблюдает fetch/storage и не хранит историю независимых операций.
632
+ */
633
+ static async openWithEffects(opts) {
521
634
  const session = new _TokenSession(opts);
522
- session.tokenSnapshot = await opts.tokenStorage.load();
523
- if (!session.tokenSnapshot.deviceId?.trim()) {
524
- const di = session.resolveDeviceInfo();
525
- const id = di.deviceId?.trim();
526
- if (id) {
527
- session.tokenSnapshot = { ...session.tokenSnapshot, deviceId: id };
528
- await session.persist(false);
635
+ const effects = [];
636
+ let failureAt = "LoadStorage" /* LoadStorage */;
637
+ try {
638
+ try {
639
+ session.tokenSnapshot = await opts.tokenStorage.load();
640
+ } catch (error) {
641
+ throw sessionFailure(error, {
642
+ operation: "TokenSession.open",
643
+ failureAt,
644
+ effects,
645
+ category: TorrowSdkErrorCategory4.StorageFailure
646
+ });
529
647
  }
648
+ effects.push({ area: TorrowSdkEffectArea.SessionMemory, step: AuthSessionEffectStep.SetTokens, state: TorrowSdkEffectState.Applied });
649
+ failureAt = "InitializeDevice" /* InitializeDevice */;
650
+ if (!session.tokenSnapshot.deviceId?.trim()) {
651
+ const id = session.resolveDeviceInfo().deviceId?.trim();
652
+ if (id) {
653
+ session.tokenSnapshot = { ...session.tokenSnapshot, deviceId: id };
654
+ await session.persist(false, effects);
655
+ }
656
+ }
657
+ failureAt = "BuildClient" /* BuildClient */;
658
+ session._client = session.buildClient();
659
+ if (!opts.skipEnsureFreshOnOpen) {
660
+ failureAt = "RefreshTokens" /* RefreshTokens */;
661
+ await session.runExclusive(() => session.ensureFreshImpl(effects));
662
+ }
663
+ return { session, effects: Object.freeze(effects.map((effect) => Object.freeze({ ...effect }))) };
664
+ } catch (error) {
665
+ throw sessionFailure(error, { operation: "TokenSession.open", failureAt, effects });
530
666
  }
531
- session._client = session.buildClient();
532
- if (!opts.skipEnsureFreshOnOpen) {
533
- await session.ensureFresh();
534
- }
535
- return session;
536
667
  }
537
668
  /** Persist, переданный в `TokenSession.open` / `TorrowAuth.*`. */
538
669
  get tokenStorage() {
@@ -567,9 +698,18 @@ var TokenSession = class _TokenSession {
567
698
  getDeviceInfo() {
568
699
  return this.resolveDeviceInfo();
569
700
  }
701
+ /** Получить access после проактивного refresh; closed возвращает undefined без записи. */
570
702
  async getAccessToken() {
571
703
  if (this.closed) return void 0;
572
- await this.ensureFresh();
704
+ try {
705
+ await this.ensureFresh();
706
+ } catch (error) {
707
+ throw sessionFailure(error, {
708
+ operation: "TokenSession.getAccessToken",
709
+ failureAt: "RefreshTokens" /* RefreshTokens */,
710
+ effects: error instanceof TorrowSdkError4 ? error.effects ?? [] : []
711
+ });
712
+ }
573
713
  return this.tokenSnapshot.accessToken?.trim() || void 0;
574
714
  }
575
715
  /**
@@ -578,26 +718,53 @@ var TokenSession = class _TokenSession {
578
718
  * (refresh-shaped); логин — `{ replace: true }`. После `logout` — новая сессия.
579
719
  */
580
720
  async applyTokenResp(tokens, opts) {
581
- await this.runExclusive(() => this.applyTokenRespUnlocked(tokens, opts));
721
+ const effects = [];
722
+ await this.runExclusive(async () => {
723
+ try {
724
+ await this.applyTokenRespUnlocked(tokens, opts, effects);
725
+ } catch (error) {
726
+ throw sessionFailure(error, { operation: "TokenSession.applyTokenResp", failureAt: "ApplyTokens" /* ApplyTokens */, effects });
727
+ }
728
+ });
582
729
  }
730
+ /** Очистить память и сохранить snapshot; storage-сбой не отменяет очистку памяти. */
583
731
  async clearTokens() {
584
- await this.runExclusive(() => this.clearTokensUnlocked());
732
+ const effects = [];
733
+ await this.runExclusive(async () => {
734
+ try {
735
+ await this.clearTokensUnlocked(effects);
736
+ } catch (error) {
737
+ throw sessionFailure(error, { operation: "TokenSession.clearTokens", failureAt: "ClearTokens" /* ClearTokens */, effects });
738
+ }
739
+ });
585
740
  }
586
741
  /**
587
742
  * Revoke refresh (если есть) + очистка storage.
588
743
  * Единственное публичное имя выхода из сессии.
589
744
  */
590
745
  async logout() {
746
+ const effects = [];
591
747
  await this.runExclusive(async () => {
592
748
  this.closed = true;
749
+ effects.push({ area: TorrowSdkEffectArea.SessionMemory, step: AuthSessionEffectStep.CloseSession, state: TorrowSdkEffectState.Applied });
593
750
  const refresh = this.tokenSnapshot.refreshToken?.trim();
594
751
  if (refresh) {
752
+ let state = TorrowSdkEffectState.Applied;
753
+ let prepared = false;
595
754
  try {
596
- await this.identityClient().auth.revoke({ token: refresh });
597
- } catch {
755
+ const identity = this.identityClient();
756
+ prepared = true;
757
+ await identity.auth.revoke({ token: refresh });
758
+ } catch (error) {
759
+ state = !prepared || error instanceof TorrowSdkError4 && error.request === TorrowSdkRequestState2.NotStarted ? TorrowSdkEffectState.NotApplied : TorrowSdkEffectState.Unknown;
598
760
  }
761
+ effects.push({ area: TorrowSdkEffectArea.Remote, step: AuthSessionEffectStep.RevokeTokens, state });
762
+ }
763
+ try {
764
+ await this.clearTokensUnlocked(effects);
765
+ } catch (error) {
766
+ throw sessionFailure(error, { operation: "TokenSession.logout", failureAt: "ClearTokens" /* ClearTokens */, effects });
599
767
  }
600
- await this.clearTokensUnlocked();
601
768
  });
602
769
  }
603
770
  /**
@@ -606,43 +773,79 @@ var TokenSession = class _TokenSession {
606
773
  *
607
774
  * Один вызов `POST /api/v1/auth/token` (`requestToken`) с `{ sessionId, code }`.
608
775
  * При успехе (`TokenReady`) — persist; если сессия ещё не готова —
609
- * {@link AuthSessionNotReadyError} (поллинг для deep-link — внутри `TorrowAuth.loginWithLink`).
776
+ * `TorrowSdkError` с причиной `SessionNotReady` (поллинг для deep-link — внутри `TorrowAuth.loginWithLink`).
610
777
  *
611
778
  * HTTP вне mutex; persist сериализован через {@link applyTokenResp} (не оборачивать
612
779
  * весь метод в `runExclusive` — вложенный mutex с публичным `applyTokenResp`).
613
780
  */
614
781
  async requestTokenWithCode(args) {
615
- this.assertNotClosedForWrite();
616
- const sessionId = args.sessionId.trim();
617
- const code = args.code.trim();
618
- if (!sessionId || !code) {
619
- throw new Error("TokenSession.requestTokenWithCode: sessionId and code are required");
620
- }
621
- const scopes = args.scopes && args.scopes.length > 0 ? args.scopes : [...DefaultAuthScopes2];
622
- const deviceInfo = this.resolveDeviceInfo();
623
- let resp;
782
+ const effects = [];
783
+ let failureAt = "ValidateInput" /* ValidateInput */;
624
784
  try {
625
- resp = normalizeTokenResp(
626
- await requestAuthToken(
627
- this.identityClient(),
628
- { sessionId, code, scopes, deviceInfo },
629
- args.signal
630
- )
631
- );
632
- } catch (e) {
633
- if (isAuthSessionNotReadyYet(e)) {
634
- throw new AuthSessionNotReadyError("Auth session is not ready yet", e);
785
+ this.assertNotClosedForWrite();
786
+ const sessionId = args.sessionId.trim();
787
+ const code = args.code.trim();
788
+ if (!sessionId || !code) {
789
+ throw sessionFailure(void 0, {
790
+ operation: "TokenSession.requestTokenWithCode",
791
+ failureAt,
792
+ effects,
793
+ category: TorrowSdkErrorCategory4.InvalidInput
794
+ });
635
795
  }
636
- throw e;
637
- }
638
- if (tokenTransition(resp.status, true) !== TokenStatusValues3.TokenReady) {
639
- throw new AuthSessionNotReadyError(
640
- `TokenResp.status=${resp.status} (\u043E\u0436\u0438\u0434\u0430\u043B\u0441\u044F TokenReady)`
641
- );
796
+ const scopes = args.scopes && args.scopes.length > 0 ? args.scopes : [...DefaultAuthScopes2];
797
+ const deviceInfo = this.resolveDeviceInfo();
798
+ failureAt = "IssueTokens" /* IssueTokens */;
799
+ const identity = this.identityClient();
800
+ let resp;
801
+ try {
802
+ resp = normalizeTokenResp(await requestAuthToken(identity, { sessionId, code, scopes, deviceInfo }, args.signal));
803
+ } catch (error) {
804
+ effects.push({
805
+ area: TorrowSdkEffectArea.Remote,
806
+ step: AuthSessionEffectStep.IssueTokens,
807
+ state: error instanceof TorrowSdkError4 && error.request === TorrowSdkRequestState2.NotStarted ? TorrowSdkEffectState.NotApplied : TorrowSdkEffectState.Unknown
808
+ });
809
+ if (isAuthSessionNotReadyYet(error)) {
810
+ throw sessionFailure(error, {
811
+ operation: "TokenSession.requestTokenWithCode",
812
+ failureAt,
813
+ effects,
814
+ reason: AuthSessionErrorReason2.SessionNotReady
815
+ });
816
+ }
817
+ throw error;
818
+ }
819
+ failureAt = "ValidateTokens" /* ValidateTokens */;
820
+ if (tokenTransition(resp.status, true) !== TokenStatusValues3.TokenReady) {
821
+ effects.push({ area: TorrowSdkEffectArea.Remote, step: AuthSessionEffectStep.IssueTokens, state: TorrowSdkEffectState.Unknown });
822
+ throw sessionFailure(void 0, {
823
+ operation: "TokenSession.requestTokenWithCode",
824
+ failureAt,
825
+ effects,
826
+ category: TorrowSdkErrorCategory4.InvalidState,
827
+ reason: AuthSessionErrorReason2.SessionNotReady
828
+ });
829
+ }
830
+ let ready;
831
+ try {
832
+ ready = assertTokenRespReadyForStorage(resp, "TokenSession.requestTokenWithCode");
833
+ } catch (error) {
834
+ effects.push({ area: TorrowSdkEffectArea.Remote, step: AuthSessionEffectStep.IssueTokens, state: TorrowSdkEffectState.Unknown });
835
+ throw sessionFailure(error, {
836
+ operation: "TokenSession.requestTokenWithCode",
837
+ failureAt,
838
+ effects,
839
+ category: TorrowSdkErrorCategory4.InvalidResponse
840
+ });
841
+ }
842
+ effects.push({ area: TorrowSdkEffectArea.Remote, step: AuthSessionEffectStep.IssueTokens, state: TorrowSdkEffectState.Applied });
843
+ failureAt = "ApplyTokens" /* ApplyTokens */;
844
+ await this.runExclusive(() => this.applyTokenRespUnlocked(ready, { replace: true }, effects));
845
+ return ready;
846
+ } catch (error) {
847
+ throw sessionFailure(error, { operation: "TokenSession.requestTokenWithCode", failureAt, effects });
642
848
  }
643
- const ready = assertTokenRespReadyForStorage(resp, "TokenSession.requestTokenWithCode");
644
- await this.applyTokenResp(ready, { replace: true });
645
- return ready;
646
849
  }
647
850
  /**
648
851
  * Явный refresh (single-flight через общий mutex сессии).
@@ -651,86 +854,145 @@ var TokenSession = class _TokenSession {
651
854
  * не используй return value; смотри `session.status` / `getTokenSnapshot()`.
652
855
  */
653
856
  async refresh() {
857
+ const effects = [];
654
858
  return this.runExclusive(async () => {
655
- this.assertNotClosedForWrite();
656
- if (!this.tokenSnapshot.refreshToken?.trim()) {
657
- throw new Error("TokenSession.refresh: \u043D\u0435\u0442 refreshToken \u0432 storage");
658
- }
659
- const result = await this.refreshWithFailurePolicy();
660
- if (result.outcome === "refreshed") {
661
- return result.resp;
662
- }
663
- if (this.lastRefreshError !== void 0) {
664
- throw this.lastRefreshError;
859
+ try {
860
+ this.assertNotClosedForWrite();
861
+ if (!this.tokenSnapshot.refreshToken?.trim()) {
862
+ throw sessionFailure(void 0, {
863
+ operation: "TokenSession.refresh",
864
+ failureAt: "ValidateState" /* ValidateState */,
865
+ effects,
866
+ category: TorrowSdkErrorCategory4.InvalidState
867
+ });
868
+ }
869
+ const result = await this.refreshWithFailurePolicy(effects);
870
+ if (result.outcome === "refreshed") return result.resp;
871
+ throw result.error ?? this.lastRefreshError;
872
+ } catch (error) {
873
+ throw sessionFailure(error, { operation: "TokenSession.refresh", failureAt: "RefreshTokens" /* RefreshTokens */, effects });
665
874
  }
666
- throw new Error("TokenSession.refresh: refresh failed");
667
875
  });
668
876
  }
877
+ /** Обновить просроченный access; политика keep/clear сохраняет прежнее resolve-поведение. */
669
878
  async ensureFresh() {
670
- await this.runExclusive(() => this.ensureFreshImpl());
879
+ const effects = [];
880
+ await this.runExclusive(async () => {
881
+ try {
882
+ await this.ensureFreshImpl(effects);
883
+ } catch (error) {
884
+ throw sessionFailure(error, { operation: "TokenSession.ensureFresh", failureAt: "RefreshTokens" /* RefreshTokens */, effects });
885
+ }
886
+ });
671
887
  }
672
888
  assertNotClosedForWrite() {
673
889
  if (this.closed) {
674
- throw new Error(SESSION_CLOSED_MESSAGE);
890
+ throw sessionFailure(new Error(SESSION_CLOSED_MESSAGE), {
891
+ operation: "TokenSession",
892
+ failureAt: "ValidateState" /* ValidateState */,
893
+ effects: [],
894
+ category: TorrowSdkErrorCategory4.InvalidState
895
+ });
675
896
  }
676
897
  }
677
898
  /** Caller must hold `runExclusive`. Do not call public `applyTokenResp` from here. */
678
- async applyTokenRespUnlocked(tokens, opts) {
899
+ async applyTokenRespUnlocked(tokens, opts, effects = []) {
679
900
  this.assertNotClosedForWrite();
680
901
  const normalized = normalizeTokenResp(tokens);
681
- assertTokenRespReadyForStorage(normalized, "TokenSession.applyTokenResp");
902
+ try {
903
+ assertTokenRespReadyForStorage(normalized, "TokenSession.applyTokenResp");
904
+ } catch (error) {
905
+ throw sessionFailure(error, {
906
+ operation: "TokenSession",
907
+ failureAt: "ValidateTokens" /* ValidateTokens */,
908
+ effects,
909
+ category: TorrowSdkErrorCategory4.InvalidResponse
910
+ });
911
+ }
682
912
  this.tokenSnapshot = mergeAuthTokens(this.tokenSnapshot, normalized, {
683
913
  ...opts,
684
914
  // Host TTL survives refresh without expiration only while still fresh (incl. skew); else cleared → opaque until 401.
685
915
  keepPrevTtlIfExpiresAfterMs: Date.now() + (this.opts.accessTokenSkewMs ?? DEFAULT_SKEW_MS)
686
916
  });
687
- await this.persist(true);
917
+ effects.push({ area: TorrowSdkEffectArea.SessionMemory, step: AuthSessionEffectStep.SetTokens, state: TorrowSdkEffectState.Applied });
918
+ await this.persist(true, effects);
688
919
  }
689
- async clearTokensUnlocked() {
920
+ async clearTokensUnlocked(effects = []) {
690
921
  this.tokenSnapshot = {
691
922
  ...this.tokenSnapshot,
692
923
  accessToken: void 0,
693
924
  refreshToken: void 0,
694
925
  accessExpiresAtMs: void 0
695
926
  };
696
- await this.persist(true);
927
+ effects.push({ area: TorrowSdkEffectArea.SessionMemory, step: AuthSessionEffectStep.ClearTokens, state: TorrowSdkEffectState.Applied });
928
+ await this.persist(true, effects, true);
697
929
  }
698
930
  /** Caller must hold `runExclusive`. `refreshWithFailurePolicy` skips when no refreshToken; guard here is intentional defense-in-depth. */
699
- async refreshUnlocked() {
931
+ async refreshUnlocked(effects) {
700
932
  const refresh = this.tokenSnapshot.refreshToken?.trim();
701
933
  if (!refresh) {
702
- throw new Error("TokenSession.refresh: \u043D\u0435\u0442 refreshToken \u0432 storage");
934
+ throw sessionFailure(void 0, {
935
+ operation: "TokenSession",
936
+ failureAt: "ValidateState" /* ValidateState */,
937
+ effects,
938
+ category: TorrowSdkErrorCategory4.InvalidState
939
+ });
703
940
  }
704
941
  const deviceInfo = this.resolveDeviceInfo();
705
- const resp = await this.identityClient().auth.refresh({
706
- refreshToken: refresh,
707
- deviceInfo
708
- });
709
- await this.applyTokenRespUnlocked(resp);
710
- return resp;
711
- }
712
- /** Caller must hold `runExclusive`. */
713
- async refreshWithFailurePolicy() {
714
- if (this.closed) {
715
- return { outcome: "unchanged" };
942
+ const identity = this.identityClient();
943
+ let resp;
944
+ try {
945
+ resp = await identity.auth.refresh({ refreshToken: refresh, deviceInfo });
946
+ } catch (error) {
947
+ effects.push({
948
+ area: TorrowSdkEffectArea.Remote,
949
+ step: AuthSessionEffectStep.RefreshTokens,
950
+ state: error instanceof TorrowSdkError4 && error.request === TorrowSdkRequestState2.NotStarted ? TorrowSdkEffectState.NotApplied : TorrowSdkEffectState.Unknown
951
+ });
952
+ throw error;
716
953
  }
717
- const refresh = this.tokenSnapshot.refreshToken?.trim();
718
- if (!refresh) {
719
- return { outcome: "unchanged" };
954
+ const normalized = normalizeTokenResp(resp);
955
+ try {
956
+ assertTokenRespReadyForStorage(normalized, "TokenSession.refresh");
957
+ } catch (error) {
958
+ effects.push({ area: TorrowSdkEffectArea.Remote, step: AuthSessionEffectStep.RefreshTokens, state: TorrowSdkEffectState.Unknown });
959
+ throw sessionFailure(error, {
960
+ operation: "TokenSession",
961
+ failureAt: "ValidateTokens" /* ValidateTokens */,
962
+ effects,
963
+ category: TorrowSdkErrorCategory4.InvalidResponse
964
+ });
720
965
  }
966
+ effects.push({ area: TorrowSdkEffectArea.Remote, step: AuthSessionEffectStep.RefreshTokens, state: TorrowSdkEffectState.Applied });
967
+ await this.applyTokenRespUnlocked(resp, void 0, effects);
968
+ return resp;
969
+ }
970
+ /** Caller must hold `runExclusive`; сведения принадлежат только текущей операции. */
971
+ async refreshWithFailurePolicy(effects = []) {
972
+ if (this.closed || !this.tokenSnapshot.refreshToken?.trim()) return { outcome: "unchanged" };
721
973
  try {
722
- const resp = await this.refreshUnlocked();
974
+ const resp = await this.refreshUnlocked(effects);
723
975
  this.lastRefreshError = void 0;
724
976
  return { outcome: "refreshed", resp };
725
- } catch (e) {
726
- this.lastRefreshError = e;
727
- const action = await this.opts.onRefreshFailure?.(e) ?? defaultOnRefreshFailure(e);
728
- if (action === "throw") throw e;
729
- if (action === "keep") {
730
- return { outcome: "unchanged" };
977
+ } catch (cause) {
978
+ const error = sessionFailure(cause, { operation: "TokenSession", failureAt: "RefreshTokens" /* RefreshTokens */, effects });
979
+ this.lastRefreshError = error.withContext({ effects: void 0, operation: void 0, failureAt: void 0 });
980
+ let action;
981
+ try {
982
+ action = await this.opts.onRefreshFailure?.(error) ?? defaultOnRefreshFailure(error);
983
+ } catch (callbackError) {
984
+ throw sessionFailure(callbackError, {
985
+ operation: "TokenSession",
986
+ failureAt: "RefreshFailureCallback" /* RefreshFailureCallback */,
987
+ effects,
988
+ category: TorrowSdkErrorCategory4.Unknown,
989
+ reason: AuthSessionErrorReason2.CallbackFailed
990
+ });
731
991
  }
732
- await this.clearTokensUnlocked();
733
- return { outcome: "cleared" };
992
+ if (action === "throw") throw error;
993
+ if (action === "keep") return { outcome: "unchanged", error };
994
+ await this.clearTokensUnlocked(effects);
995
+ return { outcome: "cleared", error: error.withContext({ effects }) };
734
996
  }
735
997
  }
736
998
  async runExclusive(fn) {
@@ -746,7 +1008,7 @@ var TokenSession = class _TokenSession {
746
1008
  release();
747
1009
  }
748
1010
  }
749
- async ensureFreshImpl() {
1011
+ async ensureFreshImpl(effects = []) {
750
1012
  if (this.closed) return;
751
1013
  const skew = this.opts.accessTokenSkewMs ?? DEFAULT_SKEW_MS;
752
1014
  const access = this.tokenSnapshot.accessToken?.trim() ?? "";
@@ -756,7 +1018,7 @@ var TokenSession = class _TokenSession {
756
1018
  }
757
1019
  const refresh = this.tokenSnapshot.refreshToken?.trim() ?? "";
758
1020
  if (refresh) {
759
- await this.refreshWithFailurePolicy();
1021
+ await this.refreshWithFailurePolicy(effects);
760
1022
  }
761
1023
  }
762
1024
  isAccessStillFresh(access, expiresAtMs, accessTokenSkewMs) {
@@ -821,7 +1083,7 @@ var TokenSession = class _TokenSession {
821
1083
  refresh: async () => {
822
1084
  await this.runExclusive(async () => {
823
1085
  if (this.closed) {
824
- throw new Error(SESSION_CLOSED_MESSAGE);
1086
+ this.assertNotClosedForWrite();
825
1087
  }
826
1088
  const result = await this.refreshWithFailurePolicy();
827
1089
  if (result.outcome !== "refreshed") {
@@ -842,19 +1104,47 @@ var TokenSession = class _TokenSession {
842
1104
  getAccessToken: async () => this.getAccessToken()
843
1105
  });
844
1106
  }
845
- async persist(emitStatus) {
846
- await this.opts.tokenStorage.save(this.tokenSnapshot);
1107
+ async persist(emitStatus, effects = [], clear = false) {
1108
+ const effect = {
1109
+ area: TorrowSdkEffectArea.Storage,
1110
+ step: clear ? AuthSessionEffectStep.ClearStorage : AuthSessionEffectStep.SaveTokens,
1111
+ state: TorrowSdkEffectState.Unknown
1112
+ };
1113
+ effects.push(effect);
1114
+ try {
1115
+ await this.opts.tokenStorage.save(this.tokenSnapshot);
1116
+ } catch (error) {
1117
+ throw sessionFailure(error, {
1118
+ operation: "TokenSession",
1119
+ failureAt: clear ? "ClearStorage" /* ClearStorage */ : "SaveTokens" /* SaveTokens */,
1120
+ effects,
1121
+ category: TorrowSdkErrorCategory4.StorageFailure
1122
+ });
1123
+ }
1124
+ effects[effects.length - 1] = { ...effect, state: TorrowSdkEffectState.Applied };
847
1125
  if (emitStatus) this.emitStatusChange();
848
1126
  }
849
1127
  };
850
1128
 
851
1129
  // ../../src/auth-session/torrow-auth.ts
852
1130
  import {
853
- TorrowClient as TorrowClient3
1131
+ TorrowClient as TorrowClient3,
1132
+ TorrowSdkError as TorrowSdkError8,
1133
+ TorrowSdkErrorCategory as TorrowSdkErrorCategory8,
1134
+ TorrowSdkEffectArea as TorrowSdkEffectArea4,
1135
+ TorrowSdkEffectState as TorrowSdkEffectState4,
1136
+ AuthSessionEffectStep as AuthSessionEffectStep4
854
1137
  } from "@torrow/sdk-client";
855
1138
 
856
1139
  // ../../src/auth-session/link-login.ts
857
1140
  import {
1141
+ TorrowSdkError as TorrowSdkError5,
1142
+ TorrowSdkErrorCategory as TorrowSdkErrorCategory5,
1143
+ TorrowSdkRequestState as TorrowSdkRequestState3,
1144
+ TorrowSdkEffectArea as TorrowSdkEffectArea2,
1145
+ TorrowSdkEffectState as TorrowSdkEffectState2,
1146
+ AuthSessionEffectStep as AuthSessionEffectStep2,
1147
+ AuthSessionErrorReason as AuthSessionErrorReason3,
858
1148
  AccountTypeValues,
859
1149
  DefaultAuthScopes as DefaultAuthScopes3
860
1150
  } from "@torrow/sdk-client";
@@ -885,62 +1175,96 @@ var DEFAULT_POLL_TIMEOUT_SEC = 180;
885
1175
  var DEFAULT_LANGUAGE = "ru";
886
1176
  var DEFAULT_REGION = 70;
887
1177
  async function runLinkLogin(session, options) {
888
- const client = session.identityClient();
889
- const accountType = options.accountType ?? AccountTypeValues.Telegram;
890
- const body = {
891
- accountType,
892
- language: options.language?.trim() || DEFAULT_LANGUAGE,
893
- regionStorageId: typeof options.regionStorageId === "number" && Number.isFinite(options.regionStorageId) ? options.regionStorageId : DEFAULT_REGION,
894
- ...options.inviteSessionId?.trim() ? { inviteSessionId: options.inviteSessionId.trim() } : {},
895
- ...options.captcha?.trim() ? { captcha: options.captcha.trim() } : {},
896
- ...options.deviceInfo.deviceId?.trim() ? { deviceId: options.deviceInfo.deviceId.trim() } : {}
897
- };
898
- const createResp = await client.auth.createSession(body, { signal: options.signal });
899
- const sessionId = createResp.sessionId?.trim();
900
- const code = createResp.code?.trim();
901
- const authUrl = createResp.authUrl?.trim();
902
- if (!sessionId || !code || !authUrl) {
903
- throw new Error(
904
- "runLinkLogin: createSession response incomplete (need sessionId, code, authUrl)"
905
- );
906
- }
907
- await options.onLink(authUrl);
908
- const scopes = options.scopes && options.scopes.length > 0 ? options.scopes : [...DefaultAuthScopes3];
909
- const intervalMs = typeof options.pollIntervalMs === "number" && options.pollIntervalMs > 0 ? options.pollIntervalMs : DEFAULT_POLL_INTERVAL_MS;
910
- const timeoutSec = typeof options.pollTimeoutSec === "number" && options.pollTimeoutSec > 0 ? options.pollTimeoutSec : typeof createResp.sessionLifeTimeInSeconds === "number" && createResp.sessionLifeTimeInSeconds > 0 ? createResp.sessionLifeTimeInSeconds : DEFAULT_POLL_TIMEOUT_SEC;
911
- const deadline = Date.now() + timeoutSec * 1e3;
912
- for (; ; ) {
913
- options.signal?.throwIfAborted?.();
1178
+ const effects = [];
1179
+ let failureAt = "CreateAuthSession" /* CreateAuthSession */;
1180
+ try {
1181
+ throwIfOtpAborted(options.signal);
1182
+ const client = session.identityClient();
1183
+ const accountType = options.accountType ?? AccountTypeValues.Telegram;
1184
+ const body = {
1185
+ accountType,
1186
+ language: options.language?.trim() || DEFAULT_LANGUAGE,
1187
+ regionStorageId: typeof options.regionStorageId === "number" && Number.isFinite(options.regionStorageId) ? options.regionStorageId : DEFAULT_REGION,
1188
+ ...options.inviteSessionId?.trim() ? { inviteSessionId: options.inviteSessionId.trim() } : {},
1189
+ ...options.captcha?.trim() ? { captcha: options.captcha.trim() } : {},
1190
+ ...options.deviceInfo.deviceId?.trim() ? { deviceId: options.deviceInfo.deviceId.trim() } : {}
1191
+ };
1192
+ let createResp;
914
1193
  try {
915
- return await session.requestTokenWithCode({
916
- sessionId,
917
- code,
918
- scopes,
919
- signal: options.signal
1194
+ createResp = await client.auth.createSession(body, { signal: options.signal });
1195
+ } catch (error) {
1196
+ effects.push({
1197
+ area: TorrowSdkEffectArea2.Remote,
1198
+ step: AuthSessionEffectStep2.CreateAuthSession,
1199
+ state: error instanceof TorrowSdkError5 && error.request === TorrowSdkRequestState3.NotStarted ? TorrowSdkEffectState2.NotApplied : TorrowSdkEffectState2.Unknown
920
1200
  });
921
- } catch (e) {
922
- if (!isAuthSessionNotReadyYet(e)) throw e;
923
- if (Date.now() >= deadline) {
924
- throw new Error(
925
- `runLinkLogin: timeout ~${timeoutSec}s waiting for auth session to become ready`
926
- );
1201
+ throw error;
1202
+ }
1203
+ effects.push({ area: TorrowSdkEffectArea2.Remote, step: AuthSessionEffectStep2.CreateAuthSession, state: TorrowSdkEffectState2.Unknown });
1204
+ failureAt = "ValidateResponse" /* ValidateResponse */;
1205
+ const sessionId = createResp.sessionId?.trim();
1206
+ const code = createResp.code?.trim();
1207
+ const authUrl = createResp.authUrl?.trim();
1208
+ if (!sessionId || !code || !authUrl) {
1209
+ throw sessionFailure(void 0, { operation: "TorrowAuth.loginWithLink", failureAt, effects: [], category: TorrowSdkErrorCategory5.InvalidResponse });
1210
+ }
1211
+ effects[effects.length - 1] = { area: TorrowSdkEffectArea2.Remote, step: AuthSessionEffectStep2.CreateAuthSession, state: TorrowSdkEffectState2.Applied };
1212
+ failureAt = "OnLink" /* OnLink */;
1213
+ await options.onLink(authUrl);
1214
+ const scopes = options.scopes && options.scopes.length > 0 ? options.scopes : [...DefaultAuthScopes3];
1215
+ const intervalMs = typeof options.pollIntervalMs === "number" && options.pollIntervalMs > 0 ? options.pollIntervalMs : DEFAULT_POLL_INTERVAL_MS;
1216
+ const timeoutSec = typeof options.pollTimeoutSec === "number" && options.pollTimeoutSec > 0 ? options.pollTimeoutSec : typeof createResp.sessionLifeTimeInSeconds === "number" && createResp.sessionLifeTimeInSeconds > 0 ? createResp.sessionLifeTimeInSeconds : DEFAULT_POLL_TIMEOUT_SEC;
1217
+ const deadline = Date.now() + timeoutSec * 1e3;
1218
+ failureAt = "PollTokens" /* PollTokens */;
1219
+ for (; ; ) {
1220
+ throwIfOtpAborted(options.signal);
1221
+ try {
1222
+ return await session.requestTokenWithCode({
1223
+ sessionId,
1224
+ code,
1225
+ scopes,
1226
+ signal: options.signal
1227
+ });
1228
+ } catch (e) {
1229
+ if (!isAuthSessionNotReadyYet(e)) throw e;
1230
+ if (Date.now() >= deadline) {
1231
+ throw sessionFailure(e, { operation: "TorrowAuth.loginWithLink", failureAt, effects: [], category: TorrowSdkErrorCategory5.InvalidState, reason: AuthSessionErrorReason3.SessionNotReady });
1232
+ }
1233
+ await sleepMs(intervalMs, options.signal);
927
1234
  }
928
- await sleepMs(intervalMs, options.signal);
929
1235
  }
1236
+ } catch (cause) {
1237
+ const error = options.signal?.aborted && !(cause instanceof TorrowSdkError5) ? new TorrowSdkError5({ category: TorrowSdkErrorCategory5.Cancelled, sensitive: { cause } }) : cause;
1238
+ const callback = failureAt === "OnLink" /* OnLink */;
1239
+ throw sessionFailure(error, {
1240
+ operation: "TorrowAuth.loginWithLink",
1241
+ failureAt,
1242
+ effects: [...effects, ...failureAt === "PollTokens" /* PollTokens */ && error instanceof TorrowSdkError5 ? error.effects ?? [] : []],
1243
+ ...callback ? { category: TorrowSdkErrorCategory5.Unknown, reason: AuthSessionErrorReason3.CallbackFailed } : {}
1244
+ });
930
1245
  }
931
1246
  }
932
1247
 
933
1248
  // ../../src/auth-session/otp-login.ts
934
1249
  import {
1250
+ AuthSessionEffectStep as AuthSessionEffectStep3,
1251
+ AuthSessionErrorReason as AuthSessionErrorReason4,
1252
+ TorrowSdkEffectArea as TorrowSdkEffectArea3,
1253
+ TorrowSdkEffectState as TorrowSdkEffectState3,
1254
+ TorrowSdkErrorCategory as TorrowSdkErrorCategory7,
1255
+ TorrowSdkRequestState as TorrowSdkRequestState4,
935
1256
  DefaultAuthScopes as DefaultAuthScopes5,
936
1257
  SendCodeAccountTypeValues,
937
- TorrowHttpError as TorrowHttpError3,
1258
+ TorrowSdkError as TorrowSdkError7,
938
1259
  VerificationMethodValues as VerificationMethodValues2,
939
1260
  TokenStatusValues as TokenStatusValues5
940
1261
  } from "@torrow/sdk-client";
941
1262
 
942
1263
  // ../../src/auth-session/mobile-id.ts
943
1264
  import {
1265
+ TorrowSdkError as TorrowSdkError6,
1266
+ TorrowSdkErrorCategory as TorrowSdkErrorCategory6,
1267
+ TorrowSdkErrorDomain as TorrowSdkErrorDomain4,
944
1268
  DefaultAuthScopes as DefaultAuthScopes4,
945
1269
  TokenStatusValues as TokenStatusValues4
946
1270
  } from "@torrow/sdk-client";
@@ -952,7 +1276,7 @@ function scopesOrDefault(scopes) {
952
1276
  async function pollMobileIdOnce(client, args) {
953
1277
  const sessionId = args.sessionId.trim();
954
1278
  if (!sessionId) {
955
- throw new Error("pollMobileIdOnce: sessionId is empty");
1279
+ throw otpFailure(TorrowSdkErrorCategory6.InvalidInput);
956
1280
  }
957
1281
  const body = {
958
1282
  sessionId,
@@ -961,21 +1285,24 @@ async function pollMobileIdOnce(client, args) {
961
1285
  scopes: scopesOrDefault(args.scopes),
962
1286
  deviceInfo: args.deviceInfo
963
1287
  };
964
- return normalizeTokenResp(await requestAuthToken(client, body, args.signal));
1288
+ const raw = await requestAuthToken(client, body, args.signal);
1289
+ try {
1290
+ return normalizeTokenResp(raw);
1291
+ } catch (cause) {
1292
+ throw otpFailure(TorrowSdkErrorCategory6.InvalidResponse, void 0, cause);
1293
+ }
965
1294
  }
966
1295
  async function pollMobileIdUntilReady(client, options) {
967
1296
  const sessionId = options.sessionId.trim();
968
1297
  if (!sessionId) {
969
- throw new Error("pollMobileIdUntilReady: sessionId is empty");
1298
+ throw otpFailure(TorrowSdkErrorCategory6.InvalidInput);
970
1299
  }
971
1300
  const intervalSec = typeof options.pollIntervalSeconds === "number" && options.pollIntervalSeconds > 0 ? options.pollIntervalSeconds : DEFAULT_POLL_INTERVAL_SEC;
972
1301
  const maxWaitSec = typeof options.maxWaitSeconds === "number" && options.maxWaitSeconds > 0 ? options.maxWaitSeconds : DEFAULT_MAX_WAIT_SEC;
973
1302
  const deadline = Date.now() + maxWaitSec * 1e3;
974
1303
  const scopes = scopesOrDefault(options.scopes);
975
1304
  for (; ; ) {
976
- if (options.signal?.aborted) {
977
- throw options.signal.reason ?? new Error("Aborted");
978
- }
1305
+ throwIfOtpAborted(options.signal);
979
1306
  const resp = await pollMobileIdOnce(client, {
980
1307
  sessionId,
981
1308
  deviceInfo: options.deviceInfo,
@@ -984,25 +1311,21 @@ async function pollMobileIdUntilReady(client, options) {
984
1311
  });
985
1312
  const status = resp.status;
986
1313
  if (tokenTransition(status, true) === TokenStatusValues4.TokenReady) {
987
- return assertTokenRespReadyForStorage(resp, "pollMobileIdUntilReady");
1314
+ return readyTokens(resp);
988
1315
  }
989
1316
  if (status === TokenStatusValues4.Failed) {
990
- throw new Error(
991
- "pollMobileIdUntilReady: TokenResp.status=Failed \u2014 \u043D\u0443\u0436\u0435\u043D \u043D\u043E\u0432\u044B\u0439 sendCode."
992
- );
1317
+ throw mobileInvalidState();
993
1318
  }
994
1319
  if (status === TokenStatusValues4.AwaitingSmsCode) {
995
1320
  const obtain = options.obtainSmsCode;
996
1321
  if (!obtain) {
997
- throw new Error(
998
- "pollMobileIdUntilReady: AwaitingSmsCode, \u043D\u043E obtainSmsCode \u043D\u0435 \u0437\u0430\u0434\u0430\u043D."
999
- );
1322
+ throw otpFailure(TorrowSdkErrorCategory6.InvalidInput);
1000
1323
  }
1001
1324
  const code = (await obtain({
1002
1325
  codeLength: options.smsCodeLength
1003
1326
  })).trim();
1004
1327
  if (!code) {
1005
- throw new Error("pollMobileIdUntilReady: \u043F\u0443\u0441\u0442\u043E\u0439 SMS-\u043A\u043E\u0434 \u043F\u043E\u0441\u043B\u0435 AwaitingSmsCode.");
1328
+ throw otpFailure(TorrowSdkErrorCategory6.InvalidInput);
1006
1329
  }
1007
1330
  const afterSms = await pollMobileIdOnce(client, {
1008
1331
  sessionId,
@@ -1012,12 +1335,10 @@ async function pollMobileIdUntilReady(client, options) {
1012
1335
  signal: options.signal
1013
1336
  });
1014
1337
  if (tokenTransition(afterSms.status, true) === TokenStatusValues4.TokenReady) {
1015
- return assertTokenRespReadyForStorage(afterSms, "pollMobileIdUntilReady (SMS)");
1338
+ return readyTokens(afterSms);
1016
1339
  }
1017
1340
  if (afterSms.status === TokenStatusValues4.Failed) {
1018
- throw new Error(
1019
- "pollMobileIdUntilReady: TokenResp.status=Failed \u043F\u043E\u0441\u043B\u0435 SMS \u2014 \u043D\u0443\u0436\u0435\u043D \u043D\u043E\u0432\u044B\u0439 sendCode."
1020
- );
1341
+ throw mobileInvalidState();
1021
1342
  }
1022
1343
  if (afterSms.status === TokenStatusValues4.Pending) {
1023
1344
  continue;
@@ -1025,86 +1346,33 @@ async function pollMobileIdUntilReady(client, options) {
1025
1346
  if (afterSms.status === TokenStatusValues4.AwaitingSmsCode) {
1026
1347
  continue;
1027
1348
  }
1028
- throw new Error(
1029
- `pollMobileIdUntilReady: \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 TokenResp.status=${String(afterSms.status)} \u043F\u043E\u0441\u043B\u0435 SMS.`
1030
- );
1349
+ throw otpFailure(TorrowSdkErrorCategory6.InvalidResponse);
1031
1350
  }
1032
1351
  if (status !== TokenStatusValues4.Pending) {
1033
- throw new Error(
1034
- `pollMobileIdUntilReady: \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 TokenResp.status=${String(status)}.`
1035
- );
1352
+ throw otpFailure(TorrowSdkErrorCategory6.InvalidResponse);
1036
1353
  }
1037
1354
  if (Date.now() >= deadline) {
1038
- throw new Error(
1039
- `pollMobileIdUntilReady: timeout ~${maxWaitSec}\u0441 (poll \u043A\u0430\u0436\u0434\u044B\u0435 ${intervalSec}\u0441) \u2014 \u0441\u0442\u0430\u0442\u0443\u0441 Pending.`
1040
- );
1355
+ throw mobileInvalidState();
1041
1356
  }
1042
1357
  await sleepMs(intervalSec * 1e3, options.signal);
1043
1358
  }
1044
1359
  }
1045
-
1046
- // ../../src/auth-session/otp-login.ts
1047
- var MOBILE_ID_SMS_CODE_LENGTH = 4;
1048
- var OtpCodeRequestLimitExceededError = class extends Error {
1049
- name = "OtpCodeRequestLimitExceededError";
1050
- minutes;
1051
- seconds;
1052
- traceUrl;
1053
- apiMessage;
1054
- body;
1055
- constructor(body) {
1056
- const parsed = parseRateLimitBody(body);
1057
- const wait = parsed.min || parsed.sec ? `\u041F\u043E\u0432\u0442\u043E\u0440 \u043F\u0440\u0438\u043C\u0435\u0440\u043D\u043E \u0447\u0435\u0440\u0435\u0437 ${[parsed.min && `${parsed.min} \u043C\u0438\u043D.`, parsed.sec && `${parsed.sec} \u0441\u0435\u043A.`].filter(Boolean).join(" ")}.` : "\u0422\u043E\u0447\u043D\u043E\u0435 \u0432\u0440\u0435\u043C\u044F \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F \u0441\u0435\u0440\u0432\u0435\u0440 \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043B.";
1058
- super(`sendCode: CODE_REQUEST_LIMIT_EXCEEDED. ${wait}`);
1059
- this.minutes = parsed.min;
1060
- this.seconds = parsed.sec;
1061
- this.traceUrl = parsed.trace;
1062
- this.apiMessage = parsed.message;
1063
- this.body = body;
1064
- }
1065
- };
1066
- var OtpAuthHeaderMissingError = class extends Error {
1067
- name = "OtpAuthHeaderMissingError";
1068
- body;
1069
- systemMessage;
1070
- constructor(body) {
1071
- const sys = body !== null && typeof body === "object" && typeof body.systemMessage === "string" ? body.systemMessage.trim() : void 0;
1072
- super(
1073
- [
1074
- "requestToken: AUTH_HEADER_MISSING \u2014 \u0437\u0430\u0434\u0430\u0439 appCredentials: { clientId, clientSecret }.",
1075
- sys ? `\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0430: ${sys}` : ""
1076
- ].filter(Boolean).join(" ")
1077
- );
1078
- this.body = body;
1079
- this.systemMessage = sys;
1360
+ function readyTokens(response) {
1361
+ try {
1362
+ return assertTokenRespReadyForStorage(response, "pollMobileIdUntilReady");
1363
+ } catch (cause) {
1364
+ throw otpFailure(TorrowSdkErrorCategory6.InvalidResponse, void 0, cause);
1080
1365
  }
1081
- };
1082
- function parseRateLimitBody(body) {
1083
- if (body === null || typeof body !== "object") return {};
1084
- const o = body;
1085
- const p = o.parameters ?? {};
1086
- return {
1087
- min: p.minutes != null && String(p.minutes) !== "" ? String(p.minutes) : void 0,
1088
- sec: p.seconds != null && String(p.seconds) !== "" ? String(p.seconds) : void 0,
1089
- message: typeof o.message === "string" && o.message.trim() ? o.message.trim() : void 0,
1090
- trace: typeof o.traceUrl === "string" && o.traceUrl.trim() ? o.traceUrl.trim() : void 0
1091
- };
1092
1366
  }
1093
- function isSendCodeRateLimitedBody(body) {
1094
- if (body === null || typeof body !== "object") return false;
1095
- return body.name === "CODE_REQUEST_LIMIT_EXCEEDED";
1096
- }
1097
- function isAuthHeaderMissingBody(body) {
1098
- if (body === null || typeof body !== "object") return false;
1099
- return body.name === "AUTH_HEADER_MISSING";
1100
- }
1101
- function httpErrorBody(e) {
1102
- if (e instanceof TorrowHttpError3) return e.body;
1103
- if (e !== null && typeof e === "object" && "body" in e) {
1104
- return e.body;
1105
- }
1106
- return void 0;
1367
+ function mobileInvalidState() {
1368
+ return new TorrowSdkError6({
1369
+ category: TorrowSdkErrorCategory6.InvalidState,
1370
+ detail: { domain: TorrowSdkErrorDomain4.AuthSession }
1371
+ });
1107
1372
  }
1373
+
1374
+ // ../../src/auth-session/otp-login.ts
1375
+ var MOBILE_ID_SMS_CODE_LENGTH = 4;
1108
1376
  function scopesOrDefault2(scopes) {
1109
1377
  return scopes && scopes.length > 0 ? scopes : [...DefaultAuthScopes5];
1110
1378
  }
@@ -1113,100 +1381,141 @@ function effectiveCodeLength(raw, fallback) {
1113
1381
  return fallback > 0 ? fallback : void 0;
1114
1382
  }
1115
1383
  async function requestTokenOrThrow(client, body, signal) {
1384
+ let raw;
1116
1385
  try {
1117
- return normalizeTokenResp(await requestAuthToken(client, body, signal));
1118
- } catch (e) {
1119
- const errBody = httpErrorBody(e);
1120
- if (errBody !== void 0 && isAuthHeaderMissingBody(errBody)) {
1121
- throw new OtpAuthHeaderMissingError(errBody);
1122
- }
1123
- throw e;
1386
+ raw = await requestAuthToken(client, body, signal);
1387
+ } catch (error) {
1388
+ throw otpHttpFailure(error);
1124
1389
  }
1125
- }
1126
- async function runOtpLogin(client, options) {
1127
- const accountName = options.accountName.trim();
1128
- const accountType = options.type;
1129
- const scopes = scopesOrDefault2(options.scopes);
1130
- const signal = options.signal;
1131
- let sendResp;
1132
1390
  try {
1133
- sendResp = await sendOtpCode(client, options, signal);
1134
- } catch (e) {
1135
- const errBody = httpErrorBody(e);
1136
- if (errBody !== void 0 && isSendCodeRateLimitedBody(errBody)) {
1137
- throw new OtpCodeRequestLimitExceededError(errBody);
1138
- }
1139
- throw e;
1391
+ return normalizeTokenResp(raw);
1392
+ } catch (cause) {
1393
+ throw otpFailure(TorrowSdkErrorCategory7.InvalidResponse, void 0, cause);
1140
1394
  }
1141
- const sessionId = sendResp.sessionId?.trim();
1142
- if (!sessionId) {
1143
- throw new Error("runOtpLogin: sendCode response had no sessionId");
1144
- }
1145
- const result = {
1146
- accountName,
1147
- accountType,
1148
- sessionId,
1149
- usedMethod: sendResp.usedMethod,
1150
- codeLength: sendResp.codeLength,
1151
- pollIntervalSeconds: sendResp.pollIntervalSeconds,
1152
- availableMethods: sendResp.availableMethods,
1153
- sessionLifeTimeInSeconds: sendResp.sessionLifeTimeInSeconds,
1154
- nextAvailableUsageTime: sendResp.nextAvailableUsageTime ?? null
1155
- };
1156
- await options.onSendCode(result);
1157
- if (sendResp.usedMethod === VerificationMethodValues2.MtsMobileId) {
1395
+ }
1396
+ async function runOtpLoginWithEffects(client, options) {
1397
+ const effects = [];
1398
+ let failureAt = "SendCode" /* SendCode */;
1399
+ try {
1400
+ throwIfOtpAborted(options.signal);
1401
+ const accountName = options.accountName.trim();
1402
+ const accountType = options.type;
1403
+ const scopes = scopesOrDefault2(options.scopes);
1404
+ const signal = options.signal;
1405
+ let sendResp;
1158
1406
  try {
1159
- return await pollMobileIdUntilReady(client, {
1160
- sessionId,
1161
- deviceInfo: options.deviceInfo,
1162
- scopes,
1163
- pollIntervalSeconds: sendResp.pollIntervalSeconds,
1164
- maxWaitSeconds: sendResp.sessionLifeTimeInSeconds,
1165
- signal,
1166
- smsCodeLength: typeof sendResp.codeLength === "number" && sendResp.codeLength > 0 ? sendResp.codeLength : MOBILE_ID_SMS_CODE_LENGTH,
1167
- obtainSmsCode: async (ctx) => {
1168
- const codeLength2 = effectiveCodeLength(ctx.codeLength, MOBILE_ID_SMS_CODE_LENGTH);
1169
- return (await options.getCode({
1170
- sessionId,
1171
- codeLength: codeLength2,
1172
- usedMethod: sendResp.usedMethod
1173
- })).trim();
1174
- }
1407
+ sendResp = await sendOtpCode(client, options, signal);
1408
+ } catch (error) {
1409
+ effects.push({
1410
+ area: TorrowSdkEffectArea3.Remote,
1411
+ step: AuthSessionEffectStep3.SendCode,
1412
+ state: error instanceof TorrowSdkError7 && error.request === TorrowSdkRequestState4.NotStarted ? TorrowSdkEffectState3.NotApplied : TorrowSdkEffectState3.Unknown
1175
1413
  });
1176
- } catch (e) {
1177
- const errBody = httpErrorBody(e);
1178
- if (errBody !== void 0 && isAuthHeaderMissingBody(errBody)) {
1179
- throw new OtpAuthHeaderMissingError(errBody);
1414
+ throw otpHttpFailure(error);
1415
+ }
1416
+ effects.push({ area: TorrowSdkEffectArea3.Remote, step: AuthSessionEffectStep3.SendCode, state: TorrowSdkEffectState3.Unknown });
1417
+ failureAt = "ValidateResponse" /* ValidateResponse */;
1418
+ const sessionId = sendResp.sessionId?.trim();
1419
+ if (!sessionId) {
1420
+ throw otpFailure(TorrowSdkErrorCategory7.InvalidResponse);
1421
+ }
1422
+ effects[effects.length - 1] = { area: TorrowSdkEffectArea3.Remote, step: AuthSessionEffectStep3.SendCode, state: TorrowSdkEffectState3.Applied };
1423
+ const result = {
1424
+ accountName,
1425
+ accountType,
1426
+ sessionId,
1427
+ usedMethod: sendResp.usedMethod,
1428
+ codeLength: sendResp.codeLength,
1429
+ pollIntervalSeconds: sendResp.pollIntervalSeconds,
1430
+ availableMethods: sendResp.availableMethods,
1431
+ sessionLifeTimeInSeconds: sendResp.sessionLifeTimeInSeconds,
1432
+ nextAvailableUsageTime: sendResp.nextAvailableUsageTime ?? null
1433
+ };
1434
+ failureAt = "OnSendCode" /* OnSendCode */;
1435
+ await options.onSendCode(result);
1436
+ if (sendResp.usedMethod === VerificationMethodValues2.MtsMobileId) {
1437
+ try {
1438
+ failureAt = "PollTokens" /* PollTokens */;
1439
+ const tokens = await pollMobileIdUntilReady(client, {
1440
+ sessionId,
1441
+ deviceInfo: options.deviceInfo,
1442
+ scopes,
1443
+ pollIntervalSeconds: sendResp.pollIntervalSeconds,
1444
+ maxWaitSeconds: sendResp.sessionLifeTimeInSeconds,
1445
+ signal,
1446
+ smsCodeLength: typeof sendResp.codeLength === "number" && sendResp.codeLength > 0 ? sendResp.codeLength : MOBILE_ID_SMS_CODE_LENGTH,
1447
+ obtainSmsCode: async (ctx) => {
1448
+ const codeLength2 = effectiveCodeLength(ctx.codeLength, MOBILE_ID_SMS_CODE_LENGTH);
1449
+ failureAt = "GetCode" /* GetCode */;
1450
+ const code2 = (await options.getCode({
1451
+ sessionId,
1452
+ codeLength: codeLength2,
1453
+ usedMethod: sendResp.usedMethod
1454
+ })).trim();
1455
+ failureAt = "PollTokens" /* PollTokens */;
1456
+ return code2;
1457
+ }
1458
+ });
1459
+ effects.push({ area: TorrowSdkEffectArea3.Remote, step: AuthSessionEffectStep3.IssueTokens, state: TorrowSdkEffectState3.Applied });
1460
+ return { tokens, effects };
1461
+ } catch (error) {
1462
+ throw otpHttpFailure(error);
1180
1463
  }
1181
- throw e;
1182
1464
  }
1183
- }
1184
- const classicLength = effectiveCodeLength(
1185
- sendResp.codeLength,
1186
- accountType === SendCodeAccountTypeValues.Email ? 6 : 4
1187
- );
1188
- const code = (await options.getCode({
1189
- sessionId,
1190
- codeLength: classicLength,
1191
- usedMethod: sendResp.usedMethod
1192
- })).trim();
1193
- if (!code) {
1194
- throw new Error("runOtpLogin: empty OTP code");
1195
- }
1196
- const resp = await requestTokenOrThrow(
1197
- client,
1198
- {
1465
+ const classicLength = effectiveCodeLength(
1466
+ sendResp.codeLength,
1467
+ accountType === SendCodeAccountTypeValues.Email ? 6 : 4
1468
+ );
1469
+ failureAt = "GetCode" /* GetCode */;
1470
+ const code = (await options.getCode({
1199
1471
  sessionId,
1200
- code,
1201
- scopes,
1202
- deviceInfo: options.deviceInfo
1203
- },
1204
- signal
1205
- );
1206
- if (tokenTransition(resp.status, true) !== TokenStatusValues5.TokenReady) {
1207
- throw new Error(`runOtpLogin: TokenResp.status=${resp.status} (\u043E\u0436\u0438\u0434\u0430\u043B\u0441\u044F TokenReady)`);
1472
+ codeLength: classicLength,
1473
+ usedMethod: sendResp.usedMethod
1474
+ })).trim();
1475
+ failureAt = "ValidateInput" /* ValidateInput */;
1476
+ if (!code) {
1477
+ throw otpFailure(TorrowSdkErrorCategory7.InvalidInput);
1478
+ }
1479
+ failureAt = "IssueTokens" /* IssueTokens */;
1480
+ const resp = await requestTokenOrThrow(
1481
+ client,
1482
+ {
1483
+ sessionId,
1484
+ code,
1485
+ scopes,
1486
+ deviceInfo: options.deviceInfo
1487
+ },
1488
+ signal
1489
+ );
1490
+ if (tokenTransition(resp.status, true) !== TokenStatusValues5.TokenReady) {
1491
+ throw otpFailure(TorrowSdkErrorCategory7.InvalidResponse);
1492
+ }
1493
+ let ready;
1494
+ try {
1495
+ ready = assertTokenRespReadyForStorage(resp, "runOtpLogin");
1496
+ } catch (cause) {
1497
+ throw otpFailure(TorrowSdkErrorCategory7.InvalidResponse, void 0, cause);
1498
+ }
1499
+ effects.push({ area: TorrowSdkEffectArea3.Remote, step: AuthSessionEffectStep3.IssueTokens, state: TorrowSdkEffectState3.Applied });
1500
+ return { tokens: ready, effects };
1501
+ } catch (cause) {
1502
+ const error = options.signal?.aborted && !(cause instanceof TorrowSdkError7) ? new TorrowSdkError7({ category: TorrowSdkErrorCategory7.Cancelled, sensitive: { cause } }) : cause;
1503
+ const tokenStages = ["IssueTokens" /* IssueTokens */, "PollTokens" /* PollTokens */];
1504
+ if (tokenStages.includes(failureAt)) {
1505
+ effects.push({
1506
+ area: TorrowSdkEffectArea3.Remote,
1507
+ step: AuthSessionEffectStep3.IssueTokens,
1508
+ state: error instanceof TorrowSdkError7 && error.request === TorrowSdkRequestState4.NotStarted ? TorrowSdkEffectState3.NotApplied : TorrowSdkEffectState3.Unknown
1509
+ });
1510
+ }
1511
+ const callback = failureAt === "GetCode" /* GetCode */ || failureAt === "OnSendCode" /* OnSendCode */;
1512
+ throw sessionFailure(error, {
1513
+ operation: "TorrowAuth.loginWithOtp",
1514
+ failureAt,
1515
+ effects,
1516
+ ...callback && !options.signal?.aborted ? { category: TorrowSdkErrorCategory7.Unknown, reason: AuthSessionErrorReason4.CallbackFailed } : {}
1517
+ });
1208
1518
  }
1209
- return assertTokenRespReadyForStorage(resp, "runOtpLogin");
1210
1519
  }
1211
1520
 
1212
1521
  // ../../src/auth-session/torrow-auth.ts
@@ -1227,72 +1536,157 @@ function sessionOpenOpts(base) {
1227
1536
  function sessionOpenOptsForLogin(base) {
1228
1537
  return { ...sessionOpenOpts(base), skipEnsureFreshOnOpen: true };
1229
1538
  }
1539
+ function loginFailureStage(error, current) {
1540
+ return error instanceof TorrowSdkError8 ? Object.values(SessionFailureStage).find((stage) => stage === error.failureAt) ?? current : current;
1541
+ }
1230
1542
  var TorrowAuth = {
1231
1543
  /** Пошаговый OTP без storage, polling и refresh. */
1232
1544
  createOtpFlow,
1545
+ /** Загрузить сессию; общий отказ сохраняет текущую диагностику open/refresh. */
1233
1546
  async connect(options) {
1234
- const session = await TokenSession.open(sessionOpenOpts(options));
1235
- return { client: session.client, session };
1547
+ try {
1548
+ const session = await TokenSession.open(sessionOpenOpts(options));
1549
+ return { client: session.client, session };
1550
+ } catch (error) {
1551
+ throw sessionFailure(error, {
1552
+ operation: "TorrowAuth.connect",
1553
+ failureAt: "OpenSession" /* OpenSession */,
1554
+ effects: error instanceof TorrowSdkError8 ? error.effects ?? [] : []
1555
+ });
1556
+ }
1236
1557
  },
1237
1558
  async loginWithOtp(options) {
1238
- const accountName = options.account.accountName.trim();
1239
- if (!accountName) {
1240
- throw new Error("TorrowAuth.loginWithOtp: account.accountName is empty");
1559
+ const effects = [];
1560
+ let failureAt = "ValidateInput" /* ValidateInput */;
1561
+ try {
1562
+ const accountName = options.account.accountName.trim();
1563
+ if (!accountName) throw sessionFailure(void 0, {
1564
+ operation: "TorrowAuth.loginWithOtp",
1565
+ failureAt,
1566
+ effects,
1567
+ category: TorrowSdkErrorCategory8.InvalidInput
1568
+ });
1569
+ failureAt = "OpenSession" /* OpenSession */;
1570
+ const opened = await TokenSession.openWithEffects(sessionOpenOptsForLogin(options));
1571
+ const session = opened.session;
1572
+ effects.push(...opened.effects);
1573
+ const deviceInfo = session.getDeviceInfo();
1574
+ failureAt = "SendCode" /* SendCode */;
1575
+ const ready = await runOtpLoginWithEffects(session.identityClient(), {
1576
+ accountName,
1577
+ type: options.account.accountType,
1578
+ deviceInfo,
1579
+ onSendCode: options.onSendCode,
1580
+ getCode: options.getCode,
1581
+ scopes: options.scopes,
1582
+ language: options.language,
1583
+ regionStorageId: options.regionStorageId,
1584
+ captcha: options.captcha,
1585
+ captchaType: options.captchaType,
1586
+ inviteSessionId: options.inviteSessionId,
1587
+ preferredMethod: options.preferredMethod,
1588
+ supportedMethods: options.supportedMethods,
1589
+ signal: options.signal
1590
+ });
1591
+ effects.push(...ready.effects);
1592
+ failureAt = "ApplyTokens" /* ApplyTokens */;
1593
+ await session.applyTokenResp(ready.tokens, { replace: true });
1594
+ return { client: session.client, session };
1595
+ } catch (error) {
1596
+ throw sessionFailure(error, {
1597
+ operation: "TorrowAuth.loginWithOtp",
1598
+ failureAt: loginFailureStage(error, failureAt),
1599
+ effects: [...effects, ...error instanceof TorrowSdkError8 ? error.effects ?? [] : []]
1600
+ });
1241
1601
  }
1242
- const session = await TokenSession.open(sessionOpenOptsForLogin(options));
1243
- const deviceInfo = session.getDeviceInfo();
1244
- const ready = await runOtpLogin(session.identityClient(), {
1245
- accountName,
1246
- type: options.account.accountType,
1247
- deviceInfo,
1248
- onSendCode: options.onSendCode,
1249
- getCode: options.getCode,
1250
- scopes: options.scopes,
1251
- language: options.language,
1252
- regionStorageId: options.regionStorageId,
1253
- captcha: options.captcha,
1254
- captchaType: options.captchaType,
1255
- inviteSessionId: options.inviteSessionId,
1256
- preferredMethod: options.preferredMethod,
1257
- supportedMethods: options.supportedMethods,
1258
- signal: options.signal
1259
- });
1260
- await session.applyTokenResp(ready, { replace: true });
1261
- return { client: session.client, session };
1262
1602
  },
1263
1603
  async loginWithLink(options) {
1264
- const session = await TokenSession.open(sessionOpenOptsForLogin(options));
1265
- await runLinkLogin(session, {
1266
- deviceInfo: session.getDeviceInfo(),
1267
- onLink: options.onLink,
1268
- accountType: options.accountType,
1269
- language: options.language,
1270
- regionStorageId: options.regionStorageId,
1271
- captcha: options.captcha,
1272
- inviteSessionId: options.inviteSessionId,
1273
- scopes: options.scopes,
1274
- pollIntervalMs: options.pollIntervalMs,
1275
- pollTimeoutSec: options.pollTimeoutSec,
1276
- signal: options.signal
1277
- });
1278
- return { client: session.client, session };
1604
+ const effects = [];
1605
+ let failureAt = "OpenSession" /* OpenSession */;
1606
+ try {
1607
+ const opened = await TokenSession.openWithEffects(sessionOpenOptsForLogin(options));
1608
+ const session = opened.session;
1609
+ effects.push(...opened.effects);
1610
+ failureAt = "CreateAuthSession" /* CreateAuthSession */;
1611
+ await runLinkLogin(session, {
1612
+ deviceInfo: session.getDeviceInfo(),
1613
+ onLink: options.onLink,
1614
+ accountType: options.accountType,
1615
+ language: options.language,
1616
+ regionStorageId: options.regionStorageId,
1617
+ captcha: options.captcha,
1618
+ inviteSessionId: options.inviteSessionId,
1619
+ scopes: options.scopes,
1620
+ pollIntervalMs: options.pollIntervalMs,
1621
+ pollTimeoutSec: options.pollTimeoutSec,
1622
+ signal: options.signal
1623
+ });
1624
+ return { client: session.client, session };
1625
+ } catch (error) {
1626
+ throw sessionFailure(error, {
1627
+ operation: "TorrowAuth.loginWithLink",
1628
+ failureAt: loginFailureStage(error, failureAt),
1629
+ effects: [...effects, ...error instanceof TorrowSdkError8 ? error.effects ?? [] : []]
1630
+ });
1631
+ }
1279
1632
  },
1633
+ /** Сохранить host tokens, затем connect; последующий отказ не отменяет Applied сохранение. */
1280
1634
  async fromTokens(options) {
1281
- const accessToken = options.accessToken.trim();
1282
- if (!accessToken) {
1283
- throw new Error("TorrowAuth.fromTokens: accessToken is empty");
1635
+ const effects = [];
1636
+ let failureAt = "ValidateInput" /* ValidateInput */;
1637
+ try {
1638
+ const accessToken = options.accessToken.trim();
1639
+ if (!accessToken) {
1640
+ throw sessionFailure(void 0, {
1641
+ operation: "TorrowAuth.fromTokens",
1642
+ failureAt,
1643
+ effects,
1644
+ category: TorrowSdkErrorCategory8.InvalidInput
1645
+ });
1646
+ }
1647
+ const refreshToken = options.refreshToken?.trim() || void 0;
1648
+ failureAt = "LoadStorage" /* LoadStorage */;
1649
+ let prev;
1650
+ try {
1651
+ prev = await options.tokenStorage.load();
1652
+ } catch (error) {
1653
+ throw sessionFailure(error, {
1654
+ operation: "TorrowAuth.fromTokens",
1655
+ failureAt,
1656
+ effects,
1657
+ category: TorrowSdkErrorCategory8.StorageFailure
1658
+ });
1659
+ }
1660
+ const explicitTtl = options.accessExpiresAtMs;
1661
+ const accessExpiresAtMs = typeof explicitTtl === "number" && Number.isFinite(explicitTtl) && explicitTtl > 0 ? explicitTtl : accessExpiresAtMsFromJwt(accessToken);
1662
+ failureAt = "SeedStorage" /* SeedStorage */;
1663
+ effects.push({ area: TorrowSdkEffectArea4.Storage, step: AuthSessionEffectStep4.SaveTokens, state: TorrowSdkEffectState4.Unknown });
1664
+ try {
1665
+ await options.tokenStorage.save({
1666
+ accessToken,
1667
+ refreshToken,
1668
+ accessExpiresAtMs: typeof accessExpiresAtMs === "number" && accessExpiresAtMs > 0 ? accessExpiresAtMs : void 0,
1669
+ deviceId: options.deviceInfo.deviceId?.trim() || prev.deviceId?.trim() || void 0
1670
+ });
1671
+ } catch (error) {
1672
+ throw sessionFailure(error, {
1673
+ operation: "TorrowAuth.fromTokens",
1674
+ failureAt,
1675
+ effects,
1676
+ category: TorrowSdkErrorCategory8.StorageFailure
1677
+ });
1678
+ }
1679
+ effects[0] = { ...effects[0], state: TorrowSdkEffectState4.Applied };
1680
+ failureAt = "Connect" /* Connect */;
1681
+ try {
1682
+ return await TorrowAuth.connect(options);
1683
+ } catch (error) {
1684
+ if (error instanceof TorrowSdkError8) effects.push(...error.effects ?? []);
1685
+ throw error;
1686
+ }
1687
+ } catch (error) {
1688
+ throw sessionFailure(error, { operation: "TorrowAuth.fromTokens", failureAt, effects });
1284
1689
  }
1285
- const refreshToken = options.refreshToken?.trim() || void 0;
1286
- const prev = await options.tokenStorage.load();
1287
- const explicitTtl = options.accessExpiresAtMs;
1288
- const accessExpiresAtMs = typeof explicitTtl === "number" && Number.isFinite(explicitTtl) && explicitTtl > 0 ? explicitTtl : accessExpiresAtMsFromJwt(accessToken);
1289
- await options.tokenStorage.save({
1290
- accessToken,
1291
- refreshToken,
1292
- ...typeof accessExpiresAtMs === "number" && accessExpiresAtMs > 0 ? { accessExpiresAtMs } : { accessExpiresAtMs: void 0 },
1293
- deviceId: options.deviceInfo.deviceId?.trim() || prev.deviceId?.trim() || void 0
1294
- });
1295
- return TorrowAuth.connect(options);
1296
1690
  },
1297
1691
  anonymous(config = {}) {
1298
1692
  return new TorrowClient3({
@@ -1302,14 +1696,9 @@ var TorrowAuth = {
1302
1696
  }
1303
1697
  };
1304
1698
  export {
1305
- AuthSessionNotReadyError,
1306
1699
  DEFAULT_APP_CREDENTIALS,
1307
1700
  LocalStorageTokenStorage,
1308
1701
  MemoryTokenStorage,
1309
- OtpAuthHeaderMissingError,
1310
- OtpCodeRequestLimitExceededError,
1311
- OtpFlowError,
1312
- OtpFlowErrorCodeValues,
1313
1702
  OtpPhaseValues,
1314
1703
  TokenSession,
1315
1704
  TorrowAuth