import axios, { AxiosInstance } from "axios"; import * as SecureStore from "expo-secure-store"; import React, { createContext, useCallback, useContext, useEffect, useRef, useState, } from "react"; const API_KEY_STORAGE = "postiz_api_key"; const BASE_URL_STORAGE = "postiz_base_url"; export const DEFAULT_BASE_URL = "https://postiz.gyozamancave.fr/api/public/v1"; export interface PostizIntegration { id: string; name: string; type: string; picture?: string; identifier?: string; internalType?: string; } export interface PostizMediaItem { id: string; path: string; } export interface PostizPost { id: string; content: string; state: "QUEUE" | "PUBLISHED" | "ERROR" | "DRAFT"; publishDate: string; integration?: PostizIntegration; integrations?: PostizIntegration[]; image?: PostizMediaItem[]; group?: string; errorMessage?: string; } export interface PostizUploadResult { id: string; path: string; } interface PostizContextValue { apiKey: string; baseUrl: string; isConfigured: boolean; isLoading: boolean; unauthorized: boolean; clearUnauthorized: () => void; client: AxiosInstance | null; saveSettings: (apiKey: string, baseUrl: string) => Promise; clearSettings: () => Promise; } const PostizContext = createContext({ apiKey: "", baseUrl: DEFAULT_BASE_URL, isConfigured: false, isLoading: true, unauthorized: false, clearUnauthorized: () => {}, client: null, saveSettings: async () => {}, clearSettings: async () => {}, }); function createClient( apiKey: string, baseUrl: string, onUnauthorized?: () => void ): AxiosInstance { const normalizedUrl = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/"; const instance = axios.create({ baseURL: normalizedUrl, headers: { Authorization: apiKey, "Content-Type": "application/json", }, timeout: 15000, }); instance.interceptors.request.use((config) => { console.log(">>> REQUEST:", config.method?.toUpperCase(), (config.baseURL || "") + (config.url || "")); return config; }); instance.interceptors.response.use( (res) => res, (err) => { if (axios.isAxiosError(err) && err.response?.status === 401) { onUnauthorized?.(); } return Promise.reject(err); } ); return instance; } export function PostizProvider({ children }: { children: React.ReactNode }) { const [apiKey, setApiKey] = useState(""); const [baseUrl, setBaseUrl] = useState(DEFAULT_BASE_URL); const [isLoading, setIsLoading] = useState(true); const [client, setClient] = useState(null); const [unauthorized, setUnauthorized] = useState(false); const unauthorizedFiredRef = useRef(false); const handleUnauthorized = useCallback(() => { if (unauthorizedFiredRef.current) return; unauthorizedFiredRef.current = true; setUnauthorized(true); }, []); const clearUnauthorized = useCallback(() => { unauthorizedFiredRef.current = false; setUnauthorized(false); }, []); useEffect(() => { (async () => { try { const storedKey = await SecureStore.getItemAsync(API_KEY_STORAGE); const storedUrl = await SecureStore.getItemAsync(BASE_URL_STORAGE); if (storedKey) { const url = (storedUrl || DEFAULT_BASE_URL).replace(/\/$/, ""); setApiKey(storedKey); setBaseUrl(url); setClient(() => createClient(storedKey, url, handleUnauthorized)); } } catch { } finally { setIsLoading(false); } })(); }, [handleUnauthorized]); const saveSettings = useCallback( async (newApiKey: string, newBaseUrl: string) => { await SecureStore.setItemAsync(API_KEY_STORAGE, newApiKey); await SecureStore.setItemAsync(BASE_URL_STORAGE, newBaseUrl); setApiKey(newApiKey); setBaseUrl(newBaseUrl); clearUnauthorized(); setClient(() => createClient(newApiKey, newBaseUrl, handleUnauthorized)); }, [handleUnauthorized, clearUnauthorized] ); const clearSettings = useCallback(async () => { await SecureStore.deleteItemAsync(API_KEY_STORAGE); await SecureStore.deleteItemAsync(BASE_URL_STORAGE); setApiKey(""); setBaseUrl(DEFAULT_BASE_URL); setClient(null); clearUnauthorized(); }, [clearUnauthorized]); return ( {children} ); } export function usePostiz() { return useContext(PostizContext); }