@insforge/sdk 1.1.0 → 1.1.2-edge.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.mts CHANGED
@@ -53,7 +53,7 @@ interface TokenStorage {
53
53
  removeItem(key: string): void | Promise<void>;
54
54
  }
55
55
  interface AuthSession {
56
- user: UserSchema;
56
+ user: UserSchema | null;
57
57
  accessToken: string;
58
58
  expiresAt?: Date;
59
59
  }
@@ -166,11 +166,19 @@ declare class TokenManager {
166
166
  declare class Auth {
167
167
  private http;
168
168
  private tokenManager;
169
+ /**
170
+ * Promise that resolves when OAuth callback handling is complete.
171
+ * Resolves immediately if no OAuth callback is detected in the URL.
172
+ */
173
+ private authCallbackHandled;
169
174
  constructor(http: HttpClient, tokenManager: TokenManager);
170
175
  /**
171
176
  * Automatically detect and handle OAuth callback parameters in the URL
172
177
  * This runs after initialization to seamlessly complete the OAuth flow
173
- * Matches the backend's OAuth callback response (backend/src/api/routes/auth.ts:540-544)
178
+ *
179
+ * Supports two flows:
180
+ * - PKCE flow (new): Backend returns `insforge_code` param, exchanged for tokens
181
+ * - Legacy flow: Backend returns tokens directly in URL (backward compatible)
174
182
  */
175
183
  private detectAuthCallback;
176
184
  /**
@@ -189,6 +197,7 @@ declare class Auth {
189
197
  }>;
190
198
  /**
191
199
  * Sign in with OAuth provider
200
+ * Uses PKCE (Proof Key for Code Exchange) for enhanced security
192
201
  */
193
202
  signInWithOAuth(options: {
194
203
  provider: OAuthProvidersSchema;
@@ -198,9 +207,45 @@ declare class Auth {
198
207
  data: {
199
208
  url?: string;
200
209
  provider?: string;
210
+ codeVerifier?: string;
201
211
  };
202
212
  error: InsForgeError | null;
203
213
  }>;
214
+ /**
215
+ * Exchange OAuth authorization code for tokens (PKCE flow)
216
+ *
217
+ * After OAuth callback redirects with an `insforge_code` parameter, call this method
218
+ * to exchange it for access tokens. The code verifier is automatically
219
+ * retrieved from sessionStorage if available.
220
+ *
221
+ * Note: This is called automatically by the SDK on initialization. You typically
222
+ * don't need to call this directly unless using `skipBrowserRedirect: true`.
223
+ *
224
+ * @param code - The authorization code from OAuth callback URL
225
+ * @param codeVerifier - Optional code verifier (auto-retrieved from sessionStorage if not provided)
226
+ * @returns Session data with access token and user info
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * // Automatic verifier retrieval (recommended for browser)
231
+ * const params = new URLSearchParams(window.location.search);
232
+ * const code = params.get('insforge_code');
233
+ * if (code) {
234
+ * const { data, error } = await insforge.auth.exchangeOAuthCode(code);
235
+ * }
236
+ *
237
+ * // Manual verifier (for custom flows)
238
+ * const { data, error } = await insforge.auth.exchangeOAuthCode(code, codeVerifier);
239
+ * ```
240
+ */
241
+ exchangeOAuthCode(code: string, codeVerifier?: string): Promise<{
242
+ data: {
243
+ accessToken: string;
244
+ user: UserSchema;
245
+ redirectTo?: string;
246
+ } | null;
247
+ error: InsForgeError | null;
248
+ }>;
204
249
  /**
205
250
  * Sign out the current user
206
251
  */
@@ -238,6 +283,7 @@ declare class Auth {
238
283
  /**
239
284
  * Get the current session (only session data, no API call)
240
285
  * Returns the stored JWT token and basic user info from local storage
286
+ * Automatically waits for any pending OAuth callback to complete first
241
287
  */
242
288
  getCurrentSession(): Promise<{
243
289
  data: {
@@ -395,6 +441,27 @@ declare class Database {
395
441
  * - Upserts
396
442
  */
397
443
  from(table: string): _supabase_postgrest_js.PostgrestQueryBuilder<any, any, any, string, unknown>;
444
+ /**
445
+ * Call a PostgreSQL function (RPC)
446
+ *
447
+ * @example
448
+ * // Call a function with parameters
449
+ * const { data, error } = await client.database
450
+ * .rpc('get_user_stats', { user_id: 123 });
451
+ *
452
+ * // Call a function with no parameters
453
+ * const { data, error } = await client.database
454
+ * .rpc('get_all_active_users');
455
+ *
456
+ * // With options (head, count, get)
457
+ * const { data, count } = await client.database
458
+ * .rpc('search_posts', { query: 'hello' }, { count: 'exact' });
459
+ */
460
+ rpc(fn: string, args?: Record<string, unknown>, options?: {
461
+ head?: boolean;
462
+ get?: boolean;
463
+ count?: 'exact' | 'planned' | 'estimated';
464
+ }): _supabase_postgrest_js.PostgrestFilterBuilder<any, any, any, any, string, null, "RPC">;
398
465
  }
399
466
 
400
467
  /**
package/dist/index.d.ts CHANGED
@@ -53,7 +53,7 @@ interface TokenStorage {
53
53
  removeItem(key: string): void | Promise<void>;
54
54
  }
55
55
  interface AuthSession {
56
- user: UserSchema;
56
+ user: UserSchema | null;
57
57
  accessToken: string;
58
58
  expiresAt?: Date;
59
59
  }
@@ -166,11 +166,19 @@ declare class TokenManager {
166
166
  declare class Auth {
167
167
  private http;
168
168
  private tokenManager;
169
+ /**
170
+ * Promise that resolves when OAuth callback handling is complete.
171
+ * Resolves immediately if no OAuth callback is detected in the URL.
172
+ */
173
+ private authCallbackHandled;
169
174
  constructor(http: HttpClient, tokenManager: TokenManager);
170
175
  /**
171
176
  * Automatically detect and handle OAuth callback parameters in the URL
172
177
  * This runs after initialization to seamlessly complete the OAuth flow
173
- * Matches the backend's OAuth callback response (backend/src/api/routes/auth.ts:540-544)
178
+ *
179
+ * Supports two flows:
180
+ * - PKCE flow (new): Backend returns `insforge_code` param, exchanged for tokens
181
+ * - Legacy flow: Backend returns tokens directly in URL (backward compatible)
174
182
  */
175
183
  private detectAuthCallback;
176
184
  /**
@@ -189,6 +197,7 @@ declare class Auth {
189
197
  }>;
190
198
  /**
191
199
  * Sign in with OAuth provider
200
+ * Uses PKCE (Proof Key for Code Exchange) for enhanced security
192
201
  */
193
202
  signInWithOAuth(options: {
194
203
  provider: OAuthProvidersSchema;
@@ -198,9 +207,45 @@ declare class Auth {
198
207
  data: {
199
208
  url?: string;
200
209
  provider?: string;
210
+ codeVerifier?: string;
201
211
  };
202
212
  error: InsForgeError | null;
203
213
  }>;
214
+ /**
215
+ * Exchange OAuth authorization code for tokens (PKCE flow)
216
+ *
217
+ * After OAuth callback redirects with an `insforge_code` parameter, call this method
218
+ * to exchange it for access tokens. The code verifier is automatically
219
+ * retrieved from sessionStorage if available.
220
+ *
221
+ * Note: This is called automatically by the SDK on initialization. You typically
222
+ * don't need to call this directly unless using `skipBrowserRedirect: true`.
223
+ *
224
+ * @param code - The authorization code from OAuth callback URL
225
+ * @param codeVerifier - Optional code verifier (auto-retrieved from sessionStorage if not provided)
226
+ * @returns Session data with access token and user info
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * // Automatic verifier retrieval (recommended for browser)
231
+ * const params = new URLSearchParams(window.location.search);
232
+ * const code = params.get('insforge_code');
233
+ * if (code) {
234
+ * const { data, error } = await insforge.auth.exchangeOAuthCode(code);
235
+ * }
236
+ *
237
+ * // Manual verifier (for custom flows)
238
+ * const { data, error } = await insforge.auth.exchangeOAuthCode(code, codeVerifier);
239
+ * ```
240
+ */
241
+ exchangeOAuthCode(code: string, codeVerifier?: string): Promise<{
242
+ data: {
243
+ accessToken: string;
244
+ user: UserSchema;
245
+ redirectTo?: string;
246
+ } | null;
247
+ error: InsForgeError | null;
248
+ }>;
204
249
  /**
205
250
  * Sign out the current user
206
251
  */
@@ -238,6 +283,7 @@ declare class Auth {
238
283
  /**
239
284
  * Get the current session (only session data, no API call)
240
285
  * Returns the stored JWT token and basic user info from local storage
286
+ * Automatically waits for any pending OAuth callback to complete first
241
287
  */
242
288
  getCurrentSession(): Promise<{
243
289
  data: {
@@ -395,6 +441,27 @@ declare class Database {
395
441
  * - Upserts
396
442
  */
397
443
  from(table: string): _supabase_postgrest_js.PostgrestQueryBuilder<any, any, any, string, unknown>;
444
+ /**
445
+ * Call a PostgreSQL function (RPC)
446
+ *
447
+ * @example
448
+ * // Call a function with parameters
449
+ * const { data, error } = await client.database
450
+ * .rpc('get_user_stats', { user_id: 123 });
451
+ *
452
+ * // Call a function with no parameters
453
+ * const { data, error } = await client.database
454
+ * .rpc('get_all_active_users');
455
+ *
456
+ * // With options (head, count, get)
457
+ * const { data, count } = await client.database
458
+ * .rpc('search_posts', { query: 'hello' }, { count: 'exact' });
459
+ */
460
+ rpc(fn: string, args?: Record<string, unknown>, options?: {
461
+ head?: boolean;
462
+ get?: boolean;
463
+ count?: 'exact' | 'planned' | 'estimated';
464
+ }): _supabase_postgrest_js.PostgrestFilterBuilder<any, any, any, any, string, null, "RPC">;
398
465
  }
399
466
 
400
467
  /**
package/dist/index.js CHANGED
@@ -357,21 +357,74 @@ function isHostedAuthEnvironment() {
357
357
  }
358
358
  return false;
359
359
  }
360
+ var PKCE_VERIFIER_KEY = "insforge_pkce_verifier";
361
+ function generateCodeVerifier() {
362
+ const array = new Uint8Array(32);
363
+ crypto.getRandomValues(array);
364
+ return base64UrlEncode(array);
365
+ }
366
+ async function generateCodeChallenge(verifier) {
367
+ const encoder = new TextEncoder();
368
+ const data = encoder.encode(verifier);
369
+ const hash = await crypto.subtle.digest("SHA-256", data);
370
+ return base64UrlEncode(new Uint8Array(hash));
371
+ }
372
+ function base64UrlEncode(buffer) {
373
+ const base64 = btoa(String.fromCharCode(...buffer));
374
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
375
+ }
376
+ function storePkceVerifier(verifier) {
377
+ if (typeof sessionStorage !== "undefined") {
378
+ sessionStorage.setItem(PKCE_VERIFIER_KEY, verifier);
379
+ }
380
+ }
381
+ function retrievePkceVerifier() {
382
+ if (typeof sessionStorage === "undefined") {
383
+ return null;
384
+ }
385
+ const verifier = sessionStorage.getItem(PKCE_VERIFIER_KEY);
386
+ if (verifier) {
387
+ sessionStorage.removeItem(PKCE_VERIFIER_KEY);
388
+ }
389
+ return verifier;
390
+ }
360
391
  var Auth = class {
361
392
  constructor(http, tokenManager) {
362
393
  this.http = http;
363
394
  this.tokenManager = tokenManager;
364
- this.detectAuthCallback();
395
+ this.authCallbackHandled = this.detectAuthCallback();
365
396
  }
366
397
  /**
367
398
  * Automatically detect and handle OAuth callback parameters in the URL
368
399
  * This runs after initialization to seamlessly complete the OAuth flow
369
- * Matches the backend's OAuth callback response (backend/src/api/routes/auth.ts:540-544)
400
+ *
401
+ * Supports two flows:
402
+ * - PKCE flow (new): Backend returns `insforge_code` param, exchanged for tokens
403
+ * - Legacy flow: Backend returns tokens directly in URL (backward compatible)
370
404
  */
371
- detectAuthCallback() {
405
+ async detectAuthCallback() {
372
406
  if (typeof window === "undefined") return;
373
407
  try {
374
408
  const params = new URLSearchParams(window.location.search);
409
+ const error = params.get("error");
410
+ if (error) {
411
+ const url = new URL(window.location.href);
412
+ url.searchParams.delete("error");
413
+ window.history.replaceState({}, document.title, url.toString());
414
+ console.debug("OAuth callback error:", error);
415
+ return;
416
+ }
417
+ const code = params.get("insforge_code");
418
+ if (code) {
419
+ const url = new URL(window.location.href);
420
+ url.searchParams.delete("insforge_code");
421
+ window.history.replaceState({}, document.title, url.toString());
422
+ const { error: exchangeError } = await this.exchangeOAuthCode(code);
423
+ if (exchangeError) {
424
+ console.debug("OAuth code exchange failed:", exchangeError.message);
425
+ }
426
+ return;
427
+ }
375
428
  const accessToken = params.get("access_token");
376
429
  const userId = params.get("user_id");
377
430
  const email = params.get("email");
@@ -389,8 +442,6 @@ var Auth = class {
389
442
  email,
390
443
  profile: { name: name || "" },
391
444
  metadata: null,
392
- // These fields are not provided by backend OAuth callback
393
- // They'll be populated when calling getCurrentUser()
394
445
  emailVerified: false,
395
446
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
396
447
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -404,9 +455,6 @@ var Auth = class {
404
455
  url.searchParams.delete("email");
405
456
  url.searchParams.delete("name");
406
457
  url.searchParams.delete("csrf_token");
407
- if (params.has("error")) {
408
- url.searchParams.delete("error");
409
- }
410
458
  window.history.replaceState({}, document.title, url.toString());
411
459
  }
412
460
  } catch (error) {
@@ -487,11 +535,20 @@ var Auth = class {
487
535
  }
488
536
  /**
489
537
  * Sign in with OAuth provider
538
+ * Uses PKCE (Proof Key for Code Exchange) for enhanced security
490
539
  */
491
540
  async signInWithOAuth(options) {
492
541
  try {
493
542
  const { provider, redirectTo, skipBrowserRedirect } = options;
494
- const params = redirectTo ? { redirect_uri: redirectTo } : void 0;
543
+ const codeVerifier = generateCodeVerifier();
544
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
545
+ storePkceVerifier(codeVerifier);
546
+ const params = {
547
+ code_challenge: codeChallenge
548
+ };
549
+ if (redirectTo) {
550
+ params.redirect_uri = redirectTo;
551
+ }
495
552
  const endpoint = `/api/auth/oauth/${provider}`;
496
553
  const response = await this.http.get(endpoint, { params });
497
554
  if (typeof window !== "undefined" && !skipBrowserRedirect) {
@@ -501,7 +558,9 @@ var Auth = class {
501
558
  return {
502
559
  data: {
503
560
  url: response.authUrl,
504
- provider
561
+ provider,
562
+ codeVerifier
563
+ // Return verifier for manual flow (skipBrowserRedirect)
505
564
  },
506
565
  error: null
507
566
  };
@@ -519,6 +578,85 @@ var Auth = class {
519
578
  };
520
579
  }
521
580
  }
581
+ /**
582
+ * Exchange OAuth authorization code for tokens (PKCE flow)
583
+ *
584
+ * After OAuth callback redirects with an `insforge_code` parameter, call this method
585
+ * to exchange it for access tokens. The code verifier is automatically
586
+ * retrieved from sessionStorage if available.
587
+ *
588
+ * Note: This is called automatically by the SDK on initialization. You typically
589
+ * don't need to call this directly unless using `skipBrowserRedirect: true`.
590
+ *
591
+ * @param code - The authorization code from OAuth callback URL
592
+ * @param codeVerifier - Optional code verifier (auto-retrieved from sessionStorage if not provided)
593
+ * @returns Session data with access token and user info
594
+ *
595
+ * @example
596
+ * ```ts
597
+ * // Automatic verifier retrieval (recommended for browser)
598
+ * const params = new URLSearchParams(window.location.search);
599
+ * const code = params.get('insforge_code');
600
+ * if (code) {
601
+ * const { data, error } = await insforge.auth.exchangeOAuthCode(code);
602
+ * }
603
+ *
604
+ * // Manual verifier (for custom flows)
605
+ * const { data, error } = await insforge.auth.exchangeOAuthCode(code, codeVerifier);
606
+ * ```
607
+ */
608
+ async exchangeOAuthCode(code, codeVerifier) {
609
+ try {
610
+ const verifier = codeVerifier ?? retrievePkceVerifier();
611
+ if (!verifier) {
612
+ return {
613
+ data: null,
614
+ error: new InsForgeError(
615
+ "PKCE code verifier not found. Ensure signInWithOAuth was called in the same browser session.",
616
+ 400,
617
+ "PKCE_VERIFIER_MISSING"
618
+ )
619
+ };
620
+ }
621
+ const request = {
622
+ code,
623
+ code_verifier: verifier
624
+ };
625
+ const response = await this.http.post("/api/auth/oauth/exchange", request, { credentials: "include" });
626
+ if (!isHostedAuthEnvironment()) {
627
+ const session = {
628
+ accessToken: response.accessToken,
629
+ user: response.user
630
+ };
631
+ if (response.csrfToken) {
632
+ this.tokenManager.setMemoryMode();
633
+ setCsrfToken(response.csrfToken);
634
+ }
635
+ this.tokenManager.saveSession(session);
636
+ this.http.setAuthToken(response.accessToken);
637
+ }
638
+ return {
639
+ data: {
640
+ accessToken: response.accessToken,
641
+ user: response.user,
642
+ redirectTo: response.redirectTo
643
+ },
644
+ error: null
645
+ };
646
+ } catch (error) {
647
+ if (error instanceof InsForgeError) {
648
+ return { data: null, error };
649
+ }
650
+ return {
651
+ data: null,
652
+ error: new InsForgeError(
653
+ "An unexpected error occurred during OAuth code exchange",
654
+ 500,
655
+ "UNEXPECTED_ERROR"
656
+ )
657
+ };
658
+ }
659
+ }
522
660
  /**
523
661
  * Sign out the current user
524
662
  */
@@ -607,8 +745,10 @@ var Auth = class {
607
745
  /**
608
746
  * Get the current session (only session data, no API call)
609
747
  * Returns the stored JWT token and basic user info from local storage
748
+ * Automatically waits for any pending OAuth callback to complete first
610
749
  */
611
750
  async getCurrentSession() {
751
+ await this.authCallbackHandled;
612
752
  if (isHostedAuthEnvironment()) {
613
753
  return { data: { session: null }, error: null };
614
754
  }
@@ -616,6 +756,11 @@ var Auth = class {
616
756
  const session = this.tokenManager.getSession();
617
757
  if (session) {
618
758
  this.http.setAuthToken(session.accessToken);
759
+ if (!session.user) {
760
+ const authResponse = await this.http.get("/api/auth/sessions/current", { credentials: "include" });
761
+ session.user = authResponse.user;
762
+ this.tokenManager.setUser(session.user);
763
+ }
619
764
  return { data: { session }, error: null };
620
765
  }
621
766
  if (typeof window !== "undefined") {
@@ -907,8 +1052,10 @@ function createInsForgePostgrestFetch(httpClient, tokenManager) {
907
1052
  return async (input, init) => {
908
1053
  const url = typeof input === "string" ? input : input.toString();
909
1054
  const urlObj = new URL(url);
910
- const tableName = urlObj.pathname.slice(1);
911
- const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
1055
+ const pathname = urlObj.pathname.slice(1);
1056
+ const rpcMatch = pathname.match(/^rpc\/(.+)$/);
1057
+ const endpoint = rpcMatch ? `/api/database/rpc/${rpcMatch[1]}` : `/api/database/records/${pathname}`;
1058
+ const insforgeUrl = `${httpClient.baseUrl}${endpoint}${urlObj.search}`;
912
1059
  const token = tokenManager.getAccessToken();
913
1060
  const httpHeaders = httpClient.getHeaders();
914
1061
  const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
@@ -968,6 +1115,25 @@ var Database = class {
968
1115
  from(table) {
969
1116
  return this.postgrest.from(table);
970
1117
  }
1118
+ /**
1119
+ * Call a PostgreSQL function (RPC)
1120
+ *
1121
+ * @example
1122
+ * // Call a function with parameters
1123
+ * const { data, error } = await client.database
1124
+ * .rpc('get_user_stats', { user_id: 123 });
1125
+ *
1126
+ * // Call a function with no parameters
1127
+ * const { data, error } = await client.database
1128
+ * .rpc('get_all_active_users');
1129
+ *
1130
+ * // With options (head, count, get)
1131
+ * const { data, count } = await client.database
1132
+ * .rpc('search_posts', { query: 'hello' }, { count: 'exact' });
1133
+ */
1134
+ rpc(fn, args, options) {
1135
+ return this.postgrest.rpc(fn, args, options);
1136
+ }
971
1137
  };
972
1138
 
973
1139
  // src/modules/storage.ts
@@ -1773,8 +1939,7 @@ var InsForgeClient = class {
1773
1939
  this.http.setAuthToken(config.edgeFunctionToken);
1774
1940
  this.tokenManager.saveSession({
1775
1941
  accessToken: config.edgeFunctionToken,
1776
- user: {}
1777
- // Will be populated by getCurrentUser()
1942
+ user: null
1778
1943
  });
1779
1944
  }
1780
1945
  const existingSession = this.tokenManager.getSession();