integrate-sdk 0.8.27 → 0.8.28-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/oauth.js CHANGED
@@ -627,6 +627,277 @@ class OAuthWindowManager {
627
627
  }
628
628
  }
629
629
 
630
+ // src/oauth/indexeddb-storage.ts
631
+ class IndexedDBStorage {
632
+ db = null;
633
+ initPromise = null;
634
+ async init() {
635
+ if (this.db) {
636
+ return this.db;
637
+ }
638
+ if (this.initPromise) {
639
+ return this.initPromise;
640
+ }
641
+ this.initPromise = new Promise((resolve, reject) => {
642
+ if (typeof window === "undefined" || !window.indexedDB) {
643
+ reject(new Error("IndexedDB is not available in this environment"));
644
+ return;
645
+ }
646
+ const request = window.indexedDB.open(DB_NAME, DB_VERSION);
647
+ request.onerror = () => {
648
+ this.initPromise = null;
649
+ reject(new Error(`Failed to open IndexedDB: ${request.error?.message}`));
650
+ };
651
+ request.onsuccess = () => {
652
+ this.db = request.result;
653
+ this.initPromise = null;
654
+ resolve(this.db);
655
+ };
656
+ request.onupgradeneeded = (event) => {
657
+ const db = event.target.result;
658
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
659
+ const store = db.createObjectStore(STORE_NAME, { keyPath: "accountId" });
660
+ store.createIndex("provider", "provider", { unique: false });
661
+ store.createIndex("email", "email", { unique: false });
662
+ store.createIndex("provider_email", ["provider", "email"], { unique: true });
663
+ }
664
+ };
665
+ });
666
+ return this.initPromise;
667
+ }
668
+ generateAccountId(provider, email) {
669
+ const str = `${provider}:${email}`;
670
+ let hash = 0;
671
+ for (let i = 0;i < str.length; i++) {
672
+ const char = str.charCodeAt(i);
673
+ hash = (hash << 5) - hash + char;
674
+ hash = hash & hash;
675
+ }
676
+ return `${provider}_${Math.abs(hash).toString(36)}`;
677
+ }
678
+ async saveToken(provider, email, tokenData) {
679
+ const db = await this.init();
680
+ const accountId = this.generateAccountId(provider, email);
681
+ const now = Date.now();
682
+ const entry = {
683
+ provider,
684
+ email,
685
+ accountId,
686
+ tokenData,
687
+ createdAt: now,
688
+ updatedAt: now
689
+ };
690
+ return new Promise((resolve, reject) => {
691
+ const transaction = db.transaction([STORE_NAME], "readwrite");
692
+ const store = transaction.objectStore(STORE_NAME);
693
+ const getRequest = store.get(accountId);
694
+ getRequest.onsuccess = () => {
695
+ const existing = getRequest.result;
696
+ if (existing) {
697
+ entry.createdAt = existing.createdAt;
698
+ }
699
+ const putRequest = store.put(entry);
700
+ putRequest.onsuccess = () => resolve();
701
+ putRequest.onerror = () => reject(new Error(`Failed to save token: ${putRequest.error?.message}`));
702
+ };
703
+ getRequest.onerror = () => reject(new Error(`Failed to check existing token: ${getRequest.error?.message}`));
704
+ });
705
+ }
706
+ async getToken(provider, email) {
707
+ const db = await this.init();
708
+ const accountId = this.generateAccountId(provider, email);
709
+ return new Promise((resolve, reject) => {
710
+ const transaction = db.transaction([STORE_NAME], "readonly");
711
+ const store = transaction.objectStore(STORE_NAME);
712
+ const request = store.get(accountId);
713
+ request.onsuccess = () => {
714
+ const entry = request.result;
715
+ resolve(entry?.tokenData);
716
+ };
717
+ request.onerror = () => reject(new Error(`Failed to get token: ${request.error?.message}`));
718
+ });
719
+ }
720
+ async getTokensByProvider(provider) {
721
+ const db = await this.init();
722
+ const tokens = new Map;
723
+ return new Promise((resolve, reject) => {
724
+ const transaction = db.transaction([STORE_NAME], "readonly");
725
+ const store = transaction.objectStore(STORE_NAME);
726
+ const index = store.index("provider");
727
+ const request = index.getAll(provider);
728
+ request.onsuccess = () => {
729
+ const entries = request.result;
730
+ for (const entry of entries) {
731
+ tokens.set(entry.email, entry.tokenData);
732
+ }
733
+ resolve(tokens);
734
+ };
735
+ request.onerror = () => reject(new Error(`Failed to get tokens: ${request.error?.message}`));
736
+ });
737
+ }
738
+ async listAccounts(provider) {
739
+ const db = await this.init();
740
+ return new Promise((resolve, reject) => {
741
+ const transaction = db.transaction([STORE_NAME], "readonly");
742
+ const store = transaction.objectStore(STORE_NAME);
743
+ const index = store.index("provider");
744
+ const request = index.getAll(provider);
745
+ request.onsuccess = () => {
746
+ const entries = request.result;
747
+ const accounts = entries.map((entry) => ({
748
+ email: entry.email,
749
+ accountId: entry.accountId,
750
+ expiresAt: entry.tokenData.expiresAt,
751
+ scopes: entry.tokenData.scopes,
752
+ createdAt: entry.createdAt
753
+ }));
754
+ resolve(accounts);
755
+ };
756
+ request.onerror = () => reject(new Error(`Failed to list accounts: ${request.error?.message}`));
757
+ });
758
+ }
759
+ async deleteToken(provider, email) {
760
+ const db = await this.init();
761
+ const accountId = this.generateAccountId(provider, email);
762
+ return new Promise((resolve, reject) => {
763
+ const transaction = db.transaction([STORE_NAME], "readwrite");
764
+ const store = transaction.objectStore(STORE_NAME);
765
+ const request = store.delete(accountId);
766
+ request.onsuccess = () => resolve();
767
+ request.onerror = () => reject(new Error(`Failed to delete token: ${request.error?.message}`));
768
+ });
769
+ }
770
+ async deleteTokensByProvider(provider) {
771
+ const db = await this.init();
772
+ return new Promise((resolve, reject) => {
773
+ const transaction = db.transaction([STORE_NAME], "readwrite");
774
+ const store = transaction.objectStore(STORE_NAME);
775
+ const index = store.index("provider");
776
+ const request = index.getAll(provider);
777
+ request.onsuccess = () => {
778
+ const entries = request.result;
779
+ const deletePromises = entries.map((entry) => {
780
+ return new Promise((resolveDelete, rejectDelete) => {
781
+ const deleteRequest = store.delete(entry.accountId);
782
+ deleteRequest.onsuccess = () => resolveDelete();
783
+ deleteRequest.onerror = () => rejectDelete(deleteRequest.error);
784
+ });
785
+ });
786
+ Promise.all(deletePromises).then(() => resolve()).catch((error) => reject(error));
787
+ };
788
+ request.onerror = () => reject(new Error(`Failed to delete tokens: ${request.error?.message}`));
789
+ });
790
+ }
791
+ async clearAll() {
792
+ const db = await this.init();
793
+ return new Promise((resolve, reject) => {
794
+ const transaction = db.transaction([STORE_NAME], "readwrite");
795
+ const store = transaction.objectStore(STORE_NAME);
796
+ const request = store.clear();
797
+ request.onsuccess = () => resolve();
798
+ request.onerror = () => reject(new Error(`Failed to clear tokens: ${request.error?.message}`));
799
+ });
800
+ }
801
+ }
802
+ var DB_NAME = "integrate_oauth_tokens", DB_VERSION = 1, STORE_NAME = "tokens";
803
+
804
+ // src/oauth/email-fetcher.ts
805
+ async function fetchUserEmail(provider, tokenData) {
806
+ try {
807
+ switch (provider.toLowerCase()) {
808
+ case "github":
809
+ return await fetchGitHubEmail(tokenData.accessToken);
810
+ case "gmail":
811
+ case "google":
812
+ return await fetchGoogleEmail(tokenData.accessToken);
813
+ case "notion":
814
+ return await fetchNotionEmail(tokenData.accessToken);
815
+ default:
816
+ return tokenData.email;
817
+ }
818
+ } catch (error) {
819
+ console.error(`Failed to fetch email for ${provider}:`, error);
820
+ return;
821
+ }
822
+ }
823
+ async function fetchGitHubEmail(accessToken) {
824
+ try {
825
+ const userResponse = await fetch("https://api.github.com/user", {
826
+ headers: {
827
+ Authorization: `Bearer ${accessToken}`,
828
+ Accept: "application/vnd.github.v3+json"
829
+ }
830
+ });
831
+ if (!userResponse.ok) {
832
+ return;
833
+ }
834
+ const user = await userResponse.json();
835
+ if (user.email) {
836
+ return user.email;
837
+ }
838
+ const emailsResponse = await fetch("https://api.github.com/user/emails", {
839
+ headers: {
840
+ Authorization: `Bearer ${accessToken}`,
841
+ Accept: "application/vnd.github.v3+json"
842
+ }
843
+ });
844
+ if (!emailsResponse.ok) {
845
+ return;
846
+ }
847
+ const emails = await emailsResponse.json();
848
+ const primaryEmail = emails.find((e) => e.primary && e.verified);
849
+ if (primaryEmail) {
850
+ return primaryEmail.email;
851
+ }
852
+ const verifiedEmail = emails.find((e) => e.verified);
853
+ if (verifiedEmail) {
854
+ return verifiedEmail.email;
855
+ }
856
+ if (emails.length > 0 && emails[0]?.email) {
857
+ return emails[0].email;
858
+ }
859
+ return;
860
+ } catch (error) {
861
+ console.error("Failed to fetch GitHub email:", error);
862
+ return;
863
+ }
864
+ }
865
+ async function fetchGoogleEmail(accessToken) {
866
+ try {
867
+ const response = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", {
868
+ headers: {
869
+ Authorization: `Bearer ${accessToken}`
870
+ }
871
+ });
872
+ if (!response.ok) {
873
+ return;
874
+ }
875
+ const user = await response.json();
876
+ return user.email;
877
+ } catch (error) {
878
+ console.error("Failed to fetch Google email:", error);
879
+ return;
880
+ }
881
+ }
882
+ async function fetchNotionEmail(accessToken) {
883
+ try {
884
+ const response = await fetch("https://api.notion.com/v1/users/me", {
885
+ headers: {
886
+ Authorization: `Bearer ${accessToken}`,
887
+ "Notion-Version": "2022-06-28"
888
+ }
889
+ });
890
+ if (!response.ok) {
891
+ return;
892
+ }
893
+ const user = await response.json();
894
+ return user.person?.email;
895
+ } catch (error) {
896
+ console.error("Failed to fetch Notion email:", error);
897
+ return;
898
+ }
899
+ }
900
+
630
901
  // src/oauth/manager.ts
