@mitway/sdk 0.4.0 → 0.5.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/index.d.cts CHANGED
@@ -33,22 +33,42 @@ interface User {
33
33
  /**
34
34
  * Token Manager for the MITWAY-BaaS SDK.
35
35
  *
36
- * In-memory storage for the access token + user. Browser CSRF token lives
37
- * in a cookie so the cookie-based refresh flow works across page reloads.
36
+ * Stores the access token + user in memory and optionally persists them
37
+ * to `localStorage` so sessions survive page reloads (F5). Browser CSRF
38
+ * token lives in a cookie for the cookie-based refresh flow.
38
39
  */
39
40
 
41
+ interface TokenManagerOptions {
42
+ /** Persist session to localStorage so it survives page reloads. Default: true. */
43
+ persistSession?: boolean;
44
+ /** localStorage key. Default: 'mitway_baas_session'. */
45
+ storageKey?: string;
46
+ }
40
47
  declare class TokenManager {
41
48
  private accessToken;
49
+ private refreshToken;
42
50
  private user;
51
+ private readonly persistSession;
52
+ private readonly storageKey;
43
53
  /** Fired when the access token changes (used by long-lived consumers). */
44
54
  onTokenChange: (() => void) | null;
55
+ constructor(opts?: TokenManagerOptions);
45
56
  saveSession(session: AuthSession): void;
46
57
  getSession(): AuthSession | null;
47
58
  getAccessToken(): string | null;
48
59
  setAccessToken(token: string): void;
60
+ getRefreshToken(): string | null;
61
+ setRefreshToken(token: string | null): void;
49
62
  getUser(): User | null;
50
63
  setUser(user: User): void;
51
64
  clearSession(): void;
65
+ /**
66
+ * Restore the session from localStorage. Returns true if a persisted
67
+ * session was found and loaded into memory.
68
+ */
69
+ restoreSession(): boolean;
70
+ private persist;
71
+ private removePersisted;
52
72
  }
53
73
 
54
74
  /**
@@ -451,6 +471,18 @@ interface MitwayBaasConfig {
451
471
  * Realtime transport options. See `RealtimeOptions`.
452
472
  */
453
473
  realtime?: RealtimeOptions;
474
+ /**
475
+ * Persist the auth session to `localStorage` so it survives page reloads.
476
+ * Set to `false` for SSR / Node.js environments where `localStorage` is
477
+ * not available.
478
+ * @default true
479
+ */
480
+ persistSession?: boolean;
481
+ /**
482
+ * `localStorage` key used to persist the session.
483
+ * @default "mitway_baas_session"
484
+ */
485
+ storageKey?: string;
454
486
  }
455
487
  /**
456
488
  * Active user session in memory. Mirrors what the auth endpoints return.
@@ -458,6 +490,7 @@ interface MitwayBaasConfig {
458
490
  interface AuthSession {
459
491
  user: User;
460
492
  accessToken: string;
493
+ refreshToken?: string;
461
494
  expiresAt?: Date;
462
495
  }
463
496
  /**
@@ -654,6 +687,23 @@ declare class Auth {
654
687
  * not need to call it directly.
655
688
  */
656
689
  refreshSession(): Promise<AuthResult<AuthResponse>>;
690
+ /**
691
+ * Restore the session from localStorage and validate it with the backend.
692
+ * Call this once on app startup (e.g. in a React AuthProvider useEffect).
693
+ *
694
+ * Flow:
695
+ * 1. Read persisted session from localStorage.
696
+ * 2. Populate in-memory state (TokenManager + HttpClient).
697
+ * 3. Validate with `GET /api/auth/sessions/current`.
698
+ * - If the access token expired, the HttpClient auto-refresh kicks in
699
+ * using the persisted refresh token (sent in the POST body, not
700
+ * cookies — works cross-site).
701
+ * 4. Return the validated user or an error.
702
+ *
703
+ * If no persisted session exists, returns `{ data: null, error }` — the
704
+ * app should show the login page.
705
+ */
706
+ initialize(): Promise<AuthResult<AuthResponse>>;
657
707
  /**
658
708
  * Get the current in-memory session, or null if the user is not signed in.
659
709
  * Synchronous — does not hit the network.
package/dist/index.d.ts CHANGED
@@ -33,22 +33,42 @@ interface User {
33
33
  /**
34
34
  * Token Manager for the MITWAY-BaaS SDK.
35
35
  *
36
- * In-memory storage for the access token + user. Browser CSRF token lives
37
- * in a cookie so the cookie-based refresh flow works across page reloads.
36
+ * Stores the access token + user in memory and optionally persists them
37
+ * to `localStorage` so sessions survive page reloads (F5). Browser CSRF
38
+ * token lives in a cookie for the cookie-based refresh flow.
38
39
  */
