mindcache 3.4.4 → 3.5.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.mjs CHANGED
@@ -367,10 +367,12 @@ var init_CloudAdapter = __esm({
367
367
  try {
368
368
  if (typeof event.data === "string") {
369
369
  const msg = JSON.parse(event.data);
370
+ console.log("\u2601\uFE0F CloudAdapter: Received JSON message:", msg.type, msg);
370
371
  if (msg.type === "auth_success") {
371
372
  this._state = "connected";
372
373
  this.reconnectAttempts = 0;
373
374
  this.emit("connected");
375
+ console.log("\u2601\uFE0F Connected to MindCache cloud");
374
376
  } else if (msg.type === "auth_error" || msg.type === "error") {
375
377
  this._state = "error";
376
378
  this.emit("error", new Error(msg.error));
@@ -378,6 +380,7 @@ var init_CloudAdapter = __esm({
378
380
  console.debug("MindCache Cloud: Received message type:", msg.type, msg);
379
381
  }
380
382
  } else {
383
+ console.log("\u2601\uFE0F CloudAdapter: Received binary message, length:", event.data.byteLength);
381
384
  const encoder = encoding.createEncoder();
382
385
  const decoder = decoding.createDecoder(new Uint8Array(event.data));
383
386
  if (this.mindcache) {
@@ -388,6 +391,7 @@ var init_CloudAdapter = __esm({
388
391
  if (!this._synced && (messageType === 1 || messageType === 2)) {
389
392
  this._synced = true;
390
393
  this.emit("synced");
394
+ console.log("\u2601\uFE0F Synced with cloud");
391
395
  }
392
396
  }
393
397
  }
@@ -2617,6 +2621,355 @@ var MindCache = class {
2617
2621
  init_CloudAdapter();
2618
2622
  init_CloudAdapter();
2619
2623
 
2624
+ // src/cloud/OAuthClient.ts
2625
+ var DEFAULT_AUTH_URL = "https://api.mindcache.dev/oauth/authorize";
2626
+ var DEFAULT_TOKEN_URL = "https://api.mindcache.dev/oauth/token";
2627
+ var DEFAULT_USERINFO_URL = "https://api.mindcache.dev/oauth/userinfo";
2628
+ var TOKEN_REFRESH_BUFFER = 5 * 60 * 1e3;
2629
+ function generateRandomString(length2) {
2630
+ const array = new Uint8Array(length2);
2631
+ crypto.getRandomValues(array);
2632
+ return Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length2);
2633
+ }
2634
+ function base64UrlEncode(buffer) {
2635
+ const bytes = new Uint8Array(buffer);
2636
+ let binary = "";
2637
+ for (let i = 0; i < bytes.length; i++) {
2638
+ binary += String.fromCharCode(bytes[i]);
2639
+ }
2640
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
2641
+ }
2642
+ function generateCodeVerifier() {
2643
+ return generateRandomString(64);
2644
+ }
2645
+ async function generateCodeChallenge(verifier) {
2646
+ const encoder = new TextEncoder();
2647
+ const data = encoder.encode(verifier);
2648
+ const hash = await crypto.subtle.digest("SHA-256", data);
2649
+ return base64UrlEncode(hash);
2650
+ }
2651
+ var OAuthClient = class {
2652
+ config;
2653
+ tokens = null;
2654
+ refreshPromise = null;
2655
+ constructor(config) {
2656
+ let redirectUri = config.redirectUri;
2657
+ if (!redirectUri && typeof window !== "undefined") {
2658
+ const url = new URL(window.location.href);
2659
+ url.search = "";
2660
+ url.hash = "";
2661
+ redirectUri = url.toString();
2662
+ }
2663
+ this.config = {
2664
+ clientId: config.clientId,
2665
+ redirectUri: redirectUri || "",
2666
+ scopes: config.scopes || ["read", "write"],
2667
+ authUrl: config.authUrl || DEFAULT_AUTH_URL,
2668
+ tokenUrl: config.tokenUrl || DEFAULT_TOKEN_URL,
2669
+ apiUrl: config.apiUrl || (config.tokenUrl || DEFAULT_TOKEN_URL).replace("/oauth/token", ""),
2670
+ usePKCE: config.usePKCE !== false,
2671
+ // Default true
2672
+ storagePrefix: config.storagePrefix || "mindcache_oauth"
2673
+ };
2674
+ this.loadTokens();
2675
+ }
2676
+ /**
2677
+ * Check if user is authenticated
2678
+ */
2679
+ isAuthenticated() {
2680
+ return this.tokens !== null && this.tokens.expiresAt > Date.now();
2681
+ }
2682
+ /**
2683
+ * Get stored tokens (if any)
2684
+ */
2685
+ getTokens() {
2686
+ return this.tokens;
2687
+ }
2688
+ /**
2689
+ * Get instance ID for this user+app
2690
+ */
2691
+ getInstanceId() {
2692
+ return this.tokens?.instanceId || null;
2693
+ }
2694
+ /**
2695
+ * Start OAuth authorization flow
2696
+ * Redirects to MindCache authorization page
2697
+ */
2698
+ async authorize(options) {
2699
+ const state = options?.state || generateRandomString(32);
2700
+ this.setStorage("state", state);
2701
+ const url = new URL(this.config.authUrl);
2702
+ url.searchParams.set("response_type", "code");
2703
+ url.searchParams.set("client_id", this.config.clientId);
2704
+ url.searchParams.set("redirect_uri", this.config.redirectUri);
2705
+ url.searchParams.set("scope", this.config.scopes.join(" "));
2706
+ url.searchParams.set("state", state);
2707
+ if (this.config.usePKCE) {
2708
+ const codeVerifier = generateCodeVerifier();
2709
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
2710
+ this.setStorage("code_verifier", codeVerifier);
2711
+ url.searchParams.set("code_challenge", codeChallenge);
2712
+ url.searchParams.set("code_challenge_method", "S256");
2713
+ }
2714
+ if (options?.popup) {
2715
+ const popup = window.open(url.toString(), "mindcache_oauth", "width=500,height=600");
2716
+ if (!popup) {
2717
+ throw new Error("Popup blocked. Please allow popups for this site.");
2718
+ }
2719
+ } else {
2720
+ window.location.href = url.toString();
2721
+ }
2722
+ }
2723
+ /**
2724
+ * Handle OAuth callback
2725
+ * Call this on your redirect URI page
2726
+ *
2727
+ * @returns Tokens if successful
2728
+ */
2729
+ async handleCallback() {
2730
+ if (typeof window === "undefined") {
2731
+ throw new Error("handleCallback must be called in browser");
2732
+ }
2733
+ const url = new URL(window.location.href);
2734
+ const code = url.searchParams.get("code");
2735
+ const state = url.searchParams.get("state");
2736
+ const error = url.searchParams.get("error");
2737
+ const errorDescription = url.searchParams.get("error_description");
2738
+ if (error) {
2739
+ this.clearStorage();
2740
+ throw new Error(errorDescription || error);
2741
+ }
2742
+ const storedState = this.getStorage("state");
2743
+ if (!state || state !== storedState) {
2744
+ this.clearStorage();
2745
+ throw new Error("Invalid state parameter");
2746
+ }
2747
+ if (!code) {
2748
+ this.clearStorage();
2749
+ throw new Error("No authorization code received");
2750
+ }
2751
+ const body = {
2752
+ grant_type: "authorization_code",
2753
+ code,
2754
+ client_id: this.config.clientId,
2755
+ redirect_uri: this.config.redirectUri
2756
+ };
2757
+ if (this.config.usePKCE) {
2758
+ const codeVerifier = this.getStorage("code_verifier");
2759
+ if (!codeVerifier) {
2760
+ throw new Error("Missing code verifier");
2761
+ }
2762
+ body.code_verifier = codeVerifier;
2763
+ }
2764
+ const response = await fetch(this.config.tokenUrl, {
2765
+ method: "POST",
2766
+ headers: {
2767
+ "Content-Type": "application/json"
2768
+ },
2769
+ body: JSON.stringify(body)
2770
+ });
2771
+ if (!response.ok) {
2772
+ const data2 = await response.json().catch(() => ({}));
2773
+ throw new Error(data2.error_description || data2.error || "Token exchange failed");
2774
+ }
2775
+ const data = await response.json();
2776
+ this.tokens = {
2777
+ accessToken: data.access_token,
2778
+ refreshToken: data.refresh_token,
2779
+ expiresAt: Date.now() + data.expires_in * 1e3,
2780
+ scopes: data.scope?.split(" ") || this.config.scopes,
2781
+ instanceId: data.instance_id
2782
+ };
2783
+ this.saveTokens();
2784
+ url.searchParams.delete("code");
2785
+ url.searchParams.delete("state");
2786
+ window.history.replaceState({}, "", url.toString());
2787
+ this.removeStorage("state");
2788
+ this.removeStorage("code_verifier");
2789
+ return this.tokens;
2790
+ }
2791
+ /**
2792
+ * Get a valid access token
2793
+ * Automatically refreshes if needed
2794
+ */
2795
+ async getAccessToken() {
2796
+ if (!this.tokens) {
2797
+ throw new Error("Not authenticated. Call authorize() first.");
2798
+ }
2799
+ const needsRefresh = this.tokens.expiresAt - Date.now() < TOKEN_REFRESH_BUFFER;
2800
+ if (needsRefresh && this.tokens.refreshToken) {
2801
+ if (!this.refreshPromise) {
2802
+ this.refreshPromise = this.refreshTokens();
2803
+ }
2804
+ return this.refreshPromise;
2805
+ }
2806
+ return this.tokens.accessToken;
2807
+ }
2808
+ /**
2809
+ * Refresh access token
2810
+ */
2811
+ async refreshTokens() {
2812
+ if (!this.tokens?.refreshToken) {
2813
+ throw new Error("No refresh token available");
2814
+ }
2815
+ try {
2816
+ const response = await fetch(this.config.tokenUrl, {
2817
+ method: "POST",
2818
+ headers: {
2819
+ "Content-Type": "application/json"
2820
+ },
2821
+ body: JSON.stringify({
2822
+ grant_type: "refresh_token",
2823
+ refresh_token: this.tokens.refreshToken,
2824
+ client_id: this.config.clientId
2825
+ })
2826
+ });
2827
+ if (!response.ok) {
2828
+ this.clearAuth();
2829
+ throw new Error("Session expired. Please sign in again.");
2830
+ }
2831
+ const data = await response.json();
2832
+ this.tokens = {
2833
+ accessToken: data.access_token,
2834
+ refreshToken: data.refresh_token || this.tokens.refreshToken,
2835
+ expiresAt: Date.now() + data.expires_in * 1e3,
2836
+ scopes: data.scope?.split(" ") || this.tokens.scopes,
2837
+ instanceId: data.instance_id || this.tokens.instanceId
2838
+ };
2839
+ this.saveTokens();
2840
+ return this.tokens.accessToken;
2841
+ } finally {
2842
+ this.refreshPromise = null;
2843
+ }
2844
+ }
2845
+ /**
2846
+ * Get user info from MindCache
2847
+ */
2848
+ async getUserInfo() {
2849
+ const token = await this.getAccessToken();
2850
+ const response = await fetch(DEFAULT_USERINFO_URL, {
2851
+ headers: {
2852
+ Authorization: `Bearer ${token}`
2853
+ }
2854
+ });
2855
+ if (!response.ok) {
2856
+ throw new Error("Failed to get user info");
2857
+ }
2858
+ const data = await response.json();
2859
+ return {
2860
+ id: data.sub,
2861
+ email: data.email,
2862
+ name: data.name,
2863
+ instanceId: data.instance_id
2864
+ };
2865
+ }
2866
+ /**
2867
+ * Logout - revoke tokens and clear storage
2868
+ */
2869
+ async logout() {
2870
+ if (this.tokens?.accessToken) {
2871
+ try {
2872
+ await fetch(this.config.tokenUrl.replace("/token", "/revoke"), {
2873
+ method: "POST",
2874
+ headers: {
2875
+ "Content-Type": "application/json"
2876
+ },
2877
+ body: JSON.stringify({
2878
+ token: this.tokens.accessToken
2879
+ })
2880
+ });
2881
+ } catch {
2882
+ }
2883
+ }
2884
+ this.clearAuth();
2885
+ }
2886
+ /**
2887
+ * Clear authentication state
2888
+ */
2889
+ clearAuth() {
2890
+ this.tokens = null;
2891
+ this.removeStorage("tokens");
2892
+ }
2893
+ /**
2894
+ * Token provider for MindCache cloud config
2895
+ * This fetches a WebSocket token (short-lived) using the OAuth access token
2896
+ * Use this with MindCacheCloudOptions.tokenProvider
2897
+ */
2898
+ tokenProvider = async () => {
2899
+ const accessToken = await this.getAccessToken();
2900
+ const instanceId = this.getInstanceId();
2901
+ if (!instanceId) {
2902
+ throw new Error("No instance ID available. Complete OAuth flow first.");
2903
+ }
2904
+ const response = await fetch(`${this.config.apiUrl}/api/ws-token`, {
2905
+ method: "POST",
2906
+ headers: {
2907
+ "Content-Type": "application/json",
2908
+ "Authorization": `Bearer ${accessToken}`
2909
+ },
2910
+ body: JSON.stringify({
2911
+ instanceId,
2912
+ permission: "write"
2913
+ })
2914
+ });
2915
+ if (!response.ok) {
2916
+ const data2 = await response.json().catch(() => ({}));
2917
+ throw new Error(data2.error || "Failed to get WebSocket token");
2918
+ }
2919
+ const data = await response.json();
2920
+ return data.token;
2921
+ };
2922
+ /**
2923
+ * Get raw OAuth access token (for API calls, not WebSocket)
2924
+ * Use getAccessToken() for most cases
2925
+ */
2926
+ accessTokenProvider = async () => {
2927
+ return this.getAccessToken();
2928
+ };
2929
+ // Storage helpers
2930
+ getStorage(key) {
2931
+ if (typeof localStorage === "undefined") {
2932
+ return null;
2933
+ }
2934
+ return localStorage.getItem(`${this.config.storagePrefix}_${key}`);
2935
+ }
2936
+ setStorage(key, value) {
2937
+ if (typeof localStorage === "undefined") {
2938
+ return;
2939
+ }
2940
+ localStorage.setItem(`${this.config.storagePrefix}_${key}`, value);
2941
+ }
2942
+ removeStorage(key) {
2943
+ if (typeof localStorage === "undefined") {
2944
+ return;
2945
+ }
2946
+ localStorage.removeItem(`${this.config.storagePrefix}_${key}`);
2947
+ }
2948
+ clearStorage() {
2949
+ this.removeStorage("state");
2950
+ this.removeStorage("code_verifier");
2951
+ this.removeStorage("tokens");
2952
+ }
2953
+ loadTokens() {
2954
+ const stored = this.getStorage("tokens");
2955
+ if (stored) {
2956
+ try {
2957
+ this.tokens = JSON.parse(stored);
2958
+ } catch {
2959
+ this.tokens = null;
2960
+ }
2961
+ }
2962
+ }
2963
+ saveTokens() {
2964
+ if (this.tokens) {
2965
+ this.setStorage("tokens", JSON.stringify(this.tokens));
2966
+ }
2967
+ }
2968
+ };
2969
+ function createOAuthClient(config) {
2970
+ return new OAuthClient(config);
2971
+ }
2972
+
2620
2973
  // src/local/index.ts
2621
2974
  init_IndexedDBAdapter();
2622
2975
  function useMindCache(options) {
@@ -2657,6 +3010,6 @@ function useMindCache(options) {
2657
3010
  // src/index.ts
2658
3011
  var mindcache = new MindCache();
2659
3012
 
2660
- export { CloudAdapter, DEFAULT_KEY_ATTRIBUTES, IndexedDBAdapter, MindCache, SystemTagHelpers, mindcache, useMindCache };
3013
+ export { CloudAdapter, DEFAULT_KEY_ATTRIBUTES, IndexedDBAdapter, MindCache, OAuthClient, SystemTagHelpers, createOAuthClient, mindcache, useMindCache };
2661
3014
  //# sourceMappingURL=index.mjs.map
2662
3015
  //# sourceMappingURL=index.mjs.map