mindcache 3.4.4 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2617,6 +2617,325 @@ var MindCache = class {
2617
2617
  init_CloudAdapter();
2618
2618
  init_CloudAdapter();
2619
2619
 
2620
+ // src/cloud/OAuthClient.ts
2621
+ var DEFAULT_AUTH_URL = "https://api.mindcache.dev/oauth/authorize";
2622
+ var DEFAULT_TOKEN_URL = "https://api.mindcache.dev/oauth/token";
2623
+ var DEFAULT_USERINFO_URL = "https://api.mindcache.dev/oauth/userinfo";
2624
+ var TOKEN_REFRESH_BUFFER = 5 * 60 * 1e3;
2625
+ function generateRandomString(length2) {
2626
+ const array = new Uint8Array(length2);
2627
+ crypto.getRandomValues(array);
2628
+ return Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length2);
2629
+ }
2630
+ function base64UrlEncode(buffer) {
2631
+ const bytes = new Uint8Array(buffer);
2632
+ let binary = "";
2633
+ for (let i = 0; i < bytes.length; i++) {
2634
+ binary += String.fromCharCode(bytes[i]);
2635
+ }
2636
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
2637
+ }
2638
+ function generateCodeVerifier() {
2639
+ return generateRandomString(64);
2640
+ }
2641
+ async function generateCodeChallenge(verifier) {
2642
+ const encoder = new TextEncoder();
2643
+ const data = encoder.encode(verifier);
2644
+ const hash = await crypto.subtle.digest("SHA-256", data);
2645
+ return base64UrlEncode(hash);
2646
+ }
2647
+ var OAuthClient = class {
2648
+ config;
2649
+ tokens = null;
2650
+ refreshPromise = null;
2651
+ constructor(config) {
2652
+ let redirectUri = config.redirectUri;
2653
+ if (!redirectUri && typeof window !== "undefined") {
2654
+ const url = new URL(window.location.href);
2655
+ url.search = "";
2656
+ url.hash = "";
2657
+ redirectUri = url.toString();
2658
+ }
2659
+ this.config = {
2660
+ clientId: config.clientId,
2661
+ redirectUri: redirectUri || "",
2662
+ scopes: config.scopes || ["read", "write"],
2663
+ authUrl: config.authUrl || DEFAULT_AUTH_URL,
2664
+ tokenUrl: config.tokenUrl || DEFAULT_TOKEN_URL,
2665
+ usePKCE: config.usePKCE !== false,
2666
+ // Default true
2667
+ storagePrefix: config.storagePrefix || "mindcache_oauth"
2668
+ };
2669
+ this.loadTokens();
2670
+ }
2671
+ /**
2672
+ * Check if user is authenticated
2673
+ */
2674
+ isAuthenticated() {
2675
+ return this.tokens !== null && this.tokens.expiresAt > Date.now();
2676
+ }
2677
+ /**
2678
+ * Get stored tokens (if any)
2679
+ */
2680
+ getTokens() {
2681
+ return this.tokens;
2682
+ }
2683
+ /**
2684
+ * Get instance ID for this user+app
2685
+ */
2686
+ getInstanceId() {
2687
+ return this.tokens?.instanceId || null;
2688
+ }
2689
+ /**
2690
+ * Start OAuth authorization flow
2691
+ * Redirects to MindCache authorization page
2692
+ */
2693
+ async authorize(options) {
2694
+ const state = options?.state || generateRandomString(32);
2695
+ this.setStorage("state", state);
2696
+ const url = new URL(this.config.authUrl);
2697
+ url.searchParams.set("response_type", "code");
2698
+ url.searchParams.set("client_id", this.config.clientId);
2699
+ url.searchParams.set("redirect_uri", this.config.redirectUri);
2700
+ url.searchParams.set("scope", this.config.scopes.join(" "));
2701
+ url.searchParams.set("state", state);
2702
+ if (this.config.usePKCE) {
2703
+ const codeVerifier = generateCodeVerifier();
2704
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
2705
+ this.setStorage("code_verifier", codeVerifier);
2706
+ url.searchParams.set("code_challenge", codeChallenge);
2707
+ url.searchParams.set("code_challenge_method", "S256");
2708
+ }
2709
+ if (options?.popup) {
2710
+ const popup = window.open(url.toString(), "mindcache_oauth", "width=500,height=600");
2711
+ if (!popup) {
2712
+ throw new Error("Popup blocked. Please allow popups for this site.");
2713
+ }
2714
+ } else {
2715
+ window.location.href = url.toString();
2716
+ }
2717
+ }
2718
+ /**
2719
+ * Handle OAuth callback
2720
+ * Call this on your redirect URI page
2721
+ *
2722
+ * @returns Tokens if successful
2723
+ */
2724
+ async handleCallback() {
2725
+ if (typeof window === "undefined") {
2726
+ throw new Error("handleCallback must be called in browser");
2727
+ }
2728
+ const url = new URL(window.location.href);
2729
+ const code = url.searchParams.get("code");
2730
+ const state = url.searchParams.get("state");
2731
+ const error = url.searchParams.get("error");
2732
+ const errorDescription = url.searchParams.get("error_description");
2733
+ if (error) {
2734
+ this.clearStorage();
2735
+ throw new Error(errorDescription || error);
2736
+ }
2737
+ const storedState = this.getStorage("state");
2738
+ if (!state || state !== storedState) {
2739
+ this.clearStorage();
2740
+ throw new Error("Invalid state parameter");
2741
+ }
2742
+ if (!code) {
2743
+ this.clearStorage();
2744
+ throw new Error("No authorization code received");
2745
+ }
2746
+ const body = {
2747
+ grant_type: "authorization_code",
2748
+ code,
2749
+ client_id: this.config.clientId,
2750
+ redirect_uri: this.config.redirectUri
2751
+ };
2752
+ if (this.config.usePKCE) {
2753
+ const codeVerifier = this.getStorage("code_verifier");
2754
+ if (!codeVerifier) {
2755
+ throw new Error("Missing code verifier");
2756
+ }
2757
+ body.code_verifier = codeVerifier;
2758
+ }
2759
+ const response = await fetch(this.config.tokenUrl, {
2760
+ method: "POST",
2761
+ headers: {
2762
+ "Content-Type": "application/json"
2763
+ },
2764
+ body: JSON.stringify(body)
2765
+ });
2766
+ if (!response.ok) {
2767
+ const data2 = await response.json().catch(() => ({}));
2768
+ throw new Error(data2.error_description || data2.error || "Token exchange failed");
2769
+ }
2770
+ const data = await response.json();
2771
+ this.tokens = {
2772
+ accessToken: data.access_token,
2773
+ refreshToken: data.refresh_token,
2774
+ expiresAt: Date.now() + data.expires_in * 1e3,
2775
+ scopes: data.scope?.split(" ") || this.config.scopes,
2776
+ instanceId: data.instance_id
2777
+ };
2778
+ this.saveTokens();
2779
+ url.searchParams.delete("code");
2780
+ url.searchParams.delete("state");
2781
+ window.history.replaceState({}, "", url.toString());
2782
+ this.removeStorage("state");
2783
+ this.removeStorage("code_verifier");
2784
+ return this.tokens;
2785
+ }
2786
+ /**
2787
+ * Get a valid access token
2788
+ * Automatically refreshes if needed
2789
+ */
2790
+ async getAccessToken() {
2791
+ if (!this.tokens) {
2792
+ throw new Error("Not authenticated. Call authorize() first.");
2793
+ }
2794
+ const needsRefresh = this.tokens.expiresAt - Date.now() < TOKEN_REFRESH_BUFFER;
2795
+ if (needsRefresh && this.tokens.refreshToken) {
2796
+ if (!this.refreshPromise) {
2797
+ this.refreshPromise = this.refreshTokens();
2798
+ }
2799
+ return this.refreshPromise;
2800
+ }
2801
+ return this.tokens.accessToken;
2802
+ }
2803
+ /**
2804
+ * Refresh access token
2805
+ */
2806
+ async refreshTokens() {
2807
+ if (!this.tokens?.refreshToken) {
2808
+ throw new Error("No refresh token available");
2809
+ }
2810
+ try {
2811
+ const response = await fetch(this.config.tokenUrl, {
2812
+ method: "POST",
2813
+ headers: {
2814
+ "Content-Type": "application/json"
2815
+ },
2816
+ body: JSON.stringify({
2817
+ grant_type: "refresh_token",
2818
+ refresh_token: this.tokens.refreshToken,
2819
+ client_id: this.config.clientId
2820
+ })
2821
+ });
2822
+ if (!response.ok) {
2823
+ this.clearAuth();
2824
+ throw new Error("Session expired. Please sign in again.");
2825
+ }
2826
+ const data = await response.json();
2827
+ this.tokens = {
2828
+ accessToken: data.access_token,
2829
+ refreshToken: data.refresh_token || this.tokens.refreshToken,
2830
+ expiresAt: Date.now() + data.expires_in * 1e3,
2831
+ scopes: data.scope?.split(" ") || this.tokens.scopes,
2832
+ instanceId: data.instance_id || this.tokens.instanceId
2833
+ };
2834
+ this.saveTokens();
2835
+ return this.tokens.accessToken;
2836
+ } finally {
2837
+ this.refreshPromise = null;
2838
+ }
2839
+ }
2840
+ /**
2841
+ * Get user info from MindCache
2842
+ */
2843
+ async getUserInfo() {
2844
+ const token = await this.getAccessToken();
2845
+ const response = await fetch(DEFAULT_USERINFO_URL, {
2846
+ headers: {
2847
+ Authorization: `Bearer ${token}`
2848
+ }
2849
+ });
2850
+ if (!response.ok) {
2851
+ throw new Error("Failed to get user info");
2852
+ }
2853
+ const data = await response.json();
2854
+ return {
2855
+ id: data.sub,
2856
+ email: data.email,
2857
+ name: data.name,
2858
+ instanceId: data.instance_id
2859
+ };
2860
+ }
2861
+ /**
2862
+ * Logout - revoke tokens and clear storage
2863
+ */
2864
+ async logout() {
2865
+ if (this.tokens?.accessToken) {
2866
+ try {
2867
+ await fetch(this.config.tokenUrl.replace("/token", "/revoke"), {
2868
+ method: "POST",
2869
+ headers: {
2870
+ "Content-Type": "application/json"
2871
+ },
2872
+ body: JSON.stringify({
2873
+ token: this.tokens.accessToken
2874
+ })
2875
+ });
2876
+ } catch {
2877
+ }
2878
+ }
2879
+ this.clearAuth();
2880
+ }
2881
+ /**
2882
+ * Clear authentication state
2883
+ */
2884
+ clearAuth() {
2885
+ this.tokens = null;
2886
+ this.removeStorage("tokens");
2887
+ }
2888
+ /**
2889
+ * Token provider function for MindCache cloud config
2890
+ * Use this with MindCacheCloudOptions.tokenProvider
2891
+ */
2892
+ tokenProvider = async () => {
2893
+ return this.getAccessToken();
2894
+ };
2895
+ // Storage helpers
2896
+ getStorage(key) {
2897
+ if (typeof localStorage === "undefined") {
2898
+ return null;
2899
+ }
2900
+ return localStorage.getItem(`${this.config.storagePrefix}_${key}`);
2901
+ }
2902
+ setStorage(key, value) {
2903
+ if (typeof localStorage === "undefined") {
2904
+ return;
2905
+ }
2906
+ localStorage.setItem(`${this.config.storagePrefix}_${key}`, value);
2907
+ }
2908
+ removeStorage(key) {
2909
+ if (typeof localStorage === "undefined") {
2910
+ return;
2911
+ }
2912
+ localStorage.removeItem(`${this.config.storagePrefix}_${key}`);
2913
+ }
2914
+ clearStorage() {
2915
+ this.removeStorage("state");
2916
+ this.removeStorage("code_verifier");
2917
+ this.removeStorage("tokens");
2918
+ }
2919
+ loadTokens() {
2920
+ const stored = this.getStorage("tokens");
2921
+ if (stored) {
2922
+ try {
2923
+ this.tokens = JSON.parse(stored);
2924
+ } catch {
2925
+ this.tokens = null;
2926
+ }
2927
+ }
2928
+ }
2929
+ saveTokens() {
2930
+ if (this.tokens) {
2931
+ this.setStorage("tokens", JSON.stringify(this.tokens));
2932
+ }
2933
+ }
2934
+ };
2935
+ function createOAuthClient(config) {
2936
+ return new OAuthClient(config);
2937
+ }
2938
+
2620
2939
  // src/local/index.ts
2621
2940
  init_IndexedDBAdapter();
2622
2941
  function useMindCache(options) {
@@ -2657,6 +2976,6 @@ function useMindCache(options) {
2657
2976
  // src/index.ts
2658
2977
  var mindcache = new MindCache();
2659
2978
 
2660
- export { CloudAdapter, DEFAULT_KEY_ATTRIBUTES, IndexedDBAdapter, MindCache, SystemTagHelpers, mindcache, useMindCache };
2979
+ export { CloudAdapter, DEFAULT_KEY_ATTRIBUTES, IndexedDBAdapter, MindCache, OAuthClient, SystemTagHelpers, createOAuthClient, mindcache, useMindCache };
2661
2980
  //# sourceMappingURL=index.mjs.map
2662
2981
  //# sourceMappingURL=index.mjs.map