@techfinityedge/koolbase-react-native 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auth.d.ts CHANGED
@@ -1,29 +1,104 @@
1
- import { KoolbaseConfig, KoolbaseSession, KoolbaseUser, LinkPhoneParams, LoginParams, OtpSendResult, PhoneVerifyResult, RegisterParams, SendOtpParams, VerifyOtpParams } from './types';
1
+ import { AuthStateListener, KoolbaseConfig, KoolbaseSession, KoolbaseUser, LinkPhoneParams, LoginParams, OtpSendResult, PhoneVerifyResult, RegisterParams, RestoreResult, SendOtpParams, VerifyOtpParams } from './types';
2
2
  export declare class KoolbaseAuth {
3
3
  private config;
4
+ private storage;
4
5
  private session;
6
+ private metadata;
7
+ private fetchFn;
8
+ private timeoutMs;
9
+ private ongoingRefresh;
10
+ private listeners;
5
11
  constructor(config: KoolbaseConfig);
6
- private get headers();
7
- private get authHeaders();
8
- private request;
12
+ /**
13
+ * Subscribe to authentication state changes. The listener fires:
14
+ * - Immediately on subscribe, with the current user (or null).
15
+ * - On every successful login, register, refresh, session restoration.
16
+ * - On logout / explicit setSession(null).
17
+ * - On linkPhone success (user object updated with phone fields).
18
+ *
19
+ * Returns an unsubscribe function. Call it when the consumer no longer
20
+ * needs updates (e.g. in a React useEffect cleanup).
21
+ *
22
+ * Listener errors are swallowed so a buggy listener can't break auth
23
+ * state propagation to other listeners.
24
+ *
25
+ * @example
26
+ * const unsubscribe = auth.onAuthStateChange((user) => {
27
+ * setCurrentUser(user);
28
+ * });
29
+ * // later:
30
+ * unsubscribe();
31
+ */
32
+ onAuthStateChange(listener: AuthStateListener): () => void;
33
+ private fireAuthStateChange;
34
+ /**
35
+ * Compose the full header set for an outbound request: base headers,
36
+ * device metadata, and optionally the Authorization bearer token.
37
+ * Async because device metadata's first build may read from keychain.
38
+ */
39
+ private prepareHeaders;
40
+ /**
41
+ * Low-level request helper used by every endpoint. Wires together:
42
+ * - The injected fetch implementation (config.fetch or global fetch)
43
+ * - Device metadata + x-api-key + auth header in one place
44
+ * - AbortController-based timeout (config.authTimeout, default 10s)
45
+ *
46
+ * On timeout, fetch rejects with an AbortError; callers see this as a
47
+ * non-KoolbaseAuthError exception, which restoreSession() treats as
48
+ * Offline (preserving optimistic state).
49
+ */
50
+ private authRequest;
51
+ /**
52
+ * Authenticated request wrapper. Refreshes the access token if it's
53
+ * stale (within 1-min buffer of expiry) before issuing the call, then
54
+ * delegates to {@link authRequest} with includeAuth=true.
55
+ */
56
+ private authedRequest;
57
+ private setSessionInternal;
58
+ private clearSessionInternal;
59
+ restoreSession(): Promise<RestoreResult>;
9
60
  register(params: RegisterParams): Promise<KoolbaseUser>;
10
61
  login(params: LoginParams): Promise<KoolbaseSession>;
11
- logout(): Promise<void>;
62
+ refresh(refreshToken?: string): Promise<KoolbaseSession>;
63
+ private _doRefresh;
64
+ logout(): Promise<boolean>;
12
65
  forgotPassword(email: string): Promise<void>;
13
66
  resetPassword(token: string, password: string): Promise<void>;
67
+ unlock(token: string): Promise<void>;
14
68
  get currentUser(): KoolbaseUser | null;
15
69
  get accessToken(): string | null;
16
- setSession(session: KoolbaseSession | null): void;
17
- oauthLogin({ provider, token, email, name, avatarUrl, }: {
70
+ setSession(session: KoolbaseSession | null): Promise<void>;
71
+ /**
72
+ * @deprecated v1.9.0: Server endpoint /v1/sdk/auth/oauth not yet
73
+ * shipped. This method previously routed to /v1/auth/oauth (dashboard
74
+ * developer OAuth) which never created project-scoped end-user
75
+ * sessions. Properly implemented in v1.10.0 with provider-specific
76
+ * server endpoints under /v1/sdk/auth/oauth/{apple,google,github}.
77
+ * Use email/password sign-in for now.
78
+ *
79
+ * @throws Always throws KoolbaseAuthError('not_implemented').
80
+ */
81
+ oauthLogin(_params: {
18
82
  provider: string;
19
83
  token: string;
20
84
  email?: string;
21
85
  name?: string;
22
86
  avatarUrl?: string;
23
- }): Promise<Record<string, unknown> | null>;
87
+ }): Promise<never>;
24
88
  sendOtp(params: SendOtpParams): Promise<OtpSendResult>;
25
89
  verifyOtp(params: VerifyOtpParams): Promise<PhoneVerifyResult>;
26
90
  linkPhone(params: LinkPhoneParams): Promise<void>;
91
+ /**
92
+ * Release resources held by this auth client. Clears the in-memory
93
+ * listener set. Does not invalidate sessions or clear storage — call
94
+ * {@link logout} for that.
95
+ */
96
+ dispose(): void;
27
97
  private validatePhone;
98
+ private _ensureValidToken;
99
+ private mapUser;
100
+ private parseSessionResponse;
101
+ private checkResponse;
102
+ private throwTypedError;
28
103
  private parsePhoneResponse;
29
104
  }
package/dist/auth.js CHANGED
@@ -1,62 +1,287 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KoolbaseAuth = void 0;
4
+ const types_1 = require("./types");
4
5
  const auth_errors_1 = require("./auth-errors");
6
+ const auth_storage_1 = require("./auth-storage");
7
+ const device_metadata_1 = require("./device-metadata");
5
8
  class KoolbaseAuth {
6
9
  constructor(config) {
7
10
  this.session = null;
11
+ this.ongoingRefresh = null;
12
+ this.listeners = new Set();
8
13
  this.config = config;
14
+ this.metadata = new device_metadata_1.DeviceMetadata(config.appVersion);
15
+ this.fetchFn = config.fetch ?? ((url, init) => fetch(url, init));
16
+ this.timeoutMs = config.authTimeout ?? 10000;
17
+ if (config.authStorage) {
18
+ this.storage = config.authStorage;
19
+ }
20
+ else if ((0, auth_storage_1.isKeychainAvailable)()) {
21
+ this.storage = new auth_storage_1.SecureAuthStorage();
22
+ }
23
+ else {
24
+ this.storage = null;
25
+ // eslint-disable-next-line no-console
26
+ console.warn('[Koolbase] No persistent auth storage available. Sessions will not ' +
27
+ 'survive app restarts. Install react-native-keychain for the ' +
28
+ 'default secure backend, or provide KoolbaseConfig.authStorage ' +
29
+ 'with your own implementation.');
30
+ }
31
+ }
32
+ // ─── Auth state listener ────────────────────────────────────────────────
33
+ /**
34
+ * Subscribe to authentication state changes. The listener fires:
35
+ * - Immediately on subscribe, with the current user (or null).
36
+ * - On every successful login, register, refresh, session restoration.
37
+ * - On logout / explicit setSession(null).
38
+ * - On linkPhone success (user object updated with phone fields).
39
+ *
40
+ * Returns an unsubscribe function. Call it when the consumer no longer
41
+ * needs updates (e.g. in a React useEffect cleanup).
42
+ *
43
+ * Listener errors are swallowed so a buggy listener can't break auth
44
+ * state propagation to other listeners.
45
+ *
46
+ * @example
47
+ * const unsubscribe = auth.onAuthStateChange((user) => {
48
+ * setCurrentUser(user);
49
+ * });
50
+ * // later:
51
+ * unsubscribe();
52
+ */
53
+ onAuthStateChange(listener) {
54
+ this.listeners.add(listener);
55
+ // Fire immediately with current state — matches RN ecosystem
56
+ // convention (Firebase Auth, Supabase Auth) so consumers don't
57
+ // need to separately read currentUser on mount.
58
+ try {
59
+ listener(this.session?.user ?? null);
60
+ }
61
+ catch {
62
+ // swallow
63
+ }
64
+ return () => {
65
+ this.listeners.delete(listener);
66
+ };
9
67
  }
10
- get headers() {
11
- return { 'Content-Type': 'application/json' };
68
+ fireAuthStateChange() {
69
+ const user = this.session?.user ?? null;
70
+ for (const listener of this.listeners) {
71
+ try {
72
+ listener(user);
73
+ }
74
+ catch {
75
+ // swallow — one broken listener doesn't break others
76
+ }
77
+ }
12
78
  }
13
- get authHeaders() {
79
+ // ─── Headers ────────────────────────────────────────────────────────────
80
+ /**
81
+ * Compose the full header set for an outbound request: base headers,
82
+ * device metadata, and optionally the Authorization bearer token.
83
+ * Async because device metadata's first build may read from keychain.
84
+ */
85
+ async prepareHeaders(includeAuth) {
86
+ const deviceHeaders = await this.metadata.build();
14
87
  return {
15
88
  'Content-Type': 'application/json',
16
- ...(this.session
89
+ 'x-api-key': this.config.publicKey,
90
+ ...deviceHeaders,
91
+ ...(includeAuth && this.session
17
92
  ? { Authorization: `Bearer ${this.session.accessToken}` }
18
93
  : {}),
19
94
  };
20
95
  }
21
- async request(method, path, body, auth = false) {
22
- const res = await fetch(`${this.config.baseUrl}${path}`, {
23
- method,
24
- headers: auth ? this.authHeaders : this.headers,
25
- body: body ? JSON.stringify(body) : undefined,
26
- });
27
- const data = await res.json();
28
- if (!res.ok) {
29
- throw new Error(data.error ?? `Request failed: ${res.status}`);
96
+ // ─── Request plumbing ───────────────────────────────────────────────────
97
+ /**
98
+ * Low-level request helper used by every endpoint. Wires together:
99
+ * - The injected fetch implementation (config.fetch or global fetch)
100
+ * - Device metadata + x-api-key + auth header in one place
101
+ * - AbortController-based timeout (config.authTimeout, default 10s)
102
+ *
103
+ * On timeout, fetch rejects with an AbortError; callers see this as a
104
+ * non-KoolbaseAuthError exception, which restoreSession() treats as
105
+ * Offline (preserving optimistic state).
106
+ */
107
+ async authRequest(path, options = {}) {
108
+ const headers = await this.prepareHeaders(options.includeAuth ?? false);
109
+ const controller = new AbortController();
110
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
111
+ try {
112
+ return await this.fetchFn(`${this.config.baseUrl}${path}`, {
113
+ method: options.method ?? 'GET',
114
+ headers,
115
+ body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
116
+ signal: controller.signal,
117
+ });
118
+ }
119
+ finally {
120
+ clearTimeout(timer);
121
+ }
122
+ }
123
+ /**
124
+ * Authenticated request wrapper. Refreshes the access token if it's
125
+ * stale (within 1-min buffer of expiry) before issuing the call, then
126
+ * delegates to {@link authRequest} with includeAuth=true.
127
+ */
128
+ async authedRequest(path, options = {}) {
129
+ await this._ensureValidToken();
130
+ return this.authRequest(path, { ...options, includeAuth: true });
131
+ }
132
+ // ─── Internal session lifecycle ─────────────────────────────────────────
133
+ async setSessionInternal(session) {
134
+ this.session = session;
135
+ if (this.storage) {
136
+ try {
137
+ await this.storage.saveSession(session);
138
+ }
139
+ catch (err) {
140
+ // eslint-disable-next-line no-console
141
+ console.warn('[Koolbase] Failed to persist session; staying signed in for this ' +
142
+ 'session only:', err);
143
+ }
144
+ }
145
+ this.fireAuthStateChange();
146
+ }
147
+ async clearSessionInternal() {
148
+ this.session = null;
149
+ if (this.storage) {
150
+ try {
151
+ await this.storage.clear();
152
+ }
153
+ catch {
154
+ // best effort
155
+ }
30
156
  }
31
- return data;
157
+ this.fireAuthStateChange();
32
158
  }
159
+ // ─── Session restoration ────────────────────────────────────────────────
160
+ async restoreSession() {
161
+ if (!this.storage)
162
+ return types_1.RestoreResult.NoSession;
163
+ const persisted = await this.storage.readSession();
164
+ if (!persisted)
165
+ return types_1.RestoreResult.NoSession;
166
+ // Optimistic restore — populate state and fire listener before any
167
+ // network call. App can render authenticated UI immediately.
168
+ this.session = persisted;
169
+ this.fireAuthStateChange();
170
+ const expiresAt = persisted.expiresAt
171
+ ? new Date(persisted.expiresAt).getTime()
172
+ : 0;
173
+ const oneMinuteMs = 60 * 1000;
174
+ if (expiresAt > Date.now() + oneMinuteMs) {
175
+ return types_1.RestoreResult.Restored;
176
+ }
177
+ try {
178
+ await this.refresh(persisted.refreshToken);
179
+ return types_1.RestoreResult.Restored;
180
+ }
181
+ catch (e) {
182
+ if (e instanceof auth_errors_1.SessionExpiredError ||
183
+ e instanceof auth_errors_1.TokenRevokedError ||
184
+ e instanceof auth_errors_1.InvalidCredentialsError) {
185
+ await this.clearSessionInternal();
186
+ return types_1.RestoreResult.Expired;
187
+ }
188
+ return types_1.RestoreResult.Offline;
189
+ }
190
+ }
191
+ // ─── Public auth API ────────────────────────────────────────────────────
33
192
  async register(params) {
34
- const data = await this.request('POST', '/v1/sdk/auth/register', params);
35
- return data.user;
193
+ if (params.password.length < 8)
194
+ throw new auth_errors_1.WeakPasswordError();
195
+ const res = await this.authRequest('/v1/sdk/auth/register', {
196
+ method: 'POST',
197
+ body: params,
198
+ });
199
+ const session = await this.parseSessionResponse(res, false);
200
+ await this.setSessionInternal(session);
201
+ return session.user;
36
202
  }
37
203
  async login(params) {
38
- const data = await this.request('POST', '/v1/sdk/auth/login', params);
39
- this.session = data;
40
- return data;
204
+ const res = await this.authRequest('/v1/sdk/auth/login', {
205
+ method: 'POST',
206
+ body: params,
207
+ });
208
+ const session = await this.parseSessionResponse(res, false);
209
+ await this.setSessionInternal(session);
210
+ return session;
211
+ }
212
+ async refresh(refreshToken) {
213
+ if (this.ongoingRefresh) {
214
+ return this.ongoingRefresh;
215
+ }
216
+ const promise = this._doRefresh(refreshToken);
217
+ this.ongoingRefresh = promise;
218
+ promise
219
+ .catch(() => {
220
+ // swallow; original promise still rejects to awaiters
221
+ })
222
+ .finally(() => {
223
+ if (this.ongoingRefresh === promise) {
224
+ this.ongoingRefresh = null;
225
+ }
226
+ });
227
+ return promise;
228
+ }
229
+ async _doRefresh(refreshToken) {
230
+ const token = refreshToken ?? this.session?.refreshToken;
231
+ if (!token) {
232
+ throw new auth_errors_1.SessionExpiredError();
233
+ }
234
+ const res = await this.authRequest('/v1/sdk/auth/refresh', {
235
+ method: 'POST',
236
+ body: { refresh_token: token },
237
+ });
238
+ const session = await this.parseSessionResponse(res, true);
239
+ await this.setSessionInternal(session);
240
+ return session;
41
241
  }
42
242
  async logout() {
43
- if (!this.session)
44
- return;
243
+ let serverSucceeded = true;
45
244
  try {
46
- await this.request('POST', '/v1/sdk/auth/logout', {}, true);
245
+ if (this.session) {
246
+ // Best-effort: don't auto-refresh during logout. If the token's
247
+ // already expired, we still want to clear local state — server
248
+ // will reap expired sessions itself.
249
+ const res = await this.authRequest('/v1/sdk/auth/logout', {
250
+ method: 'POST',
251
+ includeAuth: true,
252
+ });
253
+ if (!res.ok)
254
+ serverSucceeded = false;
255
+ }
256
+ }
257
+ catch {
258
+ serverSucceeded = false;
47
259
  }
48
260
  finally {
49
- this.session = null;
261
+ await this.clearSessionInternal();
50
262
  }
263
+ return serverSucceeded;
51
264
  }
52
265
  async forgotPassword(email) {
53
- await this.request('POST', '/v1/sdk/auth/forgot-password', { email });
266
+ const res = await this.authRequest('/v1/sdk/auth/password-reset', {
267
+ method: 'POST',
268
+ body: { email },
269
+ });
270
+ await this.checkResponse(res);
54
271
  }
55
272
  async resetPassword(token, password) {
56
- await this.request('POST', '/v1/sdk/auth/reset-password', {
57
- token,
58
- password,
273
+ const res = await this.authRequest('/v1/sdk/auth/password-reset/confirm', {
274
+ method: 'POST',
275
+ body: { token, password },
59
276
  });
277
+ await this.checkResponse(res);
278
+ }
279
+ async unlock(token) {
280
+ const res = await this.authRequest('/v1/sdk/auth/unlock', {
281
+ method: 'POST',
282
+ body: { token },
283
+ });
284
+ await this.checkResponse(res);
60
285
  }
61
286
  get currentUser() {
62
287
  return this.session?.user ?? null;
@@ -64,61 +289,58 @@ class KoolbaseAuth {
64
289
  get accessToken() {
65
290
  return this.session?.accessToken ?? null;
66
291
  }
67
- setSession(session) {
68
- this.session = session;
69
- }
70
- async oauthLogin({ provider, token, email = '', name = '', avatarUrl = '', }) {
71
- try {
72
- const response = await fetch(`${this.config.baseUrl}/v1/auth/oauth`, {
73
- method: 'POST',
74
- headers: { 'Content-Type': 'application/json' },
75
- body: JSON.stringify({ provider, token, email, name, avatar_url: avatarUrl }),
76
- });
77
- if (response.ok)
78
- return response.json();
79
- return null;
292
+ async setSession(session) {
293
+ if (session) {
294
+ await this.setSessionInternal(session);
80
295
  }
81
- catch {
82
- return null;
296
+ else {
297
+ await this.clearSessionInternal();
83
298
  }
84
299
  }
300
+ // ─── OAuth (DEPRECATED — see v1.10.0) ───────────────────────────────────
301
+ /**
302
+ * @deprecated v1.9.0: Server endpoint /v1/sdk/auth/oauth not yet
303
+ * shipped. This method previously routed to /v1/auth/oauth (dashboard
304
+ * developer OAuth) which never created project-scoped end-user
305
+ * sessions. Properly implemented in v1.10.0 with provider-specific
306
+ * server endpoints under /v1/sdk/auth/oauth/{apple,google,github}.
307
+ * Use email/password sign-in for now.
308
+ *
309
+ * @throws Always throws KoolbaseAuthError('not_implemented').
310
+ */
311
+ async oauthLogin(_params) {
312
+ throw new auth_errors_1.KoolbaseAuthError('OAuth sign-in is not yet implemented for the Koolbase SDK. ' +
313
+ 'Planned for v1.10.0 (server-side endpoints under ' +
314
+ '/v1/sdk/auth/oauth/{provider}). Use email/password authentication ' +
315
+ 'in the meantime.', 'not_implemented');
316
+ }
317
+ // ─── Phone OTP ──────────────────────────────────────────────────────────
85
318
  async sendOtp(params) {
86
319
  this.validatePhone(params.phoneNumber);
87
- const res = await fetch(`${this.config.baseUrl}/v1/sdk/auth/phone/send-otp`, {
320
+ const res = await this.authRequest('/v1/sdk/auth/phone/send-otp', {
88
321
  method: 'POST',
89
- headers: this.headers,
90
- body: JSON.stringify({ phone_number: params.phoneNumber }),
322
+ body: { phone_number: params.phoneNumber },
91
323
  });
92
324
  const data = await this.parsePhoneResponse(res);
93
325
  return { expiresAt: data.expires_at };
94
326
  }
95
327
  async verifyOtp(params) {
96
328
  this.validatePhone(params.phoneNumber);
97
- const res = await fetch(`${this.config.baseUrl}/v1/sdk/auth/phone/verify-otp`, {
329
+ const res = await this.authRequest('/v1/sdk/auth/phone/verify-otp', {
98
330
  method: 'POST',
99
- headers: this.headers,
100
- body: JSON.stringify({
331
+ body: {
101
332
  phone_number: params.phoneNumber,
102
333
  code: params.code,
103
- }),
334
+ },
104
335
  });
105
336
  const data = await this.parsePhoneResponse(res);
106
- const user = {
107
- id: data.user.id,
108
- email: data.user.email ?? '',
109
- phoneNumber: data.user.phone_number,
110
- phoneVerified: data.user.phone_verified ?? false,
111
- fullName: data.user.full_name,
112
- avatarUrl: data.user.avatar_url,
113
- verified: data.user.verified ?? false,
114
- createdAt: data.user.created_at,
115
- };
116
337
  const session = {
117
338
  accessToken: data.access_token,
118
339
  refreshToken: data.refresh_token,
119
- user,
340
+ expiresAt: data.expires_at,
341
+ user: this.mapUser(data.user),
120
342
  };
121
- this.session = session;
343
+ await this.setSessionInternal(session);
122
344
  return { session, isNewUser: data.is_new_user ?? false };
123
345
  }
124
346
  async linkPhone(params) {
@@ -126,27 +348,135 @@ class KoolbaseAuth {
126
348
  throw new auth_errors_1.KoolbaseAuthError('Must be signed in to link a phone number', 'unauthenticated');
127
349
  }
128
350
  this.validatePhone(params.phoneNumber);
129
- const res = await fetch(`${this.config.baseUrl}/v1/sdk/auth/phone/link`, {
351
+ const res = await this.authedRequest('/v1/sdk/auth/phone/link', {
130
352
  method: 'POST',
131
- headers: this.authHeaders,
132
- body: JSON.stringify({
353
+ body: {
133
354
  phone_number: params.phoneNumber,
134
355
  code: params.code,
135
- }),
356
+ },
136
357
  });
137
- await this.parsePhoneResponse(res);
358
+ const body = await this.parsePhoneResponse(res);
359
+ // Update local session: prefer the canonical user from the server
360
+ // response if present; otherwise merge the linked phone into the
361
+ // existing in-memory user. Either way, setSessionInternal fires the
362
+ // auth state listener so consumers can react to the phone link.
363
+ if (this.session) {
364
+ const updatedUser = body.user
365
+ ? this.mapUser(body.user)
366
+ : {
367
+ ...this.session.user,
368
+ phoneNumber: params.phoneNumber,
369
+ phoneVerified: true,
370
+ };
371
+ await this.setSessionInternal({
372
+ ...this.session,
373
+ user: updatedUser,
374
+ });
375
+ }
376
+ }
377
+ // ─── Cleanup ────────────────────────────────────────────────────────────
378
+ /**
379
+ * Release resources held by this auth client. Clears the in-memory
380
+ * listener set. Does not invalidate sessions or clear storage — call
381
+ * {@link logout} for that.
382
+ */
383
+ dispose() {
384
+ this.listeners.clear();
138
385
  }
386
+ // ─── Helpers ────────────────────────────────────────────────────────────
139
387
  validatePhone(phoneNumber) {
140
388
  if (!/^\+[1-9]\d{6,14}$/.test(phoneNumber)) {
141
389
  throw new auth_errors_1.InvalidPhoneNumberError();
142
390
  }
143
391
  }
392
+ async _ensureValidToken() {
393
+ if (this.session && this.session.expiresAt) {
394
+ const expiresAt = new Date(this.session.expiresAt).getTime();
395
+ if (Date.now() < expiresAt - 60 * 1000) {
396
+ return this.session.accessToken;
397
+ }
398
+ }
399
+ if (!this.session) {
400
+ throw new auth_errors_1.SessionExpiredError();
401
+ }
402
+ try {
403
+ const session = await this.refresh();
404
+ return session.accessToken;
405
+ }
406
+ catch (e) {
407
+ if (e instanceof auth_errors_1.KoolbaseAuthError)
408
+ throw e;
409
+ throw new auth_errors_1.SessionExpiredError();
410
+ }
411
+ }
412
+ mapUser(raw) {
413
+ return {
414
+ id: raw.id,
415
+ email: raw.email ?? '',
416
+ phoneNumber: raw.phone_number,
417
+ phoneVerified: raw.phone_verified ?? false,
418
+ fullName: raw.full_name,
419
+ avatarUrl: raw.avatar_url,
420
+ verified: raw.verified ?? false,
421
+ createdAt: raw.created_at,
422
+ };
423
+ }
424
+ async parseSessionResponse(res, isRefresh) {
425
+ if (res.status === 409)
426
+ throw new auth_errors_1.EmailAlreadyInUseError();
427
+ if (res.status === 401) {
428
+ throw isRefresh ? new auth_errors_1.SessionExpiredError() : new auth_errors_1.InvalidCredentialsError();
429
+ }
430
+ if (res.status === 403)
431
+ throw new auth_errors_1.UserDisabledError();
432
+ if (!res.ok)
433
+ await this.throwTypedError(res);
434
+ const data = await res.json();
435
+ return {
436
+ accessToken: data.access_token,
437
+ refreshToken: data.refresh_token,
438
+ expiresAt: data.expires_at,
439
+ user: this.mapUser(data.user),
440
+ };
441
+ }
442
+ async checkResponse(res) {
443
+ if (res.ok)
444
+ return;
445
+ await this.throwTypedError(res);
446
+ }
447
+ async throwTypedError(res) {
448
+ let body = {};
449
+ try {
450
+ body = await res.json();
451
+ }
452
+ catch {
453
+ // ignore
454
+ }
455
+ const msg = body.error ?? '';
456
+ if (res.status === 429) {
457
+ if (msg.includes('account temporarily locked')) {
458
+ throw new auth_errors_1.AccountLockedError();
459
+ }
460
+ throw new auth_errors_1.RateLimitError(msg || undefined);
461
+ }
462
+ if (msg.includes('invalid or expired unlock token')) {
463
+ throw new auth_errors_1.UnlockTokenInvalidError();
464
+ }
465
+ if (msg.includes('session revoked') ||
466
+ msg.includes('token revoked') ||
467
+ msg.includes('session has been revoked')) {
468
+ throw new auth_errors_1.TokenRevokedError();
469
+ }
470
+ throw new auth_errors_1.KoolbaseAuthError(msg || `Request failed: ${res.status}`, `http_${res.status}`);
471
+ }
144
472
  async parsePhoneResponse(res) {
145
473
  let body = {};
146
474
  try {
147
475
  body = await res.json();
148
476
  }
149
- catch { }
477
+ catch {
478
+ // ignore
479
+ }
150
480
  if (res.ok)
151
481
  return body;
152
482
  const msg = body.error ?? '';