spaps-sdk 1.2.0 → 1.2.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.d.mts CHANGED
@@ -244,14 +244,13 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
244
244
  password: string;
245
245
  }) => Promise<any>;
246
246
  /**
247
- * Verify a magic link token. Does not set access/refresh tokens.
248
- * Consumers should redirect or prompt login based on the returned status.
247
+ * Verify a magic link token and authenticate the user.
248
+ * Returns full auth response with tokens and auto-stores them.
249
249
  */
250
250
  verifyMagicLink: (payload: {
251
251
  token: string;
252
- }) => Promise<{
253
- success: boolean;
254
- }>;
252
+ type?: string;
253
+ }) => Promise<AuthResponse>;
255
254
  solana: {
256
255
  linkWallet: (payload: {
257
256
  wallet_address: string;
@@ -402,6 +401,51 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
402
401
  revokeAll: () => Promise<any>;
403
402
  touch: () => Promise<any>;
404
403
  };
404
+ /**
405
+ * CFO (QuickBooks) namespace for managing multi-account connections
406
+ */
407
+ cfo: {
408
+ /**
409
+ * Get all QuickBooks connections for the current user
410
+ */
411
+ getConnections: () => Promise<{
412
+ connections: Array<{
413
+ id: string;
414
+ status: string;
415
+ companyName?: string;
416
+ realmId?: string;
417
+ connectedAt?: string;
418
+ }>;
419
+ }>;
420
+ /**
421
+ * Get connection status (returns primary connection for backward compatibility)
422
+ */
423
+ getStatus: () => Promise<{
424
+ connection: {
425
+ id: string;
426
+ status: string;
427
+ companyName?: string;
428
+ realmId?: string;
429
+ } | null;
430
+ totalConnections: number;
431
+ }>;
432
+ /**
433
+ * Start adding a new QuickBooks connection
434
+ * Returns auth URL to redirect user to QuickBooks OAuth
435
+ */
436
+ addConnection: () => Promise<{
437
+ authUrl: string;
438
+ connectionId: string;
439
+ existingConnections: number;
440
+ }>;
441
+ /**
442
+ * Disconnect a specific QuickBooks account
443
+ */
444
+ disconnect: (connectionId: string) => Promise<{
445
+ success: boolean;
446
+ message: string;
447
+ }>;
448
+ };
405
449
  createCheckoutSession(priceId: string, successUrl: string, cancelUrl?: string): Promise<{
406
450
  data: CheckoutSession;
407
451
  }>;
@@ -462,6 +506,12 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
462
506
  isAuthenticated(): boolean;
463
507
  getAccessToken(): string | undefined;
464
508
  setAccessToken(token: string): void;
509
+ setRefreshToken(token: string): void;
510
+ /**
511
+ * Restore tokens (for SSO/OAuth flows)
512
+ * Sets both access and refresh tokens
513
+ */
514
+ restoreTokens(accessToken: string, refreshToken?: string): void;
465
515
  isLocalMode(): boolean;
466
516
  /**
467
517
  * Check if current user has admin privileges
@@ -478,6 +528,10 @@ declare class TokenManager {
478
528
  private static readonly ACCESS_TOKEN_KEY;
479
529
  private static readonly REFRESH_TOKEN_KEY;
480
530
  private static readonly USER_KEY;
531
+ /**
532
+ * Cross-platform base64 decode (works in both Node.js and browser)
533
+ */
534
+ private static base64Decode;
481
535
  private static getStorage;
482
536
  static storeTokens(tokens: AuthResponse): void;
483
537
  static getAccessToken(): string | null;
@@ -486,6 +540,24 @@ declare class TokenManager {
486
540
  static clearTokens(): void;
487
541
  static isTokenExpired(token: string): boolean;
488
542
  static autoRefreshToken(sdk: SPAPSClient): Promise<boolean>;
543
+ /**
544
+ * Parse auth tokens from URL fragment (for OAuth callbacks)
545
+ * URL format: https://app.com/callback#access_token=xxx&refresh_token=xxx
546
+ */
547
+ static parseTokensFromUrlFragment(url?: string): {
548
+ access_token?: string;
549
+ refresh_token?: string;
550
+ user_id?: string;
551
+ } | null;
552
+ /**
553
+ * Handle OAuth callback - parse tokens from URL and store them
554
+ * Returns user info decoded from JWT, or null if no tokens found
555
+ */
556
+ static handleOAuthCallback(sdk: SPAPSClient): {
557
+ id: string;
558
+ email?: string;
559
+ role?: string;
560
+ } | null;
489
561
  }
490
562
  declare class WalletUtils {
491
563
  static detectChainType(address: string): 'solana' | 'ethereum' | 'bitcoin' | null;
package/dist/index.d.ts CHANGED
@@ -244,14 +244,13 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
244
244
  password: string;
245
245
  }) => Promise<any>;
246
246
  /**
247
- * Verify a magic link token. Does not set access/refresh tokens.
248
- * Consumers should redirect or prompt login based on the returned status.
247
+ * Verify a magic link token and authenticate the user.
248
+ * Returns full auth response with tokens and auto-stores them.
249
249
  */
250
250
  verifyMagicLink: (payload: {
251
251
  token: string;
252
- }) => Promise<{
253
- success: boolean;
254
- }>;
252
+ type?: string;
253
+ }) => Promise<AuthResponse>;
255
254
  solana: {
256
255
  linkWallet: (payload: {
257
256
  wallet_address: string;
@@ -402,6 +401,51 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
402
401
  revokeAll: () => Promise<any>;
403
402
  touch: () => Promise<any>;
404
403
  };
404
+ /**
405
+ * CFO (QuickBooks) namespace for managing multi-account connections
406
+ */
407
+ cfo: {
408
+ /**
409
+ * Get all QuickBooks connections for the current user
410
+ */
411
+ getConnections: () => Promise<{
412
+ connections: Array<{
413
+ id: string;
414
+ status: string;
415
+ companyName?: string;
416
+ realmId?: string;
417
+ connectedAt?: string;
418
+ }>;
419
+ }>;
420
+ /**
421
+ * Get connection status (returns primary connection for backward compatibility)
422
+ */
423
+ getStatus: () => Promise<{
424
+ connection: {
425
+ id: string;
426
+ status: string;
427
+ companyName?: string;
428
+ realmId?: string;
429
+ } | null;
430
+ totalConnections: number;
431
+ }>;
432
+ /**
433
+ * Start adding a new QuickBooks connection
434
+ * Returns auth URL to redirect user to QuickBooks OAuth
435
+ */
436
+ addConnection: () => Promise<{
437
+ authUrl: string;
438
+ connectionId: string;
439
+ existingConnections: number;
440
+ }>;
441
+ /**
442
+ * Disconnect a specific QuickBooks account
443
+ */
444
+ disconnect: (connectionId: string) => Promise<{
445
+ success: boolean;
446
+ message: string;
447
+ }>;
448
+ };
405
449
  createCheckoutSession(priceId: string, successUrl: string, cancelUrl?: string): Promise<{
406
450
  data: CheckoutSession;
407
451
  }>;
@@ -462,6 +506,12 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
462
506
  isAuthenticated(): boolean;
463
507
  getAccessToken(): string | undefined;
464
508
  setAccessToken(token: string): void;
509
+ setRefreshToken(token: string): void;
510
+ /**
511
+ * Restore tokens (for SSO/OAuth flows)
512
+ * Sets both access and refresh tokens
513
+ */
514
+ restoreTokens(accessToken: string, refreshToken?: string): void;
465
515
  isLocalMode(): boolean;
466
516
  /**
467
517
  * Check if current user has admin privileges
@@ -478,6 +528,10 @@ declare class TokenManager {
478
528
  private static readonly ACCESS_TOKEN_KEY;
479
529
  private static readonly REFRESH_TOKEN_KEY;
480
530
  private static readonly USER_KEY;
531
+ /**
532
+ * Cross-platform base64 decode (works in both Node.js and browser)
533
+ */
534
+ private static base64Decode;
481
535
  private static getStorage;
482
536
  static storeTokens(tokens: AuthResponse): void;
483
537
  static getAccessToken(): string | null;
@@ -486,6 +540,24 @@ declare class TokenManager {
486
540
  static clearTokens(): void;
487
541
  static isTokenExpired(token: string): boolean;
488
542
  static autoRefreshToken(sdk: SPAPSClient): Promise<boolean>;
543
+ /**
544
+ * Parse auth tokens from URL fragment (for OAuth callbacks)
545
+ * URL format: https://app.com/callback#access_token=xxx&refresh_token=xxx
546
+ */
547
+ static parseTokensFromUrlFragment(url?: string): {
548
+ access_token?: string;
549
+ refresh_token?: string;
550
+ user_id?: string;
551
+ } | null;
552
+ /**
553
+ * Handle OAuth callback - parse tokens from URL and store them
554
+ * Returns user info decoded from JWT, or null if no tokens found
555
+ */
556
+ static handleOAuthCallback(sdk: SPAPSClient): {
557
+ id: string;
558
+ email?: string;
559
+ role?: string;
560
+ } | null;
489
561
  }
490
562
  declare class WalletUtils {
491
563
  static detectChainType(address: string): 'solana' | 'ethereum' | 'bitcoin' | null;
package/dist/index.js CHANGED
@@ -470,12 +470,24 @@ var SPAPSClient = class {
470
470
  return data;
471
471
  },
472
472
  /**
473
- * Verify a magic link token. Does not set access/refresh tokens.
474
- * Consumers should redirect or prompt login based on the returned status.
473
+ * Verify a magic link token and authenticate the user.
474
+ * Returns full auth response with tokens and auto-stores them.
475
475
  */
476
476
  verifyMagicLink: async (payload) => {
477
- const res = await this.client.post("/api/auth/verify-magic-link", payload);
478
- return this.unwrapApiResponse(res, "Failed to verify magic link");
477
+ const res = await this.client.post("/api/auth/verify-magic-link", {
478
+ token: payload.token,
479
+ type: payload.type || "magiclink"
480
+ });
481
+ const body = res.data;
482
+ if (body?.success === false) throw new Error(body?.error?.message || "Magic link verification failed");
483
+ const data = {
484
+ access_token: body.tokens?.access_token || body.access_token,
485
+ refresh_token: body.tokens?.refresh_token || body.refresh_token,
486
+ user: body.user
487
+ };
488
+ this.accessToken = data.access_token;
489
+ this.refreshToken = data.refresh_token;
490
+ return data;
479
491
  },
480
492
  solana: {
481
493
  linkWallet: async (payload) => {
@@ -791,6 +803,40 @@ var SPAPSClient = class {
791
803
  return this.unwrapApiResponse(res, "Failed to touch session");
792
804
  }
793
805
  };
806
+ /**
807
+ * CFO (QuickBooks) namespace for managing multi-account connections
808
+ */
809
+ cfo = {
810
+ /**
811
+ * Get all QuickBooks connections for the current user
812
+ */
813
+ getConnections: async () => {
814
+ const res = await this.client.get("/api/cfo/user-connections", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
815
+ return this.unwrapApiResponse(res, "Failed to get connections");
816
+ },
817
+ /**
818
+ * Get connection status (returns primary connection for backward compatibility)
819
+ */
820
+ getStatus: async () => {
821
+ const res = await this.client.get("/api/cfo/user-status", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
822
+ return this.unwrapApiResponse(res, "Failed to get status");
823
+ },
824
+ /**
825
+ * Start adding a new QuickBooks connection
826
+ * Returns auth URL to redirect user to QuickBooks OAuth
827
+ */
828
+ addConnection: async () => {
829
+ const res = await this.client.post("/api/cfo/user-add-connection", {}, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
830
+ return this.unwrapApiResponse(res, "Failed to start connection");
831
+ },
832
+ /**
833
+ * Disconnect a specific QuickBooks account
834
+ */
835
+ disconnect: async (connectionId) => {
836
+ const res = await this.client.delete(`/api/cfo/user-connections/${connectionId}`, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
837
+ return this.unwrapApiResponse(res, "Failed to disconnect");
838
+ }
839
+ };
794
840
  // Stripe Methods
795
841
  async createCheckoutSession(priceId, successUrl, cancelUrl) {
796
842
  return this.client.post("/api/stripe/create-checkout-session", {
@@ -914,6 +960,19 @@ var SPAPSClient = class {
914
960
  setAccessToken(token) {
915
961
  this.accessToken = token;
916
962
  }
963
+ setRefreshToken(token) {
964
+ this.refreshToken = token;
965
+ }
966
+ /**
967
+ * Restore tokens (for SSO/OAuth flows)
968
+ * Sets both access and refresh tokens
969
+ */
970
+ restoreTokens(accessToken, refreshToken) {
971
+ this.accessToken = accessToken;
972
+ if (refreshToken) {
973
+ this.refreshToken = refreshToken;
974
+ }
975
+ }
917
976
  isLocalMode() {
918
977
  return this._isLocalMode;
919
978
  }
@@ -968,6 +1027,17 @@ var TokenManager = class _TokenManager {
968
1027
  static ACCESS_TOKEN_KEY = "sweet_potato_access_token";
969
1028
  static REFRESH_TOKEN_KEY = "sweet_potato_refresh_token";
970
1029
  static USER_KEY = "sweet_potato_user";
1030
+ /**
1031
+ * Cross-platform base64 decode (works in both Node.js and browser)
1032
+ */
1033
+ static base64Decode(str) {
1034
+ if (typeof Buffer !== "undefined") {
1035
+ return Buffer.from(str, "base64").toString("utf8");
1036
+ } else if (typeof atob !== "undefined") {
1037
+ return atob(str);
1038
+ }
1039
+ throw new Error("No base64 decode method available");
1040
+ }
971
1041
  static getStorage() {
972
1042
  if (typeof globalThis !== "undefined" && globalThis.localStorage) return globalThis.localStorage;
973
1043
  if (typeof globalThis !== "undefined" && globalThis.window?.localStorage) return globalThis.window.localStorage;
@@ -1008,7 +1078,7 @@ var TokenManager = class _TokenManager {
1008
1078
  try {
1009
1079
  const parts = token.split(".");
1010
1080
  if (parts.length !== 3 || !parts[1]) return true;
1011
- const payload = JSON.parse(atob(parts[1]));
1081
+ const payload = JSON.parse(_TokenManager.base64Decode(parts[1]));
1012
1082
  const now = Math.floor(Date.now() / 1e3);
1013
1083
  return payload.exp < now;
1014
1084
  } catch {
@@ -1032,6 +1102,74 @@ var TokenManager = class _TokenManager {
1032
1102
  return false;
1033
1103
  }
1034
1104
  }
1105
+ /**
1106
+ * Parse auth tokens from URL fragment (for OAuth callbacks)
1107
+ * URL format: https://app.com/callback#access_token=xxx&refresh_token=xxx
1108
+ */
1109
+ static parseTokensFromUrlFragment(url) {
1110
+ let hash;
1111
+ if (url) {
1112
+ try {
1113
+ hash = new URL(url).hash;
1114
+ } catch {
1115
+ return null;
1116
+ }
1117
+ } else if (typeof window !== "undefined") {
1118
+ hash = window.location.hash;
1119
+ } else {
1120
+ return null;
1121
+ }
1122
+ if (!hash || hash.length < 2) return null;
1123
+ const params = new URLSearchParams(hash.substring(1));
1124
+ const access_token = params.get("access_token");
1125
+ const refresh_token = params.get("refresh_token");
1126
+ if (!access_token) return null;
1127
+ return {
1128
+ access_token,
1129
+ refresh_token: refresh_token || void 0,
1130
+ user_id: params.get("user_id") || void 0
1131
+ };
1132
+ }
1133
+ /**
1134
+ * Handle OAuth callback - parse tokens from URL and store them
1135
+ * Returns user info decoded from JWT, or null if no tokens found
1136
+ */
1137
+ static handleOAuthCallback(sdk) {
1138
+ const tokens = _TokenManager.parseTokensFromUrlFragment();
1139
+ if (!tokens?.access_token) {
1140
+ console.debug("[SPAPS] handleOAuthCallback: No access token found in URL fragment");
1141
+ return null;
1142
+ }
1143
+ if (typeof window !== "undefined") {
1144
+ window.history.replaceState(null, "", window.location.pathname + window.location.search);
1145
+ }
1146
+ try {
1147
+ const parts = tokens.access_token.split(".");
1148
+ if (parts.length !== 3 || !parts[1]) {
1149
+ console.warn("[SPAPS] handleOAuthCallback: Invalid JWT format - expected 3 parts");
1150
+ return null;
1151
+ }
1152
+ const payload = JSON.parse(_TokenManager.base64Decode(parts[1]));
1153
+ const user = {
1154
+ id: payload.user_id || payload.sub,
1155
+ email: payload.email,
1156
+ role: payload.role || "user"
1157
+ };
1158
+ _TokenManager.storeTokens({
1159
+ access_token: tokens.access_token,
1160
+ refresh_token: tokens.refresh_token || "",
1161
+ user
1162
+ });
1163
+ sdk.setAccessToken(tokens.access_token);
1164
+ if (tokens.refresh_token) {
1165
+ sdk.setRefreshToken(tokens.refresh_token);
1166
+ }
1167
+ return user;
1168
+ } catch (err) {
1169
+ console.warn("[SPAPS] handleOAuthCallback: Failed to decode JWT", err);
1170
+ return null;
1171
+ }
1172
+ }
1035
1173
  };
1036
1174
  var WalletUtils = class _WalletUtils {
1037
1175
  static detectChainType(address) {
package/dist/index.mjs CHANGED
@@ -443,12 +443,24 @@ var SPAPSClient = class {
443
443
  return data;
444
444
  },
445
445
  /**
446
- * Verify a magic link token. Does not set access/refresh tokens.
447
- * Consumers should redirect or prompt login based on the returned status.
446
+ * Verify a magic link token and authenticate the user.
447
+ * Returns full auth response with tokens and auto-stores them.
448
448
  */
449
449
  verifyMagicLink: async (payload) => {
450
- const res = await this.client.post("/api/auth/verify-magic-link", payload);
451
- return this.unwrapApiResponse(res, "Failed to verify magic link");
450
+ const res = await this.client.post("/api/auth/verify-magic-link", {
451
+ token: payload.token,
452
+ type: payload.type || "magiclink"
453
+ });
454
+ const body = res.data;
455
+ if (body?.success === false) throw new Error(body?.error?.message || "Magic link verification failed");
456
+ const data = {
457
+ access_token: body.tokens?.access_token || body.access_token,
458
+ refresh_token: body.tokens?.refresh_token || body.refresh_token,
459
+ user: body.user
460
+ };
461
+ this.accessToken = data.access_token;
462
+ this.refreshToken = data.refresh_token;
463
+ return data;
452
464
  },
453
465
  solana: {
454
466
  linkWallet: async (payload) => {
@@ -764,6 +776,40 @@ var SPAPSClient = class {
764
776
  return this.unwrapApiResponse(res, "Failed to touch session");
765
777
  }
766
778
  };
779
+ /**
780
+ * CFO (QuickBooks) namespace for managing multi-account connections
781
+ */
782
+ cfo = {
783
+ /**
784
+ * Get all QuickBooks connections for the current user
785
+ */
786
+ getConnections: async () => {
787
+ const res = await this.client.get("/api/cfo/user-connections", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
788
+ return this.unwrapApiResponse(res, "Failed to get connections");
789
+ },
790
+ /**
791
+ * Get connection status (returns primary connection for backward compatibility)
792
+ */
793
+ getStatus: async () => {
794
+ const res = await this.client.get("/api/cfo/user-status", this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
795
+ return this.unwrapApiResponse(res, "Failed to get status");
796
+ },
797
+ /**
798
+ * Start adding a new QuickBooks connection
799
+ * Returns auth URL to redirect user to QuickBooks OAuth
800
+ */
801
+ addConnection: async () => {
802
+ const res = await this.client.post("/api/cfo/user-add-connection", {}, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
803
+ return this.unwrapApiResponse(res, "Failed to start connection");
804
+ },
805
+ /**
806
+ * Disconnect a specific QuickBooks account
807
+ */
808
+ disconnect: async (connectionId) => {
809
+ const res = await this.client.delete(`/api/cfo/user-connections/${connectionId}`, this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0);
810
+ return this.unwrapApiResponse(res, "Failed to disconnect");
811
+ }
812
+ };
767
813
  // Stripe Methods
768
814
  async createCheckoutSession(priceId, successUrl, cancelUrl) {
769
815
  return this.client.post("/api/stripe/create-checkout-session", {
@@ -887,6 +933,19 @@ var SPAPSClient = class {
887
933
  setAccessToken(token) {
888
934
  this.accessToken = token;
889
935
  }
936
+ setRefreshToken(token) {
937
+ this.refreshToken = token;
938
+ }
939
+ /**
940
+ * Restore tokens (for SSO/OAuth flows)
941
+ * Sets both access and refresh tokens
942
+ */
943
+ restoreTokens(accessToken, refreshToken) {
944
+ this.accessToken = accessToken;
945
+ if (refreshToken) {
946
+ this.refreshToken = refreshToken;
947
+ }
948
+ }
890
949
  isLocalMode() {
891
950
  return this._isLocalMode;
892
951
  }
@@ -941,6 +1000,17 @@ var TokenManager = class _TokenManager {
941
1000
  static ACCESS_TOKEN_KEY = "sweet_potato_access_token";
942
1001
  static REFRESH_TOKEN_KEY = "sweet_potato_refresh_token";
943
1002
  static USER_KEY = "sweet_potato_user";
1003
+ /**
1004
+ * Cross-platform base64 decode (works in both Node.js and browser)
1005
+ */
1006
+ static base64Decode(str) {
1007
+ if (typeof Buffer !== "undefined") {
1008
+ return Buffer.from(str, "base64").toString("utf8");
1009
+ } else if (typeof atob !== "undefined") {
1010
+ return atob(str);
1011
+ }
1012
+ throw new Error("No base64 decode method available");
1013
+ }
944
1014
  static getStorage() {
945
1015
  if (typeof globalThis !== "undefined" && globalThis.localStorage) return globalThis.localStorage;
946
1016
  if (typeof globalThis !== "undefined" && globalThis.window?.localStorage) return globalThis.window.localStorage;
@@ -981,7 +1051,7 @@ var TokenManager = class _TokenManager {
981
1051
  try {
982
1052
  const parts = token.split(".");
983
1053
  if (parts.length !== 3 || !parts[1]) return true;
984
- const payload = JSON.parse(atob(parts[1]));
1054
+ const payload = JSON.parse(_TokenManager.base64Decode(parts[1]));
985
1055
  const now = Math.floor(Date.now() / 1e3);
986
1056
  return payload.exp < now;
987
1057
  } catch {
@@ -1005,6 +1075,74 @@ var TokenManager = class _TokenManager {
1005
1075
  return false;
1006
1076
  }
1007
1077
  }
1078
+ /**
1079
+ * Parse auth tokens from URL fragment (for OAuth callbacks)
1080
+ * URL format: https://app.com/callback#access_token=xxx&refresh_token=xxx
1081
+ */
1082
+ static parseTokensFromUrlFragment(url) {
1083
+ let hash;
1084
+ if (url) {
1085
+ try {
1086
+ hash = new URL(url).hash;
1087
+ } catch {
1088
+ return null;
1089
+ }
1090
+ } else if (typeof window !== "undefined") {
1091
+ hash = window.location.hash;
1092
+ } else {
1093
+ return null;
1094
+ }
1095
+ if (!hash || hash.length < 2) return null;
1096
+ const params = new URLSearchParams(hash.substring(1));
1097
+ const access_token = params.get("access_token");
1098
+ const refresh_token = params.get("refresh_token");
1099
+ if (!access_token) return null;
1100
+ return {
1101
+ access_token,
1102
+ refresh_token: refresh_token || void 0,
1103
+ user_id: params.get("user_id") || void 0
1104
+ };
1105
+ }
1106
+ /**
1107
+ * Handle OAuth callback - parse tokens from URL and store them
1108
+ * Returns user info decoded from JWT, or null if no tokens found
1109
+ */
1110
+ static handleOAuthCallback(sdk) {
1111
+ const tokens = _TokenManager.parseTokensFromUrlFragment();
1112
+ if (!tokens?.access_token) {
1113
+ console.debug("[SPAPS] handleOAuthCallback: No access token found in URL fragment");
1114
+ return null;
1115
+ }
1116
+ if (typeof window !== "undefined") {
1117
+ window.history.replaceState(null, "", window.location.pathname + window.location.search);
1118
+ }
1119
+ try {
1120
+ const parts = tokens.access_token.split(".");
1121
+ if (parts.length !== 3 || !parts[1]) {
1122
+ console.warn("[SPAPS] handleOAuthCallback: Invalid JWT format - expected 3 parts");
1123
+ return null;
1124
+ }
1125
+ const payload = JSON.parse(_TokenManager.base64Decode(parts[1]));
1126
+ const user = {
1127
+ id: payload.user_id || payload.sub,
1128
+ email: payload.email,
1129
+ role: payload.role || "user"
1130
+ };
1131
+ _TokenManager.storeTokens({
1132
+ access_token: tokens.access_token,
1133
+ refresh_token: tokens.refresh_token || "",
1134
+ user
1135
+ });
1136
+ sdk.setAccessToken(tokens.access_token);
1137
+ if (tokens.refresh_token) {
1138
+ sdk.setRefreshToken(tokens.refresh_token);
1139
+ }
1140
+ return user;
1141
+ } catch (err) {
1142
+ console.warn("[SPAPS] handleOAuthCallback: Failed to decode JWT", err);
1143
+ return null;
1144
+ }
1145
+ }
1008
1146
  };
1009
1147
  var WalletUtils = class _WalletUtils {
1010
1148
  static detectChainType(address) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spaps-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Sweet Potato Authentication & Payment Service SDK - Zero-config client with built-in permission checking and role-based access control",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -44,7 +44,7 @@
44
44
  "email": "buildooor@gmail.com"
45
45
  },
46
46
  "dependencies": {
47
- "spaps-types": "^1.0.58",
47
+ "spaps-types": "^1.0.59",
48
48
  "axios": "^1.6.0",
49
49
  "cross-fetch": "^4.0.0"
50
50
  },
@@ -72,4 +72,4 @@
72
72
  "engines": {
73
73
  "node": ">=14.0.0"
74
74
  }
75
- }
75
+ }