File size: 3,509 Bytes
9a9d18a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import React, {
  createContext,
  useContext,
  useState,
  useEffect,
  ReactNode,
} from "react";

interface ApiContextType {
  baseUrl: string;
  wsBaseUrl: string;
  isNgrokEnabled: boolean;
  setNgrokUrl: (url: string) => void;
  resetToLocalhost: () => void;
  ngrokUrl: string;
  getHeaders: () => Record<string, string>;
  fetchWithHeaders: (url: string, options?: RequestInit) => Promise<Response>;
}

const ApiContext = createContext<ApiContextType | undefined>(undefined);

const DEFAULT_LOCALHOST = "http://localhost:8000";
const DEFAULT_WS_LOCALHOST = "ws://localhost:8000";

interface ApiProviderProps {
  children: ReactNode;
}

export const ApiProvider: React.FC<ApiProviderProps> = ({ children }) => {
  const [ngrokUrl, setNgrokUrlState] = useState<string>("");
  const [isNgrokEnabled, setIsNgrokEnabled] = useState<boolean>(false);

  // Load saved ngrok configuration on mount
  useEffect(() => {
    const savedNgrokUrl = localStorage.getItem("ngrok-url");
    const savedNgrokEnabled = localStorage.getItem("ngrok-enabled") === "true";

    if (savedNgrokUrl && savedNgrokEnabled) {
      setNgrokUrlState(savedNgrokUrl);
      setIsNgrokEnabled(true);
    }
  }, []);

  const setNgrokUrl = (url: string) => {
    // Clean and validate the URL
    let cleanUrl = url.trim();
    if (cleanUrl && !cleanUrl.startsWith("http")) {
      cleanUrl = `https://${cleanUrl}`;
    }

    // Remove trailing slash
    cleanUrl = cleanUrl.replace(/\/$/, "");

    setNgrokUrlState(cleanUrl);
    setIsNgrokEnabled(!!cleanUrl);

    // Persist to localStorage
    if (cleanUrl) {
      localStorage.setItem("ngrok-url", cleanUrl);
      localStorage.setItem("ngrok-enabled", "true");
    } else {
      localStorage.removeItem("ngrok-url");
      localStorage.removeItem("ngrok-enabled");
    }
  };

  const resetToLocalhost = () => {
    setNgrokUrlState("");
    setIsNgrokEnabled(false);
    localStorage.removeItem("ngrok-url");
    localStorage.removeItem("ngrok-enabled");
  };

  const baseUrl = isNgrokEnabled && ngrokUrl ? ngrokUrl : DEFAULT_LOCALHOST;
  const wsBaseUrl =
    isNgrokEnabled && ngrokUrl
      ? ngrokUrl.replace("https://", "wss://").replace("http://", "ws://")
      : DEFAULT_WS_LOCALHOST;

  // Helper function to get headers with ngrok skip warning if needed
  const getHeaders = (): Record<string, string> => {
    const headers: Record<string, string> = {
      "Content-Type": "application/json",
    };

    // Add ngrok skip warning header when using ngrok
    if (isNgrokEnabled && ngrokUrl) {
      headers["ngrok-skip-browser-warning"] = "true";
    }

    return headers;
  };

  // Enhanced fetch function that automatically includes necessary headers
  const fetchWithHeaders = async (
    url: string,
    options: RequestInit = {}
  ): Promise<Response> => {
    const enhancedOptions: RequestInit = {
      ...options,
      headers: {
        ...getHeaders(),
        ...options.headers,
      },
    };

    return fetch(url, enhancedOptions);
  };

  return (
    <ApiContext.Provider
      value={{
        baseUrl,
        wsBaseUrl,
        isNgrokEnabled,
        setNgrokUrl,
        resetToLocalhost,
        ngrokUrl,
        getHeaders,
        fetchWithHeaders,
      }}
    >
      {children}
    </ApiContext.Provider>
  );
};

export const useApi = (): ApiContextType => {
  const context = useContext(ApiContext);
  if (context === undefined) {
    throw new Error("useApi must be used within an ApiProvider");
  }
  return context;
};