@insforge/sdk 1.0.2 → 1.0.3-dev.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
@@ -177,8 +177,31 @@ var HttpClient = class {
177
177
  // src/lib/token-manager.ts
178
178
  var TOKEN_KEY = "insforge-auth-token";
179
179
  var USER_KEY = "insforge-auth-user";
180
+ var CSRF_TOKEN_COOKIE = "insforge_csrf_token";
181
+ function getCsrfToken() {
182
+ if (typeof document === "undefined") return null;
183
+ const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
184
+ if (!match) return null;
185
+ return match.split("=")[1] || null;
186
+ }
187
+ function setCsrfToken(token) {
188
+ if (typeof document === "undefined") return;
189
+ const maxAge = 7 * 24 * 60 * 60;
190
+ const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
191
+ document.cookie = `${CSRF_TOKEN_COOKIE}=${encodeURIComponent(token)}; path=/; max-age=${maxAge}; SameSite=Lax${secure}`;
192
+ }
193
+ function clearCsrfToken() {
194
+ if (typeof document === "undefined") return;
195
+ const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
196
+ document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
197
+ }
180
198
  var TokenManager = class {
181
199
  constructor(storage) {
200
+ // In-memory storage
201
+ this.accessToken = null;
202
+ this.user = null;
203
+ // Mode: 'memory' (new backend) or 'storage' (legacy backend, default)
204
+ this._mode = "storage";
182
205
  if (storage) {
183
206
  this.storage = storage;
184
207
  } else if (typeof window !== "undefined" && window.localStorage) {
@@ -196,126 +219,117 @@ var TokenManager = class {
196
219
  };
197
220
  }
198
221
  }
199
- saveSession(session) {
200
- this.storage.setItem(TOKEN_KEY, session.accessToken);
201
- this.storage.setItem(USER_KEY, JSON.stringify(session.user));
222
+ /**
223
+ * Get current mode
224
+ */
225
+ get mode() {
226
+ return this._mode;
202
227
  }
203
- getSession() {
228
+ /**
229
+ * Set mode to memory (new backend with cookies + memory)
230
+ */
231
+ setMemoryMode() {
232
+ if (this._mode === "storage") {
233
+ this.storage.removeItem(TOKEN_KEY);
234
+ this.storage.removeItem(USER_KEY);
235
+ }
236
+ this._mode = "memory";
237
+ }
238
+ /**
239
+ * Set mode to storage (legacy backend with localStorage)
240
+ * Also loads existing session from localStorage
241
+ */
242
+ setStorageMode() {
243
+ this._mode = "storage";
244
+ this.loadFromStorage();
245
+ }
246
+ /**
247
+ * Load session from localStorage
248
+ */
249
+ loadFromStorage() {
204
250
  const token = this.storage.getItem(TOKEN_KEY);
205
251
  const userStr = this.storage.getItem(USER_KEY);
206
- if (!token || !userStr) {
207
- return null;
252
+ if (token && userStr) {
253
+ try {
254
+ this.accessToken = token;
255
+ this.user = JSON.parse(userStr);
256
+ } catch {
257
+ this.clearSession();
258
+ }
208
259
  }
209
- try {
210
- const user = JSON.parse(userStr);
211
- return { accessToken: token, user };
212
- } catch {
213
- this.clearSession();
214
- return null;
260
+ }
261
+ /**
262
+ * Save session (memory always, localStorage only in storage mode)
263
+ */
264
+ saveSession(session) {
265
+ this.accessToken = session.accessToken;
266
+ this.user = session.user;
267
+ if (this._mode === "storage") {
268
+ this.storage.setItem(TOKEN_KEY, session.accessToken);
269
+ this.storage.setItem(USER_KEY, JSON.stringify(session.user));
215
270
  }
216
271
  }
272
+ /**
273
+ * Get current session
274
+ */
275
+ getSession() {
276
+ this.loadFromStorage();
277
+ if (!this.accessToken || !this.user) return null;
278
+ return {
279
+ accessToken: this.accessToken,
280
+ user: this.user
281
+ };
282
+ }
283
+ /**
284
+ * Get access token
285
+ */
217
286
  getAccessToken() {
218
- const token = this.storage.getItem(TOKEN_KEY);
219
- return typeof token === "string" ? token : null;
287
+ this.loadFromStorage();
288
+ return this.accessToken;
220
289
  }
290
+ /**
291
+ * Set access token
292
+ */
293
+ setAccessToken(token) {
294
+ this.accessToken = token;
295
+ if (this._mode === "storage") {
296
+ this.storage.setItem(TOKEN_KEY, token);
297
+ }
298
+ }
299
+ /**
300
+ * Get user
301
+ */
302
+ getUser() {
303
+ return this.user;
304
+ }
305
+ /**
306
+ * Set user
307
+ */
308
+ setUser(user) {
309
+ this.user = user;
310
+ if (this._mode === "storage") {
311
+ this.storage.setItem(USER_KEY, JSON.stringify(user));
312
+ }
313
+ }
314
+ /**
315
+ * Clear session (both memory and localStorage)
316
+ */
221
317
  clearSession() {
318
+ this.accessToken = null;
319
+ this.user = null;
222
320
  this.storage.removeItem(TOKEN_KEY);
223
321
  this.storage.removeItem(USER_KEY);
224
322
  }
225
- };
226
-
227
- // src/modules/database-postgrest.ts
228
- var import_postgrest_js = require("@supabase/postgrest-js");
229
- function createInsForgePostgrestFetch(httpClient, tokenManager) {
230
- return async (input, init) => {
231
- const url = typeof input === "string" ? input : input.toString();
232
- const urlObj = new URL(url);
233
- const tableName = urlObj.pathname.slice(1);
234
- const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
235
- const token = tokenManager.getAccessToken();
236
- const httpHeaders = httpClient.getHeaders();
237
- const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
238
- const headers = new Headers(init?.headers);
239
- if (authToken && !headers.has("Authorization")) {
240
- headers.set("Authorization", `Bearer ${authToken}`);
241
- }
242
- const response = await fetch(insforgeUrl, {
243
- ...init,
244
- headers
245
- });
246
- return response;
247
- };
248
- }
249
- var Database = class {
250
- constructor(httpClient, tokenManager) {
251
- this.postgrest = new import_postgrest_js.PostgrestClient("http://dummy", {
252
- fetch: createInsForgePostgrestFetch(httpClient, tokenManager),
253
- headers: {}
254
- });
255
- }
256
323
  /**
257
- * Create a query builder for a table
258
- *
259
- * @example
260
- * // Basic query
261
- * const { data, error } = await client.database
262
- * .from('posts')
263
- * .select('*')
264
- * .eq('user_id', userId);
265
- *
266
- * // With count (Supabase style!)
267
- * const { data, error, count } = await client.database
268
- * .from('posts')
269
- * .select('*', { count: 'exact' })
270
- * .range(0, 9);
271
- *
272
- * // Just get count, no data
273
- * const { count } = await client.database
274
- * .from('posts')
275
- * .select('*', { count: 'exact', head: true });
276
- *
277
- * // Complex queries with OR
278
- * const { data } = await client.database
279
- * .from('posts')
280
- * .select('*, users!inner(*)')
281
- * .or('status.eq.active,status.eq.pending');
282
- *
283
- * // All features work:
284
- * - Nested selects
285
- * - Foreign key expansion
286
- * - OR/AND/NOT conditions
287
- * - Count with head
288
- * - Range pagination
289
- * - Upserts
324
+ * Check if there's a session in localStorage (for legacy detection)
290
325
  */
291
- from(table) {
292
- return this.postgrest.from(table);
326
+ hasStoredSession() {
327
+ const token = this.storage.getItem(TOKEN_KEY);
328
+ return !!token;
293
329
  }
294
330
  };
295
331
 
296
332
  // src/modules/auth.ts
297
- function convertDbProfileToCamelCase(dbProfile) {
298
- const result = {
299
- id: dbProfile.id
300
- };
301
- Object.keys(dbProfile).forEach((key) => {
302
- result[key] = dbProfile[key];
303
- if (key.includes("_")) {
304
- const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
305
- result[camelKey] = dbProfile[key];
306
- }
307
- });
308
- return result;
309
- }
310
- function convertCamelCaseToDbProfile(profile) {
311
- const dbProfile = {};
312
- Object.keys(profile).forEach((key) => {
313
- if (profile[key] === void 0) return;
314
- const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
315
- dbProfile[snakeKey] = profile[key];
316
- });
317
- return dbProfile;
318
- }
319
333
  function isHostedAuthEnvironment() {
320
334
  if (typeof window === "undefined") {
321
335
  return false;
@@ -333,12 +347,11 @@ var Auth = class {
333
347
  constructor(http, tokenManager) {
334
348
  this.http = http;
335
349
  this.tokenManager = tokenManager;
336
- this.database = new Database(http, tokenManager);
337
350
  this.detectAuthCallback();
338
351
  }
339
352
  /**
340
353
  * Automatically detect and handle OAuth callback parameters in the URL
341
- * This runs on initialization to seamlessly complete the OAuth flow
354
+ * This runs after initialization to seamlessly complete the OAuth flow
342
355
  * Matches the backend's OAuth callback response (backend/src/api/routes/auth.ts:540-544)
343
356
  */
344
357
  detectAuthCallback() {
@@ -349,13 +362,19 @@ var Auth = class {
349
362
  const userId = params.get("user_id");
350
363
  const email = params.get("email");
351
364
  const name = params.get("name");
365
+ const csrfToken = params.get("csrf_token");
352
366
  if (accessToken && userId && email) {
367
+ if (csrfToken) {
368
+ this.tokenManager.setMemoryMode();
369
+ setCsrfToken(csrfToken);
370
+ }
353
371
  const session = {
354
372
  accessToken,
355
373
  user: {
356
374
  id: userId,
357
375
  email,
358
- name: name || "",
376
+ profile: null,
377
+ metadata: null,
359
378
  // These fields are not provided by backend OAuth callback
360
379
  // They'll be populated when calling getCurrentUser()
361
380
  emailVerified: false,
@@ -370,6 +389,7 @@ var Auth = class {
370
389
  url.searchParams.delete("user_id");
371
390
  url.searchParams.delete("email");
372
391
  url.searchParams.delete("name");
392
+ url.searchParams.delete("csrf_token");
373
393
  if (params.has("error")) {
374
394
  url.searchParams.delete("error");
375
395
  }
@@ -385,15 +405,16 @@ var Auth = class {
385
405
  async signUp(request) {
386
406
  try {
387
407
  const response = await this.http.post("/api/auth/users", request);
388
- if (response.accessToken && response.user) {
408
+ if (response.accessToken && response.user && !isHostedAuthEnvironment()) {
389
409
  const session = {
390
410
  accessToken: response.accessToken,
391
411
  user: response.user
392
412
  };
393
- if (!isHostedAuthEnvironment()) {
394
- this.tokenManager.saveSession(session);
395
- }
413
+ this.tokenManager.saveSession(session);
396
414
  this.http.setAuthToken(response.accessToken);
415
+ if (response.csrfToken) {
416
+ setCsrfToken(response.csrfToken);
417
+ }
397
418
  }
398
419
  return {
399
420
  data: response,
@@ -419,21 +440,17 @@ var Auth = class {
419
440
  async signInWithPassword(request) {
420
441
  try {
421
442
  const response = await this.http.post("/api/auth/sessions", request);
422
- const session = {
423
- accessToken: response.accessToken || "",
424
- user: response.user || {
425
- id: "",
426
- email: "",
427
- name: "",
428
- emailVerified: false,
429
- createdAt: "",
430
- updatedAt: ""
431
- }
432
- };
433
443
  if (!isHostedAuthEnvironment()) {
444
+ const session = {
445
+ accessToken: response.accessToken,
446
+ user: response.user
447
+ };
434
448
  this.tokenManager.saveSession(session);
449
+ this.http.setAuthToken(response.accessToken);
450
+ if (response.csrfToken) {
451
+ setCsrfToken(response.csrfToken);
452
+ }
435
453
  }
436
- this.http.setAuthToken(response.accessToken || "");
437
454
  return {
438
455
  data: response,
439
456
  error: null
@@ -491,8 +508,13 @@ var Auth = class {
491
508
  */
492
509
  async signOut() {
493
510
  try {
511
+ try {
512
+ await this.http.post("/api/auth/logout", void 0, { credentials: "include" });
513
+ } catch {
514
+ }
494
515
  this.tokenManager.clearSession();
495
516
  this.http.setAuthToken(null);
517
+ clearCsrfToken();
496
518
  return { error: null };
497
519
  } catch (error) {
498
520
  return {
@@ -553,14 +575,9 @@ var Auth = class {
553
575
  }
554
576
  this.http.setAuthToken(session.accessToken);
555
577
  const authResponse = await this.http.get("/api/auth/sessions/current");
556
- const { data: profile, error: profileError } = await this.database.from("users").select("*").eq("id", authResponse.user.id).single();
557
- if (profileError && profileError.code !== "PGRST116") {
558
- return { data: null, error: profileError };
559
- }
560
578
  return {
561
579
  data: {
562
- user: authResponse.user,
563
- profile: profile ? convertDbProfileToCamelCase(profile) : null
580
+ user: authResponse.user
564
581
  },
565
582
  error: null
566
583
  };
@@ -584,29 +601,80 @@ var Auth = class {
584
601
  }
585
602
  /**
586
603
  * Get any user's profile by ID
587
- * Returns profile information from the users table (dynamic fields)
604
+ * Returns profile information from the users table
588
605
  */
589
606
  async getProfile(userId) {
590
- const { data, error } = await this.database.from("users").select("*").eq("id", userId).single();
591
- if (error && error.code === "PGRST116") {
592
- return { data: null, error: null };
593
- }
594
- if (data) {
595
- return { data: convertDbProfileToCamelCase(data), error: null };
607
+ try {
608
+ const response = await this.http.get(`/api/auth/profiles/${userId}`);
609
+ return {
610
+ data: response,
611
+ error: null
612
+ };
613
+ } catch (error) {
614
+ if (error instanceof InsForgeError) {
615
+ return { data: null, error };
616
+ }
617
+ return {
618
+ data: null,
619
+ error: new InsForgeError(
620
+ "An unexpected error occurred while fetching user profile",
621
+ 500,
622
+ "UNEXPECTED_ERROR"
623
+ )
624
+ };
596
625
  }
597
- return { data: null, error };
598
626
  }
599
627
  /**
600
628
  * Get the current session (only session data, no API call)
601
629
  * Returns the stored JWT token and basic user info from local storage
602
630
  */
603
- getCurrentSession() {
631
+ async getCurrentSession() {
604
632
  try {
605
633
  const session = this.tokenManager.getSession();
606
- if (session?.accessToken) {
634
+ if (session) {
607
635
  this.http.setAuthToken(session.accessToken);
608
636
  return { data: { session }, error: null };
609
637
  }
638
+ if (typeof window !== "undefined") {
639
+ try {
640
+ const csrfToken = getCsrfToken();
641
+ const response = await this.http.post(
642
+ "/api/auth/refresh",
643
+ void 0,
644
+ {
645
+ headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
646
+ credentials: "include"
647
+ }
648
+ );
649
+ if (response.accessToken) {
650
+ this.tokenManager.setMemoryMode();
651
+ this.tokenManager.setAccessToken(response.accessToken);
652
+ this.http.setAuthToken(response.accessToken);
653
+ if (response.user) {
654
+ this.tokenManager.setUser(response.user);
655
+ }
656
+ if (response.csrfToken) {
657
+ setCsrfToken(response.csrfToken);
658
+ }
659
+ return {
660
+ data: { session: this.tokenManager.getSession() },
661
+ error: null
662
+ };
663
+ }
664
+ } catch (error) {
665
+ if (error instanceof InsForgeError) {
666
+ if (error.statusCode === 404) {
667
+ this.tokenManager.setStorageMode();
668
+ const session2 = this.tokenManager.getSession();
669
+ if (session2) {
670
+ return { data: { session: session2 }, error: null };
671
+ }
672
+ return { data: { session: null }, error: null };
673
+ }
674
+ return { data: { session: null }, error };
675
+ }
676
+ }
677
+ }
610
678
  return { data: { session: null }, error: null };
611
679
  } catch (error) {
612
680
  if (error instanceof InsForgeError) {
@@ -625,42 +693,31 @@ var Auth = class {
625
693
  /**
626
694
  * Set/Update the current user's profile
627
695
  * Updates profile information in the users table (supports any dynamic fields)
696
+ * Requires authentication
628
697
  */
629
698
  async setProfile(profile) {
630
- const session = this.tokenManager.getSession();
631
- if (!session?.accessToken) {
699
+ try {
700
+ const response = await this.http.patch(
701
+ "/api/auth/profiles/current",
702
+ { profile }
703
+ );
704
+ return {
705
+ data: response,
706
+ error: null
707
+ };
708
+ } catch (error) {
709
+ if (error instanceof InsForgeError) {
710
+ return { data: null, error };
711
+ }
632
712
  return {
633
713
  data: null,
634
714
  error: new InsForgeError(
635
- "No authenticated user found",
636
- 401,
637
- "UNAUTHENTICATED"
715
+ "An unexpected error occurred while updating user profile",
716
+ 500,
717
+ "UNEXPECTED_ERROR"
638
718
  )
639
719
  };
640
720
  }
641
- if (!session.user?.id) {
642
- const { data: data2, error: error2 } = await this.getCurrentUser();
643
- if (error2) {
644
- return { data: null, error: error2 };
645
- }
646
- if (data2?.user) {
647
- session.user = {
648
- id: data2.user.id,
649
- email: data2.user.email,
650
- name: "",
651
- emailVerified: false,
652
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
653
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
654
- };
655
- this.tokenManager.saveSession(session);
656
- }
657
- }
658
- const dbProfile = convertCamelCaseToDbProfile(profile);
659
- const { data, error } = await this.database.from("users").update(dbProfile).eq("id", session.user.id).select().single();
660
- if (data) {
661
- return { data: convertDbProfileToCamelCase(data), error: null };
662
- }
663
- return { data: null, error };
664
721
  }
665
722
  /**
666
723
  * Send email verification (code or link based on config)
@@ -814,13 +871,16 @@ var Auth = class {
814
871
  "/api/auth/email/verify",
815
872
  request
816
873
  );
817
- if (response.accessToken) {
874
+ if (!isHostedAuthEnvironment()) {
818
875
  const session = {
819
876
  accessToken: response.accessToken,
820
- user: response.user || {}
877
+ user: response.user
821
878
  };
822
879
  this.tokenManager.saveSession(session);
823
880
  this.http.setAuthToken(response.accessToken);
881
+ if (response.csrfToken) {
882
+ setCsrfToken(response.csrfToken);
883
+ }
824
884
  }
825
885
  return {
826
886
  data: response,
@@ -842,6 +902,75 @@ var Auth = class {
842
902
  }
843
903
  };
844
904
 
905
+ // src/modules/database-postgrest.ts
906
+ var import_postgrest_js = require("@supabase/postgrest-js");
907
+ function createInsForgePostgrestFetch(httpClient, tokenManager) {
908
+ return async (input, init) => {
909
+ const url = typeof input === "string" ? input : input.toString();
910
+ const urlObj = new URL(url);
911
+ const tableName = urlObj.pathname.slice(1);
912
+ const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
913
+ const token = tokenManager.getAccessToken();
914
+ const httpHeaders = httpClient.getHeaders();
915
+ const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
916
+ const headers = new Headers(init?.headers);
917
+ if (authToken && !headers.has("Authorization")) {
918
+ headers.set("Authorization", `Bearer ${authToken}`);
919
+ }
920
+ const response = await fetch(insforgeUrl, {
921
+ ...init,
922
+ headers
923
+ });
924
+ return response;
925
+ };
926
+ }
927
+ var Database = class {
928
+ constructor(httpClient, tokenManager) {
929
+ this.postgrest = new import_postgrest_js.PostgrestClient("http://dummy", {
930
+ fetch: createInsForgePostgrestFetch(httpClient, tokenManager),
931
+ headers: {}
932
+ });
933
+ }
934
+ /**
935
+ * Create a query builder for a table
936
+ *
937
+ * @example
938
+ * // Basic query
939
+ * const { data, error } = await client.database
940
+ * .from('posts')
941
+ * .select('*')
942
+ * .eq('user_id', userId);
943
+ *
944
+ * // With count (Supabase style!)
945
+ * const { data, error, count } = await client.database
946
+ * .from('posts')
947
+ * .select('*', { count: 'exact' })
948
+ * .range(0, 9);
949
+ *
950
+ * // Just get count, no data
951
+ * const { count } = await client.database
952
+ * .from('posts')
953
+ * .select('*', { count: 'exact', head: true });
954
+ *
955
+ * // Complex queries with OR
956
+ * const { data } = await client.database
957
+ * .from('posts')
958
+ * .select('*, users!inner(*)')
959
+ * .or('status.eq.active,status.eq.pending');
960
+ *
961
+ * // All features work:
962
+ * - Nested selects
963
+ * - Foreign key expansion
964
+ * - OR/AND/NOT conditions
965
+ * - Count with head
966
+ * - Range pagination
967
+ * - Upserts
968
+ */
969
+ from(table) {
970
+ return this.postgrest.from(table);
971
+ }
972
+ };
973
+
845
974
  // src/modules/storage.ts
846
975
  var StorageBucket = class {
847
976
  constructor(bucketName, http) {
@@ -1603,7 +1732,7 @@ var Emails = class {
1603
1732
  }
1604
1733
  /**
1605
1734
  * Send a custom HTML email
1606
- * @param options Email options ilncluding recipients, subject, and HTML content
1735
+ * @param options Email options including recipients, subject, and HTML content
1607
1736
  */
1608
1737
  async send(options) {
1609
1738
  try {
@@ -1636,10 +1765,7 @@ var InsForgeClient = class {
1636
1765
  if (existingSession?.accessToken) {
1637
1766
  this.http.setAuthToken(existingSession.accessToken);
1638
1767
  }
1639
- this.auth = new Auth(
1640
- this.http,
1641
- this.tokenManager
1642
- );
1768
+ this.auth = new Auth(this.http, this.tokenManager);
1643
1769
  this.database = new Database(this.http, this.tokenManager);
1644
1770
  this.storage = new Storage(this.http);
1645
1771
  this.ai = new AI(this.http);