@koolbase/core 12.14.0 → 12.16.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.
@@ -1,5 +1,5 @@
1
1
  import { AuthStateListener, KoolbaseConfig, KoolbaseSession, KoolbaseUser, LinkPhoneParams, LoginParams, OtpSendResult, PhoneVerifyResult, RegisterParams, RestoreResult, KoolbaseAuditPage, KoolbaseSessionInfo, ResendVerificationResult, SendOtpParams, SignUpResult, SignInWithAppleParams, VerifyOtpParams } from './types.js';
2
- import type { SignInWithGoogleParams } from './types.js';
2
+ import type { SignInWithGoogleParams, SignOutReason } from './types.js';
3
3
  export declare class KoolbaseAuth {
4
4
  private config;
5
5
  private storage;
@@ -9,6 +9,10 @@ export declare class KoolbaseAuth {
9
9
  private timeoutMs;
10
10
  private ongoingRefresh;
11
11
  private listeners;
12
+ /** Why the last session ended (#104); null while signed in or never signed in. */
13
+ private signOutReason;
14
+ /** The last renewal failed for want of a connection, not because the server refused it (#96). */
15
+ private renewalUnavailable;
12
16
  constructor(config: KoolbaseConfig);
13
17
  /**
14
18
  * Subscribe to authentication state changes. The listener fires:
@@ -82,6 +86,29 @@ export declare class KoolbaseAuth {
82
86
  * need to.
83
87
  */
84
88
  clearStoredSession(): Promise<void>;
89
+ /**
90
+ * Why the last session ended (#104): `'signed_out'` when the person signed
91
+ * out (or deleted their account), `'session_ended'` when it ended on its own
92
+ * -- revoked, past renewal, signed out on another device. null while signed
93
+ * in. Read it in an onAuthStateChange listener to say "You were signed out"
94
+ * only when they did not ask to be.
95
+ */
96
+ get lastSignOutReason(): SignOutReason | null;
97
+ /**
98
+ * A data request carrying `refused` came back unauthenticated. Renews the
99
+ * session and returns the token to send again with, or null when there is
100
+ * none -- nobody is signed in, or the server refused the renewal too, which
101
+ * ends the session (#96).
102
+ */
103
+ renewRejected(refused: string): Promise<string | null>;
104
+ /**
105
+ * A data request was refused as unauthenticated and renewing could not
106
+ * help: the session is over, so it ends here and every listener hears it.
107
+ * Unless the renewal failed only for want of a connection -- then the
108
+ * session may well be good, and signing someone out for a dropped network
109
+ * would be wrong (#96).
110
+ */
111
+ sessionRejected(): Promise<void>;
85
112
  private clearSessionInternal;
86
113
  restoreSession(): Promise<RestoreResult>;
87
114
  /**
@@ -164,6 +191,9 @@ export declare class KoolbaseAuth {
164
191
  private parseAppleSessionResponse;
165
192
  refresh(refreshToken?: string): Promise<KoolbaseSession>;
166
193
  private _doRefresh;
194
+ private readStored;
195
+ /** A newer session another tab stored after renewing `spent`, made current; null if none. */
196
+ private adoptNewer;
167
197
  logout(): Promise<boolean>;
168
198
  forgotPassword(email: string): Promise<void>;
169
199
  /**
package/dist/cjs/auth.js CHANGED
@@ -11,6 +11,10 @@ class KoolbaseAuth {
11
11
  this.session = null;
12
12
  this.ongoingRefresh = null;
13
13
  this.listeners = new Set();
14
+ /** Why the last session ended (#104); null while signed in or never signed in. */
15
+ this.signOutReason = null;
16
+ /** The last renewal failed for want of a connection, not because the server refused it (#96). */
17
+ this.renewalUnavailable = false;
14
18
  this.config = config;
15
19
  this.metadata = new device_metadata_js_1.DeviceMetadata(config.appVersion);
16
20
  this.fetchFn = config.fetch ?? ((url, init) => (0, network_js_1.koolbaseFetch)(url, init));
@@ -152,6 +156,7 @@ class KoolbaseAuth {
152
156
  }
153
157
  async setSessionInternal(session) {
154
158
  this.session = session;
159
+ this.signOutReason = null;
155
160
  if (this.storage) {
156
161
  try {
157
162
  await this.storage.saveSession(session);
@@ -178,9 +183,52 @@ class KoolbaseAuth {
178
183
  * need to.
179
184
  */
180
185
  async clearStoredSession() {
181
- await this.clearSessionInternal();
186
+ await this.clearSessionInternal('session_ended');
182
187
  }
183
- async clearSessionInternal() {
188
+ /**
189
+ * Why the last session ended (#104): `'signed_out'` when the person signed
190
+ * out (or deleted their account), `'session_ended'` when it ended on its own
191
+ * -- revoked, past renewal, signed out on another device. null while signed
192
+ * in. Read it in an onAuthStateChange listener to say "You were signed out"
193
+ * only when they did not ask to be.
194
+ */
195
+ get lastSignOutReason() {
196
+ return this.signOutReason;
197
+ }
198
+ /**
199
+ * A data request carrying `refused` came back unauthenticated. Renews the
200
+ * session and returns the token to send again with, or null when there is
201
+ * none -- nobody is signed in, or the server refused the renewal too, which
202
+ * ends the session (#96).
203
+ */
204
+ async renewRejected(refused) {
205
+ if (!this.session)
206
+ return null;
207
+ // Another request renewed it while this one was out.
208
+ if (this.session.accessToken !== refused)
209
+ return this.session.accessToken;
210
+ try {
211
+ return (await this.refresh()).accessToken;
212
+ }
213
+ catch {
214
+ return null;
215
+ }
216
+ }
217
+ /**
218
+ * A data request was refused as unauthenticated and renewing could not
219
+ * help: the session is over, so it ends here and every listener hears it.
220
+ * Unless the renewal failed only for want of a connection -- then the
221
+ * session may well be good, and signing someone out for a dropped network
222
+ * would be wrong (#96).
223
+ */
224
+ async sessionRejected() {
225
+ if (!this.session || this.renewalUnavailable)
226
+ return;
227
+ await this.clearSessionInternal('session_ended');
228
+ }
229
+ async clearSessionInternal(reason = 'session_ended') {
230
+ if (this.session)
231
+ this.signOutReason = reason;
184
232
  this.session = null;
185
233
  if (this.storage) {
186
234
  try {
@@ -208,7 +256,28 @@ class KoolbaseAuth {
208
256
  : 0;
209
257
  const oneMinuteMs = 60 * 1000;
210
258
  if (expiresAt > Date.now() + oneMinuteMs) {
211
- return types_js_1.RestoreResult.Restored;
259
+ // Valid by the clock is not valid: the session may have been signed out
260
+ // on another device, revoked, or the account disabled since. Ask the
261
+ // server before saying it is restored (#104). No answer -- offline, a
262
+ // timeout, a server error -- keeps the optimistic session: Offline.
263
+ try {
264
+ const res = await this.authRequest('/v1/sdk/auth/me', { method: 'GET', includeAuth: true });
265
+ if (res.ok) {
266
+ try {
267
+ await this.updateUserAndPersist(this.mapUser(await res.json()));
268
+ }
269
+ catch {
270
+ // The session is good; a user that will not parse keeps the stored one.
271
+ }
272
+ return types_js_1.RestoreResult.Restored;
273
+ }
274
+ if (res.status !== 401)
275
+ return types_js_1.RestoreResult.Offline;
276
+ // Refused: the access token is done. The refresh token decides.
277
+ }
278
+ catch {
279
+ return types_js_1.RestoreResult.Offline;
280
+ }
212
281
  }
213
282
  try {
214
283
  await this.refresh(persisted.refreshToken);
@@ -224,8 +293,8 @@ class KoolbaseAuth {
224
293
  // key or the request rather than the user's session — and deleting the
225
294
  // refresh token on that reading signs someone out with no way back.
226
295
  // Nothing reaches here with it today; it stays out so nothing does.
227
- if (e instanceof auth_errors_js_1.SessionExpiredError) {
228
- await this.clearSessionInternal();
296
+ if (isRejection(e)) {
297
+ await this.clearSessionInternal('session_ended');
229
298
  return types_js_1.RestoreResult.Expired;
230
299
  }
231
300
  return types_js_1.RestoreResult.Offline;
@@ -498,17 +567,77 @@ class KoolbaseAuth {
498
567
  return promise;
499
568
  }
500
569
  async _doRefresh(refreshToken) {
501
- const token = refreshToken ?? this.session?.refreshToken;
570
+ // The newest refresh token is the stored one: another tab of the same app
571
+ // may have renewed the session, and its in-memory copy here is spent.
572
+ const stored = refreshToken ? null : await this.readStored();
573
+ const token = refreshToken ?? stored?.refreshToken ?? this.session?.refreshToken;
502
574
  if (!token) {
503
575
  throw new auth_errors_js_1.SessionExpiredError();
504
576
  }
505
- const res = await this.authRequest('/v1/sdk/auth/refresh', {
506
- method: 'POST',
507
- body: { refresh_token: token },
508
- });
509
- const session = await this.parseSessionResponse(res, true);
510
- await this.setSessionInternal(session);
511
- return session;
577
+ try {
578
+ const res = await this.authRequest('/v1/sdk/auth/refresh', {
579
+ method: 'POST',
580
+ body: { refresh_token: token },
581
+ });
582
+ const session = await this.parseSessionResponse(res, true);
583
+ this.renewalUnavailable = false;
584
+ await this.setSessionInternal(session);
585
+ return session;
586
+ }
587
+ catch (e) {
588
+ if (!isRejection(e)) {
589
+ // No answer, or a server error: the session may be fine. Keep it.
590
+ this.renewalUnavailable = true;
591
+ throw e;
592
+ }
593
+ this.renewalUnavailable = false;
594
+ // Two tabs renewing at the same moment send the same token; the
595
+ // server renews one and refuses the other. The winner's session is in
596
+ // storage: take it rather than signing out (#96).
597
+ const adopted = await this.adoptNewer(token);
598
+ if (adopted)
599
+ return adopted;
600
+ await this.clearSessionInternal('session_ended');
601
+ throw e;
602
+ }
603
+ }
604
+ async readStored() {
605
+ if (!this.storage)
606
+ return null;
607
+ try {
608
+ return await this.storage.readSession();
609
+ }
610
+ catch {
611
+ return null;
612
+ }
613
+ }
614
+ /** A newer session another tab stored after renewing `spent`, made current; null if none. */
615
+ async adoptNewer(spent) {
616
+ for (const wait of [0, 400]) {
617
+ if (wait)
618
+ await new Promise((r) => setTimeout(r, wait));
619
+ const stored = await this.readStored();
620
+ if (!stored || stored.refreshToken === spent)
621
+ continue;
622
+ const expiresAt = stored.expiresAt ? new Date(stored.expiresAt).getTime() : 0;
623
+ if (expiresAt > Date.now() + 60 * 1000) {
624
+ await this.setSessionInternal(stored);
625
+ return stored;
626
+ }
627
+ try {
628
+ const res = await this.authRequest('/v1/sdk/auth/refresh', {
629
+ method: 'POST',
630
+ body: { refresh_token: stored.refreshToken },
631
+ });
632
+ const session = await this.parseSessionResponse(res, true);
633
+ await this.setSessionInternal(session);
634
+ return session;
635
+ }
636
+ catch {
637
+ return null;
638
+ }
639
+ }
640
+ return null;
512
641
  }
513
642
  async logout() {
514
643
  let serverSucceeded = true;
@@ -529,7 +658,7 @@ class KoolbaseAuth {
529
658
  serverSucceeded = false;
530
659
  }
531
660
  finally {
532
- await this.clearSessionInternal();
661
+ await this.clearSessionInternal('signed_out');
533
662
  }
534
663
  return serverSucceeded;
535
664
  }
@@ -692,7 +821,7 @@ class KoolbaseAuth {
692
821
  includeAuth: true,
693
822
  });
694
823
  await this.checkResponse(res);
695
- await this.clearSessionInternal();
824
+ await this.clearSessionInternal('signed_out');
696
825
  }
697
826
  /**
698
827
  * Every session the signed-in user has — a "where you're signed in" list.
@@ -815,8 +944,14 @@ class KoolbaseAuth {
815
944
  try {
816
945
  return await this._ensureValidToken();
817
946
  }
818
- catch {
819
- return null;
947
+ catch (e) {
948
+ // Refused: the session has ended (and been cleared); go api-key-only.
949
+ if (isRejection(e))
950
+ return null;
951
+ // No answer: the session may be fine, so the request must not go out
952
+ // as a signed-out visitor (whose refusal would end it). It fails as
953
+ // the network failure it is, and the caller retries (#96).
954
+ throw e;
820
955
  }
821
956
  }
822
957
  async setSession(session) {
@@ -1064,9 +1199,9 @@ class KoolbaseAuth {
1064
1199
  return session.accessToken;
1065
1200
  }
1066
1201
  catch (e) {
1067
- if (e instanceof auth_errors_js_1.KoolbaseAuthError)
1068
- throw e;
1069
- throw new auth_errors_js_1.SessionExpiredError();
1202
+ // A refusal stays a refusal; a network failure stays one too, rather
1203
+ // than being reported as an ended session (#96).
1204
+ throw e;
1070
1205
  }
1071
1206
  }
1072
1207
  mapUser(raw) {
@@ -1314,3 +1449,15 @@ class KoolbaseAuth {
1314
1449
  }
1315
1450
  }
1316
1451
  exports.KoolbaseAuth = KoolbaseAuth;
1452
+ /**
1453
+ * Whether an error is the server refusing the session -- it ended -- rather
1454
+ * than no answer at all (#96). Only a refusal signs anyone out.
1455
+ */
1456
+ function isRejection(e) {
1457
+ if (e instanceof auth_errors_js_1.SessionExpiredError || e instanceof auth_errors_js_1.TokenRevokedError || e instanceof auth_errors_js_1.UserDisabledError)
1458
+ return true;
1459
+ if (e instanceof auth_errors_js_1.KoolbaseAuthError) {
1460
+ return ['invalid_refresh_token', 'token_revoked', 'account_disabled', 'mfa_session_insufficient', 'unauthenticated'].includes(e.code ?? '');
1461
+ }
1462
+ return false;
1463
+ }
@@ -97,7 +97,7 @@ class KoolbaseDatabase {
97
97
  };
98
98
  }
99
99
  async request(method, path, body) {
100
- const res = await (0, network_js_1.koolbaseFetch)(`${this.config.baseUrl}${path}`, {
100
+ const res = await (0, network_js_1.sessionFetch)(`${this.config.baseUrl}${path}`, {
101
101
  method,
102
102
  headers: await this.buildHeaders(),
103
103
  body: body ? JSON.stringify(body) : undefined,
@@ -135,7 +135,7 @@ class KoolbaseDatabase {
135
135
  * none of them clearing a rejected session. One path, two shapes of result.
136
136
  */
137
137
  async requestWithStatus(method, path, body) {
138
- const res = await (0, network_js_1.koolbaseFetch)(`${this.config.baseUrl}${path}`, {
138
+ const res = await (0, network_js_1.sessionFetch)(`${this.config.baseUrl}${path}`, {
139
139
  method,
140
140
  headers: await this.buildHeaders(),
141
141
  body: body ? JSON.stringify(body) : undefined,
@@ -113,7 +113,7 @@ class KoolbaseFiscal {
113
113
  const controller = new AbortController();
114
114
  const timer = setTimeout(() => controller.abort(), params.timeoutMs ?? 30000);
115
115
  try {
116
- const res = await (0, network_js_1.koolbaseFetch)(`${this.config.baseUrl}/v1/sdk/fiscal/submit`, {
116
+ const res = await (0, network_js_1.sessionFetch)(`${this.config.baseUrl}/v1/sdk/fiscal/submit`, {
117
117
  method: 'POST',
118
118
  headers: await this.buildHeaders(),
119
119
  signal: controller.signal,
@@ -151,7 +151,7 @@ class KoolbaseFiscal {
151
151
  device_id: params.deviceId,
152
152
  client_ref: params.clientRef,
153
153
  });
154
- const res = await (0, network_js_1.koolbaseFetch)(`${this.config.baseUrl}/v1/sdk/fiscal/status?${q.toString()}`, { method: 'GET', headers: await this.buildHeaders(), signal: controller.signal });
154
+ const res = await (0, network_js_1.sessionFetch)(`${this.config.baseUrl}/v1/sdk/fiscal/status?${q.toString()}`, { method: 'GET', headers: await this.buildHeaders(), signal: controller.signal });
155
155
  return await this.decode(res);
156
156
  }
157
157
  catch (e) {
@@ -14,7 +14,7 @@ class KoolbaseFunctions {
14
14
  // ─── Deploy ────────────────────────────────────────────────────────────────
15
15
  async deploy(options) {
16
16
  const runtime = options.runtime ?? types_js_1.FunctionRuntime.Deno;
17
- const res = await (0, network_js_1.koolbaseFetch)(`${this.config.baseUrl}/v1/sdk/functions/deploy`, {
17
+ const res = await (0, network_js_1.sessionFetch)(`${this.config.baseUrl}/v1/sdk/functions/deploy`, {
18
18
  method: 'POST',
19
19
  headers: {
20
20
  'Content-Type': 'application/json',
@@ -58,7 +58,7 @@ class KoolbaseFunctions {
58
58
  if (userToken) {
59
59
  headers['Authorization'] = `Bearer ${userToken}`;
60
60
  }
61
- const res = await (0, network_js_1.koolbaseFetch)(`${this.config.baseUrl}/v1/sdk/functions/${name}`, {
61
+ const res = await (0, network_js_1.sessionFetch)(`${this.config.baseUrl}/v1/sdk/functions/${name}`, {
62
62
  method: 'POST',
63
63
  headers,
64
64
  body: JSON.stringify({ body: body ?? {} }),
@@ -20,6 +20,9 @@ export { KoolbaseRealtime } from './realtime.js';
20
20
  export { KoolbaseConnectivity } from './connectivity.js';
21
21
  export type { ConnectivityState } from './connectivity.js';
22
22
  export { KoolbaseStorage } from './storage.js';
23
+ export { KoolbaseUploadTask } from './storage-transfer.js';
24
+ export type { KoolbaseUploadProgress, KoolbaseUploadState } from './storage-transfer.js';
25
+ export type { KoolbaseFileInfo, KoolbaseFilePage, KoolbaseFileSort, ListFilesOptions } from './storage-files.js';
23
26
  export { KoolbaseAnalytics, disabledAnalytics } from './analytics.js';
24
27
  export { KoolbaseFiscal, KoolbaseFiscalError, decodeFiscalResponse, isFiscalized, isPending } from './fiscal.js';
25
28
  export type { FiscalIntentResult, FiscalStatus } from './fiscal.js';
package/dist/cjs/index.js CHANGED
@@ -19,7 +19,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
19
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
20
20
  };
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.memoryPlatform = exports.getPlatform = exports.setPlatform = exports.RestoreResult = exports.koolbaseSdkVersion = exports.getOrCreateDeviceId = exports.KoolbaseMessaging = exports.isPending = exports.isFiscalized = exports.decodeFiscalResponse = exports.KoolbaseFiscalError = exports.KoolbaseFiscal = exports.disabledAnalytics = exports.KoolbaseAnalytics = exports.KoolbaseStorage = exports.KoolbaseConnectivity = exports.KoolbaseRealtime = exports.KoolbaseFunctions = exports.KoolbaseFlags = exports.initialRecordState = exports.KoolbaseRecordController = exports.initialCollectionState = exports.collectionQueryKey = exports.KoolbaseCollectionController = exports.KoolbaseDatabase = exports.KoolbaseAuth = void 0;
22
+ exports.memoryPlatform = exports.getPlatform = exports.setPlatform = exports.RestoreResult = exports.koolbaseSdkVersion = exports.getOrCreateDeviceId = exports.KoolbaseMessaging = exports.isPending = exports.isFiscalized = exports.decodeFiscalResponse = exports.KoolbaseFiscalError = exports.KoolbaseFiscal = exports.disabledAnalytics = exports.KoolbaseAnalytics = exports.KoolbaseUploadTask = exports.KoolbaseStorage = exports.KoolbaseConnectivity = exports.KoolbaseRealtime = exports.KoolbaseFunctions = exports.KoolbaseFlags = exports.initialRecordState = exports.KoolbaseRecordController = exports.initialCollectionState = exports.collectionQueryKey = exports.KoolbaseCollectionController = exports.KoolbaseDatabase = exports.KoolbaseAuth = void 0;
23
23
  __exportStar(require("./types.js"), exports);
24
24
  __exportStar(require("./errors.js"), exports);
25
25
  __exportStar(require("./network.js"), exports);
@@ -51,6 +51,8 @@ var connectivity_js_1 = require("./connectivity.js");
51
51
  Object.defineProperty(exports, "KoolbaseConnectivity", { enumerable: true, get: function () { return connectivity_js_1.KoolbaseConnectivity; } });
52
52
  var storage_js_1 = require("./storage.js");
53
53
  Object.defineProperty(exports, "KoolbaseStorage", { enumerable: true, get: function () { return storage_js_1.KoolbaseStorage; } });
54
+ var storage_transfer_js_1 = require("./storage-transfer.js");
55
+ Object.defineProperty(exports, "KoolbaseUploadTask", { enumerable: true, get: function () { return storage_transfer_js_1.KoolbaseUploadTask; } });
54
56
  var analytics_js_1 = require("./analytics.js");
55
57
  Object.defineProperty(exports, "KoolbaseAnalytics", { enumerable: true, get: function () { return analytics_js_1.KoolbaseAnalytics; } });
56
58
  Object.defineProperty(exports, "disabledAnalytics", { enumerable: true, get: function () { return analytics_js_1.disabledAnalytics; } });
@@ -19,3 +19,17 @@ export declare class KoolbaseNetworkError extends TypeError {
19
19
  }
20
20
  /** fetch, with network failures turned into a KoolbaseNetworkError. */
21
21
  export declare function koolbaseFetch(input: string, init?: RequestInit): Promise<Response>;
22
+ type Renewal = (refusedToken: string) => Promise<string | null>;
23
+ /** Installed by Koolbase.initialize: renews the session a request was refused with. */
24
+ export declare function setSessionRenewal(fn: Renewal | null): void;
25
+ /**
26
+ * koolbaseFetch for a data request. A request refused as unauthenticated
27
+ * (401) while carrying the signed-in person's token is the session needing
28
+ * renewal, not ending: the access token expired early, the clock is off, or
29
+ * another tab renewed it. The session is renewed once and the request sent
30
+ * again with the new token, so the caller never sees the first refusal.
31
+ * Only when renewal is refused does the 401 reach the caller, and with it the
32
+ * client's onSessionExpired, which ends the session.
33
+ */
34
+ export declare function sessionFetch(input: string, init?: RequestInit): Promise<Response>;
35
+ export {};
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KoolbaseNetworkError = void 0;
4
4
  exports.koolbaseFetch = koolbaseFetch;
5
+ exports.setSessionRenewal = setSessionRenewal;
6
+ exports.sessionFetch = sessionFetch;
5
7
  /**
6
8
  * Network failures, made actionable.
7
9
  *
@@ -52,3 +54,61 @@ async function koolbaseFetch(input, init) {
52
54
  throw new KoolbaseNetworkError(input, e);
53
55
  }
54
56
  }
57
+ let renewal = null;
58
+ /** Installed by Koolbase.initialize: renews the session a request was refused with. */
59
+ function setSessionRenewal(fn) {
60
+ renewal = fn;
61
+ }
62
+ function headerOf(headers, name) {
63
+ if (!headers)
64
+ return undefined;
65
+ if (typeof headers.get === 'function')
66
+ return headers.get(name) ?? undefined;
67
+ if (Array.isArray(headers))
68
+ return headers.find(([k]) => k.toLowerCase() === name.toLowerCase())?.[1];
69
+ const rec = headers;
70
+ const key = Object.keys(rec).find((k) => k.toLowerCase() === name.toLowerCase());
71
+ return key ? rec[key] : undefined;
72
+ }
73
+ function withHeader(headers, name, value) {
74
+ const out = {};
75
+ if (headers && typeof headers.forEach === 'function' && !Array.isArray(headers)) {
76
+ headers.forEach((v, k) => { out[k] = v; });
77
+ }
78
+ else if (Array.isArray(headers)) {
79
+ for (const [k, v] of headers)
80
+ out[k] = v;
81
+ }
82
+ else if (headers) {
83
+ Object.assign(out, headers);
84
+ }
85
+ for (const k of Object.keys(out))
86
+ if (k.toLowerCase() === name.toLowerCase())
87
+ delete out[k];
88
+ out[name] = value;
89
+ return out;
90
+ }
91
+ /**
92
+ * koolbaseFetch for a data request. A request refused as unauthenticated
93
+ * (401) while carrying the signed-in person's token is the session needing
94
+ * renewal, not ending: the access token expired early, the clock is off, or
95
+ * another tab renewed it. The session is renewed once and the request sent
96
+ * again with the new token, so the caller never sees the first refusal.
97
+ * Only when renewal is refused does the 401 reach the caller, and with it the
98
+ * client's onSessionExpired, which ends the session.
99
+ */
100
+ async function sessionFetch(input, init) {
101
+ const res = await koolbaseFetch(input, init);
102
+ if (res.status !== 401 || !renewal)
103
+ return res;
104
+ const auth = headerOf(init?.headers, 'Authorization');
105
+ if (!auth || !auth.startsWith('Bearer '))
106
+ return res;
107
+ // Only a body that can be sent twice. Data requests send JSON text.
108
+ if (init?.body !== undefined && init.body !== null && typeof init.body !== 'string')
109
+ return res;
110
+ const fresh = await renewal(auth.slice('Bearer '.length));
111
+ if (!fresh)
112
+ return res;
113
+ return koolbaseFetch(input, { ...init, headers: withHeader(init?.headers, 'Authorization', `Bearer ${fresh}`) });
114
+ }
@@ -187,6 +187,45 @@ export declare class KoolbaseStorageMetadataInvalidError extends KoolbaseStorage
187
187
  detail?: string;
188
188
  constructor(message?: string, detail?: string);
189
189
  }
190
+ /**
191
+ * An upload that was cancelled -- by `KoolbaseUploadTask.cancel()` or the
192
+ * upload's `signal` -- before the server began checking it. Nothing was
193
+ * stored. Code: `upload_cancelled`.
194
+ */
195
+ export declare class KoolbaseUploadCancelledError extends KoolbaseStorageError {
196
+ constructor(message?: string);
197
+ }
198
+ /**
199
+ * The confirm named an upload the server does not have: it expired, was
200
+ * confirmed already, or belongs to someone else. Upload the file again.
201
+ * Code: `upload_not_found`.
202
+ */
203
+ export declare class KoolbaseUploadNotFoundError extends KoolbaseStorageError {
204
+ constructor(message?: string);
205
+ }
206
+ /**
207
+ * The bucket is busy with other changes (a large move, many uploads at
208
+ * once). Nothing was changed; retry shortly. Code: `bucket_busy`.
209
+ */
210
+ export declare class KoolbaseStorageBusyError extends KoolbaseStorageError {
211
+ constructor(message?: string);
212
+ }
213
+ /**
214
+ * A listing's cursor was not one this listing issued -- made up, or from a
215
+ * listing with another sort or order. Start again from the first page.
216
+ * Code: `invalid_cursor`.
217
+ */
218
+ export declare class KoolbaseStorageInvalidCursorError extends KoolbaseStorageError {
219
+ constructor(message?: string);
220
+ }
221
+ /**
222
+ * The bucket's rules allow this only to someone signed in, and nobody is.
223
+ * Not a session that ended: the SDK's session is left alone. Code:
224
+ * `sign_in_required`.
225
+ */
226
+ export declare class KoolbaseStorageSignInRequiredError extends KoolbaseStorageError {
227
+ constructor(message?: string);
228
+ }
190
229
  /**
191
230
  * Maps a non-2xx storage-layer response to a typed
192
231
  * {@link KoolbaseStorageError}, preferring the server's stable `code` and
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.KoolbaseStorageMetadataInvalidError = exports.KoolbaseStorageMimeTypeError = exports.KoolbaseStorageFileTooLargeError = exports.KoolbaseStorageQuotaError = exports.KoolbaseCapBelowUsageError = exports.KoolbaseUploadExpiredError = exports.KoolbaseUploadURLFailedError = exports.KoolbaseStoragePermissionError = exports.KoolbaseStorageValidationError = exports.KoolbaseStorageNotFoundError = exports.KoolbaseStorageConflictError = exports.KoolbaseStorageProjectIdentityError = exports.KoolbaseStorageError = void 0;
3
+ exports.KoolbaseStorageSignInRequiredError = exports.KoolbaseStorageInvalidCursorError = exports.KoolbaseStorageBusyError = exports.KoolbaseUploadNotFoundError = exports.KoolbaseUploadCancelledError = exports.KoolbaseStorageMetadataInvalidError = exports.KoolbaseStorageMimeTypeError = exports.KoolbaseStorageFileTooLargeError = exports.KoolbaseStorageQuotaError = exports.KoolbaseCapBelowUsageError = exports.KoolbaseUploadExpiredError = exports.KoolbaseUploadURLFailedError = exports.KoolbaseStoragePermissionError = exports.KoolbaseStorageValidationError = exports.KoolbaseStorageNotFoundError = exports.KoolbaseStorageConflictError = exports.KoolbaseStorageProjectIdentityError = exports.KoolbaseStorageError = void 0;
4
4
  exports.koolbaseStorageError = koolbaseStorageError;
5
5
  exports.koolbaseStorageErrorFromResponse = koolbaseStorageErrorFromResponse;
6
6
  const errors_js_1 = require("./errors.js");
@@ -252,6 +252,70 @@ class KoolbaseStorageMetadataInvalidError extends KoolbaseStorageError {
252
252
  }
253
253
  }
254
254
  exports.KoolbaseStorageMetadataInvalidError = KoolbaseStorageMetadataInvalidError;
255
+ /**
256
+ * An upload that was cancelled -- by `KoolbaseUploadTask.cancel()` or the
257
+ * upload's `signal` -- before the server began checking it. Nothing was
258
+ * stored. Code: `upload_cancelled`.
259
+ */
260
+ class KoolbaseUploadCancelledError extends KoolbaseStorageError {
261
+ constructor(message) {
262
+ super(message ?? 'The upload was cancelled', 'upload_cancelled');
263
+ this.name = 'KoolbaseUploadCancelledError';
264
+ Object.setPrototypeOf(this, KoolbaseUploadCancelledError.prototype);
265
+ }
266
+ }
267
+ exports.KoolbaseUploadCancelledError = KoolbaseUploadCancelledError;
268
+ /**
269
+ * The confirm named an upload the server does not have: it expired, was
270
+ * confirmed already, or belongs to someone else. Upload the file again.
271
+ * Code: `upload_not_found`.
272
+ */
273
+ class KoolbaseUploadNotFoundError extends KoolbaseStorageError {
274
+ constructor(message) {
275
+ super(message ?? 'This upload was not found -- please upload the file again', 'upload_not_found');
276
+ this.name = 'KoolbaseUploadNotFoundError';
277
+ Object.setPrototypeOf(this, KoolbaseUploadNotFoundError.prototype);
278
+ }
279
+ }
280
+ exports.KoolbaseUploadNotFoundError = KoolbaseUploadNotFoundError;
281
+ /**
282
+ * The bucket is busy with other changes (a large move, many uploads at
283
+ * once). Nothing was changed; retry shortly. Code: `bucket_busy`.
284
+ */
285
+ class KoolbaseStorageBusyError extends KoolbaseStorageError {
286
+ constructor(message) {
287
+ super(message ?? 'The bucket is busy -- try again shortly', 'bucket_busy');
288
+ this.name = 'KoolbaseStorageBusyError';
289
+ Object.setPrototypeOf(this, KoolbaseStorageBusyError.prototype);
290
+ }
291
+ }
292
+ exports.KoolbaseStorageBusyError = KoolbaseStorageBusyError;
293
+ /**
294
+ * A listing's cursor was not one this listing issued -- made up, or from a
295
+ * listing with another sort or order. Start again from the first page.
296
+ * Code: `invalid_cursor`.
297
+ */
298
+ class KoolbaseStorageInvalidCursorError extends KoolbaseStorageError {
299
+ constructor(message) {
300
+ super(message ?? 'The cursor is not valid for this listing', 'invalid_cursor');
301
+ this.name = 'KoolbaseStorageInvalidCursorError';
302
+ Object.setPrototypeOf(this, KoolbaseStorageInvalidCursorError.prototype);
303
+ }
304
+ }
305
+ exports.KoolbaseStorageInvalidCursorError = KoolbaseStorageInvalidCursorError;
306
+ /**
307
+ * The bucket's rules allow this only to someone signed in, and nobody is.
308
+ * Not a session that ended: the SDK's session is left alone. Code:
309
+ * `sign_in_required`.
310
+ */
311
+ class KoolbaseStorageSignInRequiredError extends KoolbaseStorageError {
312
+ constructor(message) {
313
+ super(message ?? 'Sign in to do this', 'sign_in_required');
314
+ this.name = 'KoolbaseStorageSignInRequiredError';
315
+ Object.setPrototypeOf(this, KoolbaseStorageSignInRequiredError.prototype);
316
+ }
317
+ }
318
+ exports.KoolbaseStorageSignInRequiredError = KoolbaseStorageSignInRequiredError;
255
319
  /**
256
320
  * Maps a non-2xx storage-layer response to a typed
257
321
  * {@link KoolbaseStorageError}, preferring the server's stable `code` and
@@ -289,6 +353,16 @@ function koolbaseStorageError(status, body, fallbackMessage = 'Storage request f
289
353
  return new KoolbaseStorageMetadataInvalidError(message, body?.detail);
290
354
  case 'upload_url_failed':
291
355
  return new KoolbaseUploadURLFailedError(message);
356
+ case 'upload_not_found':
357
+ return new KoolbaseUploadNotFoundError(message);
358
+ case 'bucket_busy':
359
+ return new KoolbaseStorageBusyError(message);
360
+ case 'invalid_cursor':
361
+ return new KoolbaseStorageInvalidCursorError(message);
362
+ case 'sign_in_required':
363
+ return new KoolbaseStorageSignInRequiredError(message);
364
+ case 'not_allowed':
365
+ return new KoolbaseStoragePermissionError(message);
292
366
  }
293
367
  // ─── status fallback (pre-code servers or uncoded paths) ───
294
368
  switch (status) {