631
902
  class OAuthManager {
632
903
  pendingAuths = new Map;
@@ -638,7 +909,8 @@ class OAuthManager {
638
909
  getTokenCallback;
639
910
  setTokenCallback;
640
911
  removeTokenCallback;
641
- skipLocalStorage;
912
+ indexedDBStorage;
913
+ skipLocalStorage = false;
642
914
  constructor(oauthApiBase, flowConfig, apiBaseUrl, tokenCallbacks) {
643
915
  this.oauthApiBase = oauthApiBase;
644
916
  this.apiBaseUrl = apiBaseUrl;
@@ -651,7 +923,7 @@ class OAuthManager {
651
923
  this.getTokenCallback = tokenCallbacks?.getProviderToken;
652
924
  this.setTokenCallback = tokenCallbacks?.setProviderToken;
653
925
  this.removeTokenCallback = tokenCallbacks?.removeProviderToken;
654
- this.skipLocalStorage = !!(tokenCallbacks?.getProviderToken || tokenCallbacks?.setProviderToken);
926
+ this.indexedDBStorage = new IndexedDBStorage;
655
927
  this.cleanupExpiredPendingAuths();
656
928
  }
657
929
  async initiateFlow(provider, config, returnUrl) {
@@ -709,8 +981,12 @@ class OAuthManager {
709
981
  expiresAt: tokenData.expiresAt,
710
982
  scopes: tokenData.scopes
711
983
  };
984
+ const email = await fetchUserEmail(provider, tokenDataToStore);
985
+ if (email) {
986
+ tokenDataToStore.email = email;
987
+ }
712
988
  this.providerTokens.set(provider, tokenDataToStore);
713
- await this.saveProviderToken(provider, tokenDataToStore);
989
+ await this.saveProviderToken(provider, tokenDataToStore, email);
714
990
  this.pendingAuths.delete(state);
715
991
  this.removePendingAuthFromStorage(state);
716
992
  return { ...tokenDataToStore, provider };
@@ -746,8 +1022,12 @@ class OAuthManager {
746
1022
  expiresAt: response.expiresAt,
747
1023
  scopes: response.scopes
748
1024
  };
1025
+ const email = await fetchUserEmail(pendingAuth.provider, tokenData);
1026
+ if (email) {
1027
+ tokenData.email = email;
1028
+ }
749
1029
  this.providerTokens.set(pendingAuth.provider, tokenData);
750
- await this.saveProviderToken(pendingAuth.provider, tokenData);
1030
+ await this.saveProviderToken(pendingAuth.provider, tokenData, email);
751
1031
  this.pendingAuths.delete(state);
752
1032
  this.removePendingAuthFromStorage(state);
753
1033
  return { ...tokenData, provider: pendingAuth.provider };
@@ -757,8 +1037,8 @@ class OAuthManager {
757
1037
  throw error;
758
1038
  }
759
1039
  }
760
- async checkAuthStatus(provider) {
761
- const tokenData = await this.getProviderToken(provider);
1040
+ async checkAuthStatus(provider, email) {
1041
+ const tokenData = await this.getProviderToken(provider, email);
762
1042
  if (!tokenData) {
763
1043
  return {
764
1044
  authorized: false,
@@ -772,30 +1052,83 @@ class OAuthManager {
772
1052
  expiresAt: tokenData.expiresAt
773
1053
  };
774
1054
  }
1055
+ async disconnectAccount(provider, email, context) {
1056
+ if (this.removeTokenCallback) {
1057
+ try {
1058
+ await this.removeTokenCallback(provider, email, context);
1059
+ } catch (error) {
1060
+ console.error(`Failed to delete token for ${provider} (${email}) from database:`, error);
1061
+ }
1062
+ } else if (this.setTokenCallback) {
1063
+ try {
1064
+ await this.setTokenCallback(provider, null, email, context);
1065
+ } catch (error) {
1066
+ console.error(`Failed to delete token for ${provider} (${email}) from database:`, error);
1067
+ }
1068
+ }
1069
+ if (!this.setTokenCallback && !this.removeTokenCallback) {
1070
+ try {
1071
+ await this.indexedDBStorage.deleteToken(provider, email);
1072
+ } catch (error) {
1073
+ console.error(`Failed to delete token from IndexedDB for ${provider} (${email}):`, error);
1074
+ }
1075
+ }
1076
+ this.providerTokens.delete(provider);
1077
+ }
775
1078
  async disconnectProvider(provider, context) {
776
1079
  if (this.removeTokenCallback) {
777
1080
  try {
778
- await this.removeTokenCallback(provider, context);
1081
+ await this.removeTokenCallback(provider, undefined, context);
779
1082
  } catch (error) {
780
- console.error(`Failed to delete token for ${provider} from database via removeProviderToken:`, error);
1083
+ console.error(`Failed to delete tokens for ${provider} from database:`, error);
781
1084
  }
782
1085
  } else if (this.setTokenCallback) {
783
1086
  try {
784
- const tokenData = await this.getProviderToken(provider, context);
785
- if (tokenData) {
786
- await this.setTokenCallback(provider, null, context);
787
- }
1087
+ await this.setTokenCallback(provider, null, undefined, context);
1088
+ } catch (error) {
1089
+ console.error(`Failed to delete tokens for ${provider} from database:`, error);
1090
+ }
1091
+ }
1092
+ if (!this.setTokenCallback && !this.removeTokenCallback) {
1093
+ try {
1094
+ await this.indexedDBStorage.deleteTokensByProvider(provider);
788
1095
  } catch (error) {
789
- console.error(`Failed to delete token for ${provider} from database via setProviderToken:`, error);
1096
+ if (typeof window !== "undefined" && window.localStorage) {
1097
+ try {
1098
+ const key = `integrate_token_${provider}`;
1099
+ window.localStorage.removeItem(key);
1100
+ } catch (localStorageError) {}
1101
+ }
1102
+ }
1103
+ }
1104
+ if (!this.setTokenCallback && !this.removeTokenCallback) {
1105
+ if (typeof window !== "undefined" && window.localStorage) {
1106
+ try {
1107
+ const key = `integrate_token_${provider}`;
1108
+ window.localStorage.removeItem(key);
1109
+ } catch (localStorageError) {}
790
1110
  }
791
1111
  }
792
1112
  this.providerTokens.delete(provider);
793
- this.clearProviderToken(provider);
794
1113
  }
795
- async getProviderToken(provider, context) {
1114
+ async listAccounts(provider) {
1115
+ if (this.getTokenCallback) {
1116
+ return [];
1117
+ }
1118
+ if (!this.getTokenCallback) {
1119
+ try {
1120
+ return await this.indexedDBStorage.listAccounts(provider);
1121
+ } catch (error) {
1122
+ console.error(`Failed to list accounts for ${provider}:`, error);
1123
+ return [];
1124
+ }
1125
+ }
1126
+ return [];
1127
+ }
1128
+ async getProviderToken(provider, email, context) {
796
1129
  if (this.getTokenCallback) {
797
1130
  try {
798
- const tokenData = await this.getTokenCallback(provider, context);
1131
+ const tokenData = await this.getTokenCallback(provider, email, context);
799
1132
  if (tokenData) {
800
1133
  this.providerTokens.set(provider, tokenData);
801
1134
  }
@@ -805,6 +1138,17 @@ class OAuthManager {
805
1138
  return;
806
1139
  }
807
1140
  }
1141
+ if (email && !this.getTokenCallback) {
1142
+ try {
1143
+ const tokenData = await this.indexedDBStorage.getToken(provider, email);
1144
+ if (tokenData) {
1145
+ this.providerTokens.set(provider, tokenData);
1146
+ }
1147
+ return tokenData;
1148
+ } catch (error) {
1149
+ console.error(`Failed to get token from IndexedDB for ${provider}:`, error);
1150
+ }
1151
+ }
808
1152
  return this.providerTokens.get(provider);
809
1153
  }
810
1154
  getAllProviderTokens() {
@@ -813,35 +1157,45 @@ class OAuthManager {
813
1157
  getProviderTokenFromCache(provider) {
814
1158
  return this.providerTokens.get(provider);
815
1159
  }
816
- async setProviderToken(provider, tokenData, context) {
1160
+ async setProviderToken(provider, tokenData, email, context) {
1161
+ const tokenEmail = email || tokenData?.email;
817
1162
  if (tokenData === null) {
818
- this.providerTokens.delete(provider);
1163
+ if (tokenEmail) {
1164
+ this.providerTokens.delete(provider);
1165
+ } else {
1166
+ this.providerTokens.delete(provider);
1167
+ }
819
1168
  } else {
820
1169
  this.providerTokens.set(provider, tokenData);
821
1170
  }
822
- await this.saveProviderToken(provider, tokenData, context);
1171
+ await this.saveProviderToken(provider, tokenData, tokenEmail, context);
823
1172
  }
824
1173
  clearProviderToken(provider) {
825
1174
  this.providerTokens.delete(provider);
826
- if (!this.skipLocalStorage && typeof window !== "undefined" && window.localStorage) {
827
- try {
828
- window.localStorage.removeItem(`integrate_token_${provider}`);
829
- } catch (error) {
830
- console.error(`Failed to clear token for ${provider} from localStorage:`, error);
831
- }
1175
+ if (!this.setTokenCallback && !this.removeTokenCallback && !this.skipLocalStorage) {
1176
+ this.indexedDBStorage.deleteTokensByProvider(provider).catch((error) => {
1177
+ console.error(`Failed to clear tokens for ${provider} from IndexedDB:`, error);
1178
+ });
832
1179
  }
833
1180
  }
834
1181
  clearAllProviderTokens() {
835
- const providers = Array.from(this.providerTokens.keys());
836
1182
  this.providerTokens.clear();
837
- if (!this.skipLocalStorage && typeof window !== "undefined" && window.localStorage) {
838
- for (const provider of providers) {
1183
+ if (!this.setTokenCallback && !this.removeTokenCallback && !this.skipLocalStorage) {
1184
+ if (typeof window !== "undefined" && window.localStorage) {
839
1185
  try {
840
- window.localStorage.removeItem(`integrate_token_${provider}`);
841
- } catch (error) {
842
- console.error(`Failed to clear token for ${provider} from localStorage:`, error);
843
- }
1186
+ const keysToRemove = [];
1187
+ for (let i = 0;i < window.localStorage.length; i++) {
1188
+ const key = window.localStorage.key(i);
1189
+ if (key && key.startsWith("integrate_token_")) {
1190
+ keysToRemove.push(key);
1191
+ }
1192
+ }
1193
+ keysToRemove.forEach((key) => window.localStorage.removeItem(key));
1194
+ } catch (localStorageError) {}
844
1195
  }
1196
+ this.indexedDBStorage.clearAll().catch((error) => {
1197
+ console.error("Failed to clear all tokens from IndexedDB:", error);
1198
+ });
845
1199
  }
846
1200
  }
847
1201
  clearAllPendingAuths() {
@@ -862,66 +1216,125 @@ class OAuthManager {
862
1216
  }
863
1217
  }
864
1218
  }
865
- async saveProviderToken(provider, tokenData, context) {
1219
+ async saveProviderToken(provider, tokenData, email, context) {
866
1220
  if (this.setTokenCallback) {
867
1221
  try {
868
- await this.setTokenCallback(provider, tokenData, context);
1222
+ await this.setTokenCallback(provider, tokenData, email, context);
869
1223
  } catch (error) {
870
1224
  console.error(`Failed to ${tokenData === null ? "delete" : "save"} token for ${provider} via callback:`, error);
871
1225
  throw error;
872
1226
  }
873
1227
  return;
874
1228
  }
875
- if (tokenData === null) {
876
- this.clearProviderToken(provider);
1229
+ if (this.skipLocalStorage) {
877
1230
  return;
878
1231
  }
879
- if (this.skipLocalStorage) {
1232
+ if (tokenData === null) {
1233
+ if (email) {
1234
+ if (!this.setTokenCallback && !this.removeTokenCallback) {
1235
+ await this.indexedDBStorage.deleteToken(provider, email).catch(() => {
1236
+ if (typeof window !== "undefined" && window.localStorage) {
1237
+ try {
1238
+ const key = `integrate_token_${provider}`;
1239
+ window.localStorage.removeItem(key);
1240
+ } catch (localStorageError) {}
1241
+ }
1242
+ });
1243
+ }
1244
+ } else {
1245
+ this.clearProviderToken(provider);
1246
+ if (typeof window !== "undefined" && window.localStorage) {
1247
+ try {
1248
+ const key = `integrate_token_${provider}`;
1249
+ window.localStorage.removeItem(key);
1250
+ } catch (localStorageError) {}
1251
+ }
1252
+ }
880
1253
  return;
881
1254
  }
882
- if (typeof window !== "undefined" && window.localStorage) {
1255
+ const tokenEmail = email || tokenData.email;
1256
+ if (tokenEmail) {
883
1257
  try {
884
- const key = `integrate_token_${provider}`;
885
- window.localStorage.setItem(key, JSON.stringify(tokenData));
1258
+ await this.indexedDBStorage.saveToken(provider, tokenEmail, tokenData);
886
1259
  } catch (error) {
887
- console.error(`Failed to save token for ${provider} to localStorage:`, error);
1260
+ if (typeof window !== "undefined" && window.localStorage) {
1261
+ try {
1262
+ const key = `integrate_token_${provider}`;
1263
+ window.localStorage.setItem(key, JSON.stringify(tokenData));
1264
+ } catch (localStorageError) {
1265
+ console.error(`Failed to save token for ${provider} to localStorage:`, localStorageError);
1266
+ }
1267
+ } else {
1268
+ console.error(`Failed to save token for ${provider} to IndexedDB:`, error);
1269
+ }
1270
+ }
1271
+ } else {
1272
+ if (typeof window !== "undefined" && window.localStorage) {
1273
+ try {
1274
+ const key = `integrate_token_${provider}`;
1275
+ window.localStorage.setItem(key, JSON.stringify(tokenData));
1276
+ } catch (localStorageError) {
1277
+ console.error(`Failed to save token for ${provider} to localStorage:`, localStorageError);
1278
+ }
888
1279
  }
889
1280
  }
890
1281
  }
891
- async loadProviderToken(provider) {
1282
+ async loadProviderToken(provider, email, context) {
892
1283
  if (this.getTokenCallback) {
893
1284
  try {
894
- return await this.getTokenCallback(provider);
1285
+ return await this.getTokenCallback(provider, email, context);
895
1286
  } catch (error) {
896
1287
  console.error(`Failed to load token for ${provider} via callback:`, error);
897
1288
  return;
898
1289
  }
899
1290
  }
900
1291
  if (this.skipLocalStorage) {
901
- return;
1292
+ return this.providerTokens.get(provider);
902
1293
  }
903
- if (typeof window !== "undefined" && window.localStorage) {
1294
+ if (email) {
904
1295
  try {
905
- const key = `integrate_token_${provider}`;
906
- const stored = window.localStorage.getItem(key);
907
- if (stored) {
908
- return JSON.parse(stored);
1296
+ return await this.indexedDBStorage.getToken(provider, email);
1297
+ } catch (error) {
1298
+ if (typeof window !== "undefined" && window.localStorage) {
1299
+ try {
1300
+ const key = `integrate_token_${provider}`;
1301
+ const stored = window.localStorage.getItem(key);
1302
+ if (stored) {
1303
+ return JSON.parse(stored);
1304
+ }
1305
+ } catch (localStorageError) {}
1306
+ }
1307
+ return;
1308
+ }
1309
+ } else {
1310
+ try {
1311
+ const tokens = await this.indexedDBStorage.getTokensByProvider(provider);
1312
+ if (tokens.size > 0) {
1313
+ return tokens.values().next().value;
909
1314
  }
910
1315
  } catch (error) {
911
- console.error(`Failed to load token for ${provider} from localStorage:`, error);
1316
+ if (typeof window !== "undefined" && window.localStorage) {
1317
+ try {
1318
+ const key = `integrate_token_${provider}`;
1319
+ const stored = window.localStorage.getItem(key);
1320
+ if (stored) {
1321
+ return JSON.parse(stored);
1322
+ }
1323
+ } catch (localStorageError) {}
1324
+ }
912
1325
  }
913
1326
  }
914
1327
  return;
915
1328
  }
916
1329
  async loadAllProviderTokens(providers) {
917
1330
  for (const provider of providers) {
918
- const tokenData = await this.loadProviderToken(provider);
1331
+ const tokenData = await this.loadProviderToken(provider, undefined, undefined);
919
1332
  if (tokenData) {
920
1333
  this.providerTokens.set(provider, tokenData);
921
1334
  }
922
1335
  }
923
1336
  }
924
- loadProviderTokenSync(provider) {
1337
+ loadAllProviderTokensSync(providers) {
925
1338
  if (this.getTokenCallback) {
926
1339
  return;
927
1340
  }
@@ -929,28 +1342,18 @@ class OAuthManager {
929
1342
  return;
930
1343
  }
931
1344
  if (typeof window !== "undefined" && window.localStorage) {
932
- try {
933
- const key = `integrate_token_${provider}`;
934
- const stored = window.localStorage.getItem(key);
935
- if (stored) {
936
- return JSON.parse(stored);
937
- }
938
- } catch (error) {
939
- console.error(`Failed to load token for ${provider} from localStorage:`, error);
940
- }
941
- }
942
- return;
943
- }
944
- loadAllProviderTokensSync(providers) {
945
- if (this.getTokenCallback) {
946
- return;
947
- }
948
- for (const provider of providers) {
949
- const tokenData = this.loadProviderTokenSync(provider);
950
- if (tokenData) {
951
- this.providerTokens.set(provider, tokenData);
1345
+ for (const provider of providers) {
1346
+ try {
1347
+ const key = `integrate_token_${provider}`;
1348
+ const stored = window.localStorage.getItem(key);
1349
+ if (stored) {
1350
+ const tokenData = JSON.parse(stored);
1351
+ this.providerTokens.set(provider, tokenData);
1352
+ }
1353
+ } catch {}
952
1354
  }
953
1355
  }
1356
+ this.loadAllProviderTokens(providers).catch(() => {});
954
1357
  }
955
1358
  savePendingAuthToStorage(state, pendingAuth) {
956
1359
  if (typeof window !== "undefined" && window.localStorage) {
@@ -1033,7 +1436,7 @@ class OAuthManager {
1033
1436
  })
1034
1437
  });
1035
1438
  if (response.headers.get("X-Integrate-Use-Database") === "true") {
1036
- this.skipLocalStorage = true;
1439
+ this.setSkipLocalStorage(true);
1037
1440
  }
1038
1441
  if (!response.ok) {
1039
1442
  const error = await response.text();
@@ -1063,7 +1466,7 @@ class OAuthManager {
1063
1466
  })
1064
1467
  });
1065
1468
  if (response.headers.get("X-Integrate-Use-Database") === "true") {
1066
- this.skipLocalStorage = true;
1469
+ this.setSkipLocalStorage(true);
1067
1470
  }
1068
1471
  if (!response.ok) {
1069
1472
  const error = await response.text();
@@ -1332,7 +1735,7 @@ class MCPClientBase {
1332
1735
  const hasApiKey = !!transportHeaders["X-API-KEY"];
1333
1736
  if (hasApiKey) {
1334
1737
  if (provider) {
1335
- const tokenData = await this.oauthManager.getProviderToken(provider, options?.context);
1738
+ const tokenData = await this.oauthManager.getProviderToken(provider, undefined, options?.context);
1336
1739
  if (tokenData && this.transport.setHeader) {
1337
1740
  const previousAuthHeader = transportHeaders["Authorization"];
1338
1741
  try {
@@ -1362,7 +1765,7 @@ class MCPClientBase {
1362
1765
  "Content-Type": "application/json"
1363
1766
  };
1364
1767
  if (provider) {
1365
- const tokenData = await this.oauthManager.getProviderToken(provider, options?.context);
1768
+ const tokenData = await this.oauthManager.getProviderToken(provider, undefined, options?.context);
1366
1769
  if (tokenData) {
1367
1770
  headers["Authorization"] = `Bearer ${tokenData.accessToken}`;
1368
1771
  }
@@ -1506,6 +1909,29 @@ class MCPClientBase {
1506
1909
  throw error;
1507
1910
  }
1508
1911
  }
1912
+ async disconnectAccount(provider, email, context) {
1913
+ const integration = this.integrations.find((p) => p.oauth?.provider === provider);
1914
+ if (!integration?.oauth) {
1915
+ throw new Error(`No OAuth configuration found for provider: ${provider}`);
1916
+ }
1917
+ try {
1918
+ await this.oauthManager.disconnectAccount(provider, email, context);
1919
+ const accounts = await this.oauthManager.listAccounts(provider);
1920
+ if (accounts.length === 0) {
1921
+ this.authState.set(provider, { authenticated: false });
1922
+ }
1923
+ this.eventEmitter.emit("auth:disconnect", { provider });
1924
+ } catch (error) {
1925
+ this.eventEmitter.emit("auth:error", {
1926
+ provider,
1927
+ error
1928
+ });
1929
+ throw error;
1930
+ }
1931
+ }
1932
+ async listAccounts(provider) {
1933
+ return await this.oauthManager.listAccounts(provider);
1934
+ }
1509
1935
  async logout() {
1510
1936
  this.clearSessionToken();
1511
1937
  this.oauthManager.clearAllPendingAuths();
@@ -1538,13 +1964,13 @@ class MCPClientBase {
1538
1964
  isProviderAuthenticated(provider) {
1539
1965
  return this.authState.get(provider)?.authenticated ?? false;
1540
1966
  }
1541
- async isAuthorized(provider, context) {
1967
+ async isAuthorized(provider, email, context) {
1542
1968
  if (this.oauthCallbackPromise) {
1543
1969
  await this.oauthCallbackPromise;
1544
1970
  this.oauthCallbackPromise = null;
1545
1971
  }
1546
1972
  try {
1547
- const tokenData = await this.oauthManager.getProviderToken(provider, context);
1973
+ const tokenData = await this.oauthManager.getProviderToken(provider, email, context);
1548
1974
  const isAuthenticated = !!tokenData;
1549
1975
  const currentState = this.authState.get(provider);
1550
1976
  if (currentState) {
@@ -1582,8 +2008,8 @@ class MCPClientBase {
1582
2008
  }
1583
2009
  return authorized;
1584
2010
  }
1585
- async getAuthorizationStatus(provider) {
1586
- return await this.oauthManager.checkAuthStatus(provider);
2011
+ async getAuthorizationStatus(provider, email) {
2012
+ return await this.oauthManager.checkAuthStatus(provider, email);
1587
2013
  }
1588
2014
  async authorize(provider, options) {
1589
2015
  const integration = this.integrations.find((p) => p.oauth?.provider === provider);
@@ -1641,11 +2067,11 @@ class MCPClientBase {
1641
2067
  throw error;
1642
2068
  }
1643
2069
  }
1644
- async getProviderToken(provider, context) {
1645
- return await this.oauthManager.getProviderToken(provider, context);
2070
+ async getProviderToken(provider, email, context) {
2071
+ return await this.oauthManager.getProviderToken(provider, email, context);
1646
2072
  }
1647
- async setProviderToken(provider, tokenData, context) {
1648
- await this.oauthManager.setProviderToken(provider, tokenData, context);
2073
+ async setProviderToken(provider, tokenData, email, context) {
2074
+ await this.oauthManager.setProviderToken(provider, tokenData, email, context);
1649
2075
  if (tokenData === null) {
1650
2076
  this.authState.set(provider, { authenticated: false });
1651
2077
  } else {
@@ -7565,7 +7991,7 @@ class OAuthHandler {
7565
7991
  expiresAt: result.expiresAt,
7566
7992
  scopes: result.scopes
7567
7993
  };
7568
- await this.config.setProviderToken(callbackRequest.provider, tokenData, context);
7994
+ await this.config.setProviderToken(callbackRequest.provider, tokenData, undefined, context);
7569
7995
  } catch (error) {}
7570
7996
  }
7571
7997
  if (webRequest) {
@@ -7611,7 +8037,7 @@ class OAuthHandler {
7611
8037
  }
7612
8038
  if (context) {
7613
8039
  try {
7614
- await this.config.removeProviderToken(request.provider, context);
8040
+ await this.config.removeProviderToken(request.provider, undefined, context);
7615
8041
  } catch (error) {
7616
8042
  console.error(`Failed to delete token for ${request.provider} from database via removeProviderToken:`, error);
7617
8043
  }