39
40
 
41
+ interface TokenManagerOptions {
42
+ /** Persist session to localStorage so it survives page reloads. Default: true. */
43
+ persistSession?: boolean;
44
+ /** localStorage key. Default: 'mitway_baas_session'. */
45
+ storageKey?: string;
46
+ }
40
47
  declare class TokenManager {
41
48
  private accessToken;
49
+ private refreshToken;
42
50
  private user;
51
+ private readonly persistSession;
52
+ private readonly storageKey;
43
53
  /** Fired when the access token changes (used by long-lived consumers). */
44
54
  onTokenChange: (() => void) | null;
55
+ constructor(opts?: TokenManagerOptions);
45
56
  saveSession(session: AuthSession): void;
46
57
  getSession(): AuthSession | null;
47
58
  getAccessToken(): string | null;
48
59
  setAccessToken(token: string): void;
60
+ getRefreshToken(): string | null;
61
+ setRefreshToken(token: string | null): void;
49
62
  getUser(): User | null;
50
63
  setUser(user: User): void;
51
64
  clearSession(): void;
65
+ /**
66
+ * Restore the session from localStorage. Returns true if a persisted
67
+ * session was found and loaded into memory.
68
+ */
69
+ restoreSession(): boolean;
70
+ private persist;
71
+ private removePersisted;
52
72
  }
53
73
 
54
74
  /**
@@ -451,6 +471,18 @@ interface MitwayBaasConfig {
451
471
  * Realtime transport options. See `RealtimeOptions`.
452
472
  */
453
473
  realtime?: RealtimeOptions;
474
+ /**
475
+ * Persist the auth session to `localStorage` so it survives page reloads.
476
+ * Set to `false` for SSR / Node.js environments where `localStorage` is
477
+ * not available.
478
+ * @default true
479
+ */
480
+ persistSession?: boolean;
481
+ /**
482
+ * `localStorage` key used to persist the session.
483
+ * @default "mitway_baas_session"
484
+ */
485
+ storageKey?: string;
454
486
  }
455
487
  /**
456
488
  * Active user session in memory. Mirrors what the auth endpoints return.
@@ -458,6 +490,7 @@ interface MitwayBaasConfig {
458
490
  interface AuthSession {
459
491
  user: User;
460
492
  accessToken: string;
493
+ refreshToken?: string;
461
494
  expiresAt?: Date;
462
495
  }
463
496
  /**
@@ -654,6 +687,23 @@ declare class Auth {
654
687
  * not need to call it directly.
655
688
  */
656
689
  refreshSession(): Promise<AuthResult<AuthResponse>>;
690
+ /**
691
+ * Restore the session from localStorage and validate it with the backend.
692
+ * Call this once on app startup (e.g. in a React AuthProvider useEffect).
693
+ *
694
+ * Flow:
695
+ * 1. Read persisted session from localStorage.
696
+ * 2. Populate in-memory state (TokenManager + HttpClient).
697
+ * 3. Validate with `GET /api/auth/sessions/current`.
698
+ * - If the access token expired, the HttpClient auto-refresh kicks in
699
+ * using the persisted refresh token (sent in the POST body, not
700
+ * cookies — works cross-site).
701
+ * 4. Return the validated user or an error.
702
+ *
703
+ * If no persisted session exists, returns `{ data: null, error }` — the
704
+ * app should show the login page.
705
+ */
706
+ initialize(): Promise<AuthResult<AuthResponse>>;
657
707
  /**
658
708
  * Get the current in-memory session, or null if the user is not signed in.
659
709
  * Synchronous — does not hit the network.
package/dist/index.js CHANGED
@@ -161,6 +161,7 @@ var Logger = class {
161
161
 
162
162
  // src/lib/token-manager.ts
163
163
  var CSRF_TOKEN_COOKIE = "mitway_baas_csrf_token";
164
+ var DEFAULT_STORAGE_KEY = "mitway_baas_session";
164
165
  function getCsrfToken() {
165
166
  if (typeof document === "undefined") return null;
166
167
  const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
@@ -180,13 +181,24 @@ function clearCsrfToken() {
180
181
  }
181
182
  var TokenManager = class {
182
183
  accessToken = null;
184
+ refreshToken = null;
183
185
  user = null;
186
+ persistSession;
187
+ storageKey;
184
188
  /** Fired when the access token changes (used by long-lived consumers). */
185
189
  onTokenChange = null;
190
+ constructor(opts) {
191
+ this.persistSession = opts?.persistSession ?? true;
192
+ this.storageKey = opts?.storageKey ?? DEFAULT_STORAGE_KEY;
193
+ }
186
194
  saveSession(session) {
187
195
  const tokenChanged = session.accessToken !== this.accessToken;
188
196
  this.accessToken = session.accessToken;
189
197
  this.user = session.user;
198
+ if (session.refreshToken !== void 0) {
199
+ this.refreshToken = session.refreshToken ?? null;
200
+ }
201
+ this.persist();
190
202
  if (tokenChanged && this.onTokenChange) {
191
203
  this.onTokenChange();
192
204
  }
@@ -195,6 +207,7 @@ var TokenManager = class {
195
207
  if (!this.accessToken || !this.user) return null;
196
208
  return {
197
209
  accessToken: this.accessToken,
210
+ refreshToken: this.refreshToken ?? void 0,
198
211
  user: this.user
199
212
  };
200
213
  }
@@ -204,24 +217,76 @@ var TokenManager = class {
204
217
  setAccessToken(token) {
205
218
  const tokenChanged = token !== this.accessToken;
206
219
  this.accessToken = token;
220
+ this.persist();
207
221
  if (tokenChanged && this.onTokenChange) {
208
222
  this.onTokenChange();
209
223
  }
210
224
  }
225
+ getRefreshToken() {
226
+ return this.refreshToken;
227
+ }
228
+ setRefreshToken(token) {
229
+ this.refreshToken = token;
230
+ this.persist();
231
+ }
211
232
  getUser() {
212
233
  return this.user;
213
234
  }
214
235
  setUser(user) {
215
236
  this.user = user;
237
+ this.persist();
216
238
  }
217
239
  clearSession() {
218
240
  const hadToken = this.accessToken !== null;
219
241
  this.accessToken = null;
242
+ this.refreshToken = null;
220
243
  this.user = null;
244
+ this.removePersisted();
221
245
  if (hadToken && this.onTokenChange) {
222
246
  this.onTokenChange();
223
247
  }
224
248
  }
249
+ /**
250
+ * Restore the session from localStorage. Returns true if a persisted
251
+ * session was found and loaded into memory.
252
+ */
253
+ restoreSession() {
254
+ if (!this.persistSession || typeof localStorage === "undefined") return false;
255
+ try {
256
+ const raw = localStorage.getItem(this.storageKey);
257
+ if (!raw) return false;
258
+ const stored = JSON.parse(raw);
259
+ if (!stored.accessToken || !stored.user) return false;
260
+ this.accessToken = stored.accessToken;
261
+ this.refreshToken = stored.refreshToken ?? null;
262
+ this.user = stored.user;
263
+ return true;
264
+ } catch {
265
+ return false;
266
+ }
267
+ }
268
+ persist() {
269
+ if (!this.persistSession || typeof localStorage === "undefined") return;
270
+ if (!this.accessToken || !this.user) return;
271
+ try {
272
+ const data = {
273
+ accessToken: this.accessToken,
274
+ user: this.user
275
+ };
276
+ if (this.refreshToken) {
277
+ data.refreshToken = this.refreshToken;
278
+ }
279
+ localStorage.setItem(this.storageKey, JSON.stringify(data));
280
+ } catch {
281
+ }
282
+ }
283
+ removePersisted() {
284
+ if (!this.persistSession || typeof localStorage === "undefined") return;
285
+ try {
286
+ localStorage.removeItem(this.storageKey);
287
+ } catch {
288
+ }
289
+ }
225
290
  };
226
291
 
227
292
  // src/lib/auth-envelope.ts
@@ -641,6 +706,7 @@ var Auth = class {
641
706
  saveSessionFromResponse(response) {
642
707
  const session = {
643
708
  accessToken: response.accessToken,
709
+ refreshToken: response.refreshToken,
644
710
  user: response.user
645
711
  };
646
712
  if (response.csrfToken) {
@@ -734,6 +800,70 @@ var Auth = class {
734
800
  return wrapError(error, "Session refresh failed");
735
801
  }
736
802
  }
803
+ /**
804
+ * Restore the session from localStorage and validate it with the backend.
805
+ * Call this once on app startup (e.g. in a React AuthProvider useEffect).
806
+ *
807
+ * Flow:
808
+ * 1. Read persisted session from localStorage.
809
+ * 2. Populate in-memory state (TokenManager + HttpClient).
810
+ * 3. Validate with `GET /api/auth/sessions/current`.
811
+ * - If the access token expired, the HttpClient auto-refresh kicks in
812
+ * using the persisted refresh token (sent in the POST body, not
813
+ * cookies — works cross-site).
814
+ * 4. Return the validated user or an error.
815
+ *
816
+ * If no persisted session exists, returns `{ data: null, error }` — the
817
+ * app should show the login page.
818
+ */
819
+ async initialize() {
820
+ const restored = this.tokenManager.restoreSession();
821
+ if (!restored) {
822
+ return {
823
+ data: null,
824
+ error: new MitwayBaasError("No persisted session", 0, "NO_SESSION")
825
+ };
826
+ }
827
+ const session = this.tokenManager.getSession();
828
+ if (!session) {
829
+ return {
830
+ data: null,
831
+ error: new MitwayBaasError("No persisted session", 0, "NO_SESSION")
832
+ };
833
+ }
834
+ this.http.setAuthToken(session.accessToken);
835
+ const refreshToken = this.tokenManager.getRefreshToken();
836
+ if (refreshToken) {
837
+ this.http.setRefreshToken(refreshToken);
838
+ }
839
+ try {
840
+ const response = await this.http.get(
841
+ "/api/auth/sessions/current"
842
+ );
843
+ if (response?.user) {
844
+ this.tokenManager.setUser(response.user);
845
+ return {
846
+ data: {
847
+ user: response.user,
848
+ accessToken: session.accessToken
849
+ },
850
+ error: null
851
+ };
852
+ }
853
+ this.tokenManager.clearSession();
854
+ this.http.setAuthToken(null);
855
+ this.http.setRefreshToken(null);
856
+ return {
857
+ data: null,
858
+ error: new MitwayBaasError("Invalid session", 401, "INVALID_SESSION")
859
+ };
860
+ } catch (error) {
861
+ this.tokenManager.clearSession();
862
+ this.http.setAuthToken(null);
863
+ this.http.setRefreshToken(null);
864
+ return wrapError(error, "Session restore failed");
865
+ }
866
+ }
737
867
  /**
738
868
  * Get the current in-memory session, or null if the user is not signed in.
739
869
  * Synchronous — does not hit the network.
@@ -1877,7 +2007,10 @@ var MitwayBaasClient = class {
1877
2007
  storage;
1878
2008
  constructor(config = {}) {
1879
2009
  const logger = new Logger(config.debug);
1880
- this.tokenManager = new TokenManager();
2010
+ this.tokenManager = new TokenManager({
2011
+ persistSession: config.persistSession,
2012
+ storageKey: config.storageKey
2013
+ });
1881
2014
  this.http = new HttpClient(config, this.tokenManager, logger);
1882
2015
  this.auth = new Auth(this.http, this.tokenManager);
1883
2016
  this.database = new Database(this.http, this.tokenManager, config.anonKey);