react-os-shell 0.1.52 → 0.1.53
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/{Calendar-L5FHOXDG.js → Calendar-QSKXJSXT.js} +3 -3
- package/dist/{Calendar-L5FHOXDG.js.map → Calendar-QSKXJSXT.js.map} +1 -1
- package/dist/{Email-YPLU6HJL.js → Email-JAXZT7GQ.js} +3 -3
- package/dist/{Email-YPLU6HJL.js.map → Email-JAXZT7GQ.js.map} +1 -1
- package/dist/{GeminiChat-BXLBJFT4.js → GeminiChat-ITU46EH4.js} +3 -3
- package/dist/{GeminiChat-BXLBJFT4.js.map → GeminiChat-ITU46EH4.js.map} +1 -1
- package/dist/apps/index.js +3 -3
- package/dist/{chunk-5O2KEISQ.js → chunk-46LICZUM.js} +60 -7
- package/dist/chunk-46LICZUM.js.map +1 -0
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-5O2KEISQ.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useGoogleAuth, getGoogleAccessToken } from './chunk-
|
|
1
|
+
import { useGoogleAuth, getGoogleAccessToken } from './chunk-46LICZUM.js';
|
|
2
2
|
import { toast_default } from './chunk-WIJ45SYD.js';
|
|
3
3
|
import { useState, useRef, useEffect } from 'react';
|
|
4
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
@@ -180,5 +180,5 @@ function GeminiChat() {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
export { GeminiChat as default };
|
|
183
|
-
//# sourceMappingURL=GeminiChat-
|
|
184
|
-
//# sourceMappingURL=GeminiChat-
|
|
183
|
+
//# sourceMappingURL=GeminiChat-ITU46EH4.js.map
|
|
184
|
+
//# sourceMappingURL=GeminiChat-ITU46EH4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/apps/GeminiChat.tsx"],"names":[],"mappings":"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","file":"GeminiChat-BXLBJFT4.js","sourcesContent":["import { useState, useRef, useEffect } from 'react';\nimport useGoogleAuth, { getGoogleAccessToken } from '../hooks/useGoogleAuth';\nimport toast from '../shell/toast';\n\ninterface ChatMessage {\n role: 'user' | 'model';\n content: string;\n timestamp: Date;\n}\n\nconst GEMINI_API = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent';\n\nexport default function GeminiChat() {\n const { isConnected, user, connect, disconnect, hasClientId, setClientId, loading: authLoading, error: authError } = useGoogleAuth();\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [input, setInput] = useState('');\n const [loading, setLoading] = useState(false);\n const [clientIdInput, setClientIdInput] = useState('');\n const scrollRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' });\n }, [messages]);\n\n const sendMessage = async () => {\n const text = input.trim();\n if (!text || loading) return;\n\n const token = getGoogleAccessToken();\n if (!token) { toast.error('Google session expired. Please reconnect.'); return; }\n\n const userMsg: ChatMessage = { role: 'user', content: text, timestamp: new Date() };\n setMessages(prev => [...prev, userMsg]);\n setInput('');\n setLoading(true);\n\n try {\n // Build conversation history for context\n const contents = [...messages, userMsg].map(m => ({\n role: m.role === 'model' ? 'model' : 'user',\n parts: [{ text: m.content }],\n }));\n\n const res = await fetch(`${GEMINI_API}`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ contents }),\n });\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}));\n throw new Error(err.error?.message || `API error ${res.status}`);\n }\n\n const data = await res.json();\n const reply = data.candidates?.[0]?.content?.parts?.[0]?.text || 'No response.';\n setMessages(prev => [...prev, { role: 'model', content: reply, timestamp: new Date() }]);\n } catch (err: any) {\n toast.error(err.message || 'Failed to get response from Gemini.');\n setMessages(prev => [...prev, { role: 'model', content: `Error: ${err.message}`, timestamp: new Date() }]);\n }\n setLoading(false);\n setTimeout(() => inputRef.current?.focus(), 50);\n };\n\n const clearChat = () => {\n setMessages([]);\n };\n\n // ── Not connected ──\n if (!isConnected) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-md space-y-4 px-6\">\n <div className=\"h-16 w-16 mx-auto rounded-2xl bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 flex items-center justify-center\">\n <svg className=\"h-8 w-8 text-white\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.455 2.456L21.75 6l-1.036.259a3.375 3.375 0 00-2.455 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z\" /></svg>\n </div>\n <h2 className=\"text-lg font-semibold text-gray-900\">Gemini AI</h2>\n <p className=\"text-sm text-gray-500\">Connect your Google account to chat with Gemini AI.</p>\n\n {!hasClientId ? (\n <div className=\"text-left space-y-2 bg-gray-50 rounded-lg p-4\">\n <label className=\"block text-xs font-medium text-gray-700\">Google OAuth Client ID</label>\n <input value={clientIdInput} onChange={e => setClientIdInput(e.target.value)} placeholder=\"123456789.apps.googleusercontent.com\"\n className=\"w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-blue-500\" />\n <button onClick={() => { if (clientIdInput.trim()) setClientId(clientIdInput.trim()); }} disabled={!clientIdInput.trim()}\n className=\"w-full bg-gray-900 text-white px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-800 disabled:opacity-40\">Save Client ID</button>\n <p className=\"text-[10px] text-gray-400\">Enable \"Generative Language API\" in your Google Cloud project</p>\n </div>\n ) : (\n <button onClick={connect} disabled={authLoading}\n className=\"inline-flex items-center gap-2 bg-white border border-gray-300 shadow-sm px-6 py-2.5 text-sm font-medium rounded-lg hover:bg-gray-50 disabled:opacity-50\">\n <svg className=\"h-5 w-5\" viewBox=\"0 0 24 24\"><path d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z\" fill=\"#4285F4\"/><path d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\" fill=\"#34A853\"/><path d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\" fill=\"#FBBC05\"/><path d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\" fill=\"#EA4335\"/></svg>\n {authLoading ? 'Connecting...' : 'Sign in with Google'}\n </button>\n )}\n {authError && <p className=\"text-sm text-red-600\">{authError}</p>}\n </div>\n </div>\n );\n }\n\n // ── Connected: Chat UI ──\n return (\n <div className=\"flex flex-col h-full\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-2 border-b border-gray-200 shrink-0\">\n <div className=\"flex items-center gap-2\">\n <div className=\"h-6 w-6 rounded-lg bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 flex items-center justify-center\">\n <svg className=\"h-3.5 w-3.5 text-white\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z\" /></svg>\n </div>\n <span className=\"text-sm font-semibold text-gray-900\">Gemini</span>\n <span className=\"text-xs text-gray-400\">2.5 Flash</span>\n </div>\n <div className=\"flex items-center gap-2\">\n <button onClick={clearChat} className=\"text-xs text-gray-500 hover:text-gray-700\">Clear</button>\n <button onClick={() => window.dispatchEvent(new Event('open-google-connect'))} title=\"Google Services\"\n className=\"flex items-center gap-2 hover:bg-gray-100 rounded-md px-1.5 py-1 transition-colors\">\n {user?.picture ? (\n <img src={user.picture} alt=\"\" className=\"h-6 w-6 rounded-full\" />\n ) : (\n <div className=\"h-6 w-6 rounded-full bg-gray-200\" />\n )}\n <div className=\"text-left\">\n <p className=\"text-[11px] font-medium text-gray-900\">{user?.name}</p>\n <p className=\"text-[10px] text-gray-500\">{user?.email}</p>\n </div>\n </button>\n </div>\n </div>\n\n {/* Messages */}\n <div ref={scrollRef} className=\"flex-1 overflow-y-auto px-4 py-4 space-y-4\">\n {messages.length === 0 && (\n <div className=\"flex items-center justify-center h-full text-center\">\n <div>\n <div className=\"h-12 w-12 mx-auto mb-3 rounded-xl bg-gradient-to-br from-blue-500/10 via-purple-500/10 to-pink-500/10 flex items-center justify-center\">\n <svg className=\"h-6 w-6 text-purple-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z\" /></svg>\n </div>\n <p className=\"text-sm text-gray-500\">Ask Gemini anything</p>\n <p className=\"text-xs text-gray-400 mt-1\">Powered by Google AI</p>\n </div>\n </div>\n )}\n {messages.map((msg, i) => (\n <div key={i} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>\n <div className={`max-w-[80%] rounded-2xl px-4 py-2.5 ${msg.role === 'user' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-900'}`}>\n <div className=\"text-sm whitespace-pre-wrap leading-relaxed\">{msg.content}</div>\n <p className={`text-[10px] mt-1 ${msg.role === 'user' ? 'text-blue-200' : 'text-gray-400'}`}>\n {msg.timestamp.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })}\n </p>\n </div>\n </div>\n ))}\n {loading && (\n <div className=\"flex justify-start\">\n <div className=\"bg-gray-100 rounded-2xl px-4 py-3\">\n <div className=\"flex items-center gap-1.5\">\n <div className=\"w-2 h-2 rounded-full bg-gray-400 animate-bounce\" style={{ animationDelay: '0ms' }} />\n <div className=\"w-2 h-2 rounded-full bg-gray-400 animate-bounce\" style={{ animationDelay: '150ms' }} />\n <div className=\"w-2 h-2 rounded-full bg-gray-400 animate-bounce\" style={{ animationDelay: '300ms' }} />\n </div>\n </div>\n </div>\n )}\n </div>\n\n {/* Input */}\n <div className=\"px-4 py-3 border-t border-gray-200 shrink-0\">\n <div className=\"flex items-end gap-2\">\n <textarea\n ref={inputRef}\n value={input}\n onChange={e => setInput(e.target.value)}\n onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }}\n placeholder=\"Message Gemini...\"\n rows={1}\n className=\"flex-1 rounded-xl border border-gray-300 px-4 py-2.5 text-sm resize-none focus:border-blue-500 focus:ring-blue-500 max-h-32\"\n style={{ minHeight: '42px' }}\n />\n <button onClick={sendMessage} disabled={loading || !input.trim()}\n className=\"shrink-0 bg-blue-600 text-white rounded-xl p-2.5 hover:bg-blue-700 disabled:opacity-40 transition-colors\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5\" /></svg>\n </button>\n </div>\n </div>\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/apps/GeminiChat.tsx"],"names":[],"mappings":"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","file":"GeminiChat-ITU46EH4.js","sourcesContent":["import { useState, useRef, useEffect } from 'react';\nimport useGoogleAuth, { getGoogleAccessToken } from '../hooks/useGoogleAuth';\nimport toast from '../shell/toast';\n\ninterface ChatMessage {\n role: 'user' | 'model';\n content: string;\n timestamp: Date;\n}\n\nconst GEMINI_API = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent';\n\nexport default function GeminiChat() {\n const { isConnected, user, connect, disconnect, hasClientId, setClientId, loading: authLoading, error: authError } = useGoogleAuth();\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [input, setInput] = useState('');\n const [loading, setLoading] = useState(false);\n const [clientIdInput, setClientIdInput] = useState('');\n const scrollRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' });\n }, [messages]);\n\n const sendMessage = async () => {\n const text = input.trim();\n if (!text || loading) return;\n\n const token = getGoogleAccessToken();\n if (!token) { toast.error('Google session expired. Please reconnect.'); return; }\n\n const userMsg: ChatMessage = { role: 'user', content: text, timestamp: new Date() };\n setMessages(prev => [...prev, userMsg]);\n setInput('');\n setLoading(true);\n\n try {\n // Build conversation history for context\n const contents = [...messages, userMsg].map(m => ({\n role: m.role === 'model' ? 'model' : 'user',\n parts: [{ text: m.content }],\n }));\n\n const res = await fetch(`${GEMINI_API}`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ contents }),\n });\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}));\n throw new Error(err.error?.message || `API error ${res.status}`);\n }\n\n const data = await res.json();\n const reply = data.candidates?.[0]?.content?.parts?.[0]?.text || 'No response.';\n setMessages(prev => [...prev, { role: 'model', content: reply, timestamp: new Date() }]);\n } catch (err: any) {\n toast.error(err.message || 'Failed to get response from Gemini.');\n setMessages(prev => [...prev, { role: 'model', content: `Error: ${err.message}`, timestamp: new Date() }]);\n }\n setLoading(false);\n setTimeout(() => inputRef.current?.focus(), 50);\n };\n\n const clearChat = () => {\n setMessages([]);\n };\n\n // ── Not connected ──\n if (!isConnected) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-md space-y-4 px-6\">\n <div className=\"h-16 w-16 mx-auto rounded-2xl bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 flex items-center justify-center\">\n <svg className=\"h-8 w-8 text-white\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.455 2.456L21.75 6l-1.036.259a3.375 3.375 0 00-2.455 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z\" /></svg>\n </div>\n <h2 className=\"text-lg font-semibold text-gray-900\">Gemini AI</h2>\n <p className=\"text-sm text-gray-500\">Connect your Google account to chat with Gemini AI.</p>\n\n {!hasClientId ? (\n <div className=\"text-left space-y-2 bg-gray-50 rounded-lg p-4\">\n <label className=\"block text-xs font-medium text-gray-700\">Google OAuth Client ID</label>\n <input value={clientIdInput} onChange={e => setClientIdInput(e.target.value)} placeholder=\"123456789.apps.googleusercontent.com\"\n className=\"w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-blue-500\" />\n <button onClick={() => { if (clientIdInput.trim()) setClientId(clientIdInput.trim()); }} disabled={!clientIdInput.trim()}\n className=\"w-full bg-gray-900 text-white px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-800 disabled:opacity-40\">Save Client ID</button>\n <p className=\"text-[10px] text-gray-400\">Enable \"Generative Language API\" in your Google Cloud project</p>\n </div>\n ) : (\n <button onClick={connect} disabled={authLoading}\n className=\"inline-flex items-center gap-2 bg-white border border-gray-300 shadow-sm px-6 py-2.5 text-sm font-medium rounded-lg hover:bg-gray-50 disabled:opacity-50\">\n <svg className=\"h-5 w-5\" viewBox=\"0 0 24 24\"><path d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z\" fill=\"#4285F4\"/><path d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\" fill=\"#34A853\"/><path d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\" fill=\"#FBBC05\"/><path d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\" fill=\"#EA4335\"/></svg>\n {authLoading ? 'Connecting...' : 'Sign in with Google'}\n </button>\n )}\n {authError && <p className=\"text-sm text-red-600\">{authError}</p>}\n </div>\n </div>\n );\n }\n\n // ── Connected: Chat UI ──\n return (\n <div className=\"flex flex-col h-full\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-2 border-b border-gray-200 shrink-0\">\n <div className=\"flex items-center gap-2\">\n <div className=\"h-6 w-6 rounded-lg bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 flex items-center justify-center\">\n <svg className=\"h-3.5 w-3.5 text-white\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z\" /></svg>\n </div>\n <span className=\"text-sm font-semibold text-gray-900\">Gemini</span>\n <span className=\"text-xs text-gray-400\">2.5 Flash</span>\n </div>\n <div className=\"flex items-center gap-2\">\n <button onClick={clearChat} className=\"text-xs text-gray-500 hover:text-gray-700\">Clear</button>\n <button onClick={() => window.dispatchEvent(new Event('open-google-connect'))} title=\"Google Services\"\n className=\"flex items-center gap-2 hover:bg-gray-100 rounded-md px-1.5 py-1 transition-colors\">\n {user?.picture ? (\n <img src={user.picture} alt=\"\" className=\"h-6 w-6 rounded-full\" />\n ) : (\n <div className=\"h-6 w-6 rounded-full bg-gray-200\" />\n )}\n <div className=\"text-left\">\n <p className=\"text-[11px] font-medium text-gray-900\">{user?.name}</p>\n <p className=\"text-[10px] text-gray-500\">{user?.email}</p>\n </div>\n </button>\n </div>\n </div>\n\n {/* Messages */}\n <div ref={scrollRef} className=\"flex-1 overflow-y-auto px-4 py-4 space-y-4\">\n {messages.length === 0 && (\n <div className=\"flex items-center justify-center h-full text-center\">\n <div>\n <div className=\"h-12 w-12 mx-auto mb-3 rounded-xl bg-gradient-to-br from-blue-500/10 via-purple-500/10 to-pink-500/10 flex items-center justify-center\">\n <svg className=\"h-6 w-6 text-purple-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z\" /></svg>\n </div>\n <p className=\"text-sm text-gray-500\">Ask Gemini anything</p>\n <p className=\"text-xs text-gray-400 mt-1\">Powered by Google AI</p>\n </div>\n </div>\n )}\n {messages.map((msg, i) => (\n <div key={i} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>\n <div className={`max-w-[80%] rounded-2xl px-4 py-2.5 ${msg.role === 'user' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-900'}`}>\n <div className=\"text-sm whitespace-pre-wrap leading-relaxed\">{msg.content}</div>\n <p className={`text-[10px] mt-1 ${msg.role === 'user' ? 'text-blue-200' : 'text-gray-400'}`}>\n {msg.timestamp.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })}\n </p>\n </div>\n </div>\n ))}\n {loading && (\n <div className=\"flex justify-start\">\n <div className=\"bg-gray-100 rounded-2xl px-4 py-3\">\n <div className=\"flex items-center gap-1.5\">\n <div className=\"w-2 h-2 rounded-full bg-gray-400 animate-bounce\" style={{ animationDelay: '0ms' }} />\n <div className=\"w-2 h-2 rounded-full bg-gray-400 animate-bounce\" style={{ animationDelay: '150ms' }} />\n <div className=\"w-2 h-2 rounded-full bg-gray-400 animate-bounce\" style={{ animationDelay: '300ms' }} />\n </div>\n </div>\n </div>\n )}\n </div>\n\n {/* Input */}\n <div className=\"px-4 py-3 border-t border-gray-200 shrink-0\">\n <div className=\"flex items-end gap-2\">\n <textarea\n ref={inputRef}\n value={input}\n onChange={e => setInput(e.target.value)}\n onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }}\n placeholder=\"Message Gemini...\"\n rows={1}\n className=\"flex-1 rounded-xl border border-gray-300 px-4 py-2.5 text-sm resize-none focus:border-blue-500 focus:ring-blue-500 max-h-32\"\n style={{ minHeight: '42px' }}\n />\n <button onClick={sendMessage} disabled={loading || !input.trim()}\n className=\"shrink-0 bg-blue-600 text-white rounded-xl p-2.5 hover:bg-blue-700 disabled:opacity-40 transition-colors\">\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5\" /></svg>\n </button>\n </div>\n </div>\n </div>\n );\n}\n"]}
|
package/dist/apps/index.js
CHANGED
|
@@ -17,9 +17,9 @@ var Sudoku = lazy(() => import('../Sudoku-XHLYCEVT.js'));
|
|
|
17
17
|
var Tetris = lazy(() => import('../Tetris-ZHCZYL24.js'));
|
|
18
18
|
var Game2048 = lazy(() => import('../Game2048-3RH3ELRD.js'));
|
|
19
19
|
var Minesweeper = lazy(() => import('../Minesweeper-UUHQCDAC.js'));
|
|
20
|
-
var Email = lazy(() => import('../Email-
|
|
21
|
-
var GeminiChat = lazy(() => import('../GeminiChat-
|
|
22
|
-
var Calendar = lazy(() => import('../Calendar-
|
|
20
|
+
var Email = lazy(() => import('../Email-JAXZT7GQ.js'));
|
|
21
|
+
var GeminiChat = lazy(() => import('../GeminiChat-ITU46EH4.js'));
|
|
22
|
+
var Calendar = lazy(() => import('../Calendar-QSKXJSXT.js'));
|
|
23
23
|
var Preview = lazy(() => import('../Preview-IEPHQIJE.js'));
|
|
24
24
|
var Documents = lazy(() => import('../Documents-3UPQ5QTF.js'));
|
|
25
25
|
var Files = lazy(() => import('../Files-N3QQLSXV.js'));
|
|
@@ -58,6 +58,8 @@ function useGoogleAuth() {
|
|
|
58
58
|
const [loading, setLoading] = useState(false);
|
|
59
59
|
const [error, setError] = useState(null);
|
|
60
60
|
const clientRef = useRef(null);
|
|
61
|
+
const silentInFlightRef = useRef(false);
|
|
62
|
+
const refreshTimerRef = useRef(null);
|
|
61
63
|
const clientId = getGoogleClientId();
|
|
62
64
|
const hasClientId = !!clientId;
|
|
63
65
|
const setClientId = useCallback((id) => {
|
|
@@ -78,9 +80,39 @@ function useGoogleAuth() {
|
|
|
78
80
|
} catch {
|
|
79
81
|
}
|
|
80
82
|
}, []);
|
|
83
|
+
const cancelRefreshTimer = useCallback(() => {
|
|
84
|
+
if (refreshTimerRef.current !== null) {
|
|
85
|
+
window.clearTimeout(refreshTimerRef.current);
|
|
86
|
+
refreshTimerRef.current = null;
|
|
87
|
+
}
|
|
88
|
+
}, []);
|
|
89
|
+
const scheduleSilentRefresh = useCallback(() => {
|
|
90
|
+
cancelRefreshTimer();
|
|
91
|
+
const expiry = parseInt(localStorage.getItem(TOKEN_EXPIRY_KEY) || "0", 10);
|
|
92
|
+
if (!expiry) return;
|
|
93
|
+
const delay = Math.max(0, expiry - Date.now() - 6e4);
|
|
94
|
+
refreshTimerRef.current = window.setTimeout(() => {
|
|
95
|
+
refreshTimerRef.current = null;
|
|
96
|
+
if (!clientRef.current) return;
|
|
97
|
+
silentInFlightRef.current = true;
|
|
98
|
+
try {
|
|
99
|
+
clientRef.current.requestAccessToken({ prompt: "" });
|
|
100
|
+
} catch {
|
|
101
|
+
silentInFlightRef.current = false;
|
|
102
|
+
}
|
|
103
|
+
}, delay);
|
|
104
|
+
}, [cancelRefreshTimer]);
|
|
81
105
|
const handleTokenResponse = useCallback((response) => {
|
|
106
|
+
const wasSilent = silentInFlightRef.current;
|
|
107
|
+
silentInFlightRef.current = false;
|
|
82
108
|
if (response.error) {
|
|
83
|
-
|
|
109
|
+
if (wasSilent) {
|
|
110
|
+
localStorage.removeItem(TOKEN_KEY);
|
|
111
|
+
localStorage.removeItem(TOKEN_EXPIRY_KEY);
|
|
112
|
+
setAccessToken(null);
|
|
113
|
+
} else {
|
|
114
|
+
setError(response.error);
|
|
115
|
+
}
|
|
84
116
|
setLoading(false);
|
|
85
117
|
return;
|
|
86
118
|
}
|
|
@@ -91,8 +123,9 @@ function useGoogleAuth() {
|
|
|
91
123
|
setAccessToken(token);
|
|
92
124
|
setError(null);
|
|
93
125
|
setLoading(false);
|
|
94
|
-
fetchUserInfo(token);
|
|
95
|
-
|
|
126
|
+
if (!wasSilent) fetchUserInfo(token);
|
|
127
|
+
scheduleSilentRefresh();
|
|
128
|
+
}, [fetchUserInfo, scheduleSilentRefresh]);
|
|
96
129
|
useEffect(() => {
|
|
97
130
|
if (!clientId) return;
|
|
98
131
|
loadGisScript().then(() => {
|
|
@@ -103,8 +136,19 @@ function useGoogleAuth() {
|
|
|
103
136
|
scope: SCOPES,
|
|
104
137
|
callback: handleTokenResponse
|
|
105
138
|
});
|
|
139
|
+
if (isTokenValid()) {
|
|
140
|
+
scheduleSilentRefresh();
|
|
141
|
+
} else if (localStorage.getItem(TOKEN_KEY)) {
|
|
142
|
+
silentInFlightRef.current = true;
|
|
143
|
+
try {
|
|
144
|
+
clientRef.current.requestAccessToken({ prompt: "" });
|
|
145
|
+
} catch {
|
|
146
|
+
silentInFlightRef.current = false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
106
149
|
}).catch((err) => setError(err.message));
|
|
107
|
-
|
|
150
|
+
return () => cancelRefreshTimer();
|
|
151
|
+
}, [clientId, handleTokenResponse, scheduleSilentRefresh, cancelRefreshTimer]);
|
|
108
152
|
const connect = useCallback(() => {
|
|
109
153
|
if (!clientRef.current) {
|
|
110
154
|
setError("Google client not initialized. Check your Client ID.");
|
|
@@ -115,6 +159,7 @@ function useGoogleAuth() {
|
|
|
115
159
|
clientRef.current.requestAccessToken({ prompt: "consent" });
|
|
116
160
|
}, []);
|
|
117
161
|
const disconnect = useCallback(() => {
|
|
162
|
+
cancelRefreshTimer();
|
|
118
163
|
const token = localStorage.getItem(TOKEN_KEY);
|
|
119
164
|
if (token) {
|
|
120
165
|
const google = window.google;
|
|
@@ -127,11 +172,19 @@ function useGoogleAuth() {
|
|
|
127
172
|
localStorage.removeItem(USER_KEY);
|
|
128
173
|
setAccessToken(null);
|
|
129
174
|
setUser(null);
|
|
130
|
-
}, []);
|
|
175
|
+
}, [cancelRefreshTimer]);
|
|
131
176
|
useEffect(() => {
|
|
132
177
|
const interval = setInterval(() => {
|
|
133
178
|
if (accessToken && !isTokenValid()) {
|
|
134
179
|
setAccessToken(null);
|
|
180
|
+
if (clientRef.current && !silentInFlightRef.current) {
|
|
181
|
+
silentInFlightRef.current = true;
|
|
182
|
+
try {
|
|
183
|
+
clientRef.current.requestAccessToken({ prompt: "" });
|
|
184
|
+
} catch {
|
|
185
|
+
silentInFlightRef.current = false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
135
188
|
}
|
|
136
189
|
}, 3e4);
|
|
137
190
|
return () => clearInterval(interval);
|
|
@@ -151,5 +204,5 @@ function useGoogleAuth() {
|
|
|
151
204
|
}
|
|
152
205
|
|
|
153
206
|
export { getGoogleAccessToken, useGoogleAuth };
|
|
154
|
-
//# sourceMappingURL=chunk-
|
|
155
|
-
//# sourceMappingURL=chunk-
|
|
207
|
+
//# sourceMappingURL=chunk-46LICZUM.js.map
|
|
208
|
+
//# sourceMappingURL=chunk-46LICZUM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useGoogleAuth.ts"],"names":[],"mappings":"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","file":"chunk-46LICZUM.js","sourcesContent":["/**\n * Google OAuth2 hook for Gmail, Calendar, and Gemini access.\n *\n * Uses Google Identity Services (GIS) to get an access token with combined scopes.\n * Requires a Google Cloud OAuth2 Client ID configured in System Settings.\n *\n * Scopes requested:\n * - Gmail: read, compose, send, modify\n * - Calendar: read, write events\n * - Gemini: generative language (via Vertex AI or Google AI)\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\n\nconst SCOPES = [\n 'https://www.googleapis.com/auth/gmail.readonly',\n 'https://www.googleapis.com/auth/gmail.compose',\n 'https://www.googleapis.com/auth/gmail.send',\n 'https://www.googleapis.com/auth/gmail.modify',\n 'https://www.googleapis.com/auth/calendar.readonly',\n 'https://www.googleapis.com/auth/calendar.events',\n 'https://www.googleapis.com/auth/generative-language.retriever',\n].join(' ');\n\nconst TOKEN_KEY = 'google_access_token';\nconst TOKEN_EXPIRY_KEY = 'google_token_expiry';\nconst USER_KEY = 'google_user_info';\nconst CLIENT_ID_KEY = 'google_oauth_client_id';\n\ninterface GoogleUser {\n email: string;\n name: string;\n picture: string;\n}\n\ninterface GoogleAuthState {\n isConnected: boolean;\n user: GoogleUser | null;\n accessToken: string | null;\n loading: boolean;\n error: string | null;\n connect: () => void;\n disconnect: () => void;\n getClientId: () => string;\n setClientId: (id: string) => void;\n hasClientId: boolean;\n}\n\n// Load GIS script\nlet gisLoaded = false;\nlet gisLoadPromise: Promise<void> | null = null;\n\nfunction loadGisScript(): Promise<void> {\n if (gisLoaded) return Promise.resolve();\n if (gisLoadPromise) return gisLoadPromise;\n\n gisLoadPromise = new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = 'https://accounts.google.com/gsi/client';\n script.async = true;\n script.defer = true;\n script.onload = () => { gisLoaded = true; resolve(); };\n script.onerror = () => reject(new Error('Failed to load Google Identity Services'));\n document.head.appendChild(script);\n });\n return gisLoadPromise;\n}\n\nfunction isTokenValid(): boolean {\n const expiry = localStorage.getItem(TOKEN_EXPIRY_KEY);\n if (!expiry) return false;\n return Date.now() < parseInt(expiry, 10) - 60000; // 1 min buffer\n}\n\nexport function getGoogleAccessToken(): string | null {\n if (!isTokenValid()) return null;\n return localStorage.getItem(TOKEN_KEY);\n}\n\nexport function getGoogleClientId(): string {\n return localStorage.getItem(CLIENT_ID_KEY) || '';\n}\n\nexport default function useGoogleAuth(): GoogleAuthState {\n const [accessToken, setAccessToken] = useState<string | null>(() => isTokenValid() ? localStorage.getItem(TOKEN_KEY) : null);\n const [user, setUser] = useState<GoogleUser | null>(() => {\n try { const u = localStorage.getItem(USER_KEY); return u ? JSON.parse(u) : null; } catch { return null; }\n });\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const clientRef = useRef<any>(null);\n // Tracks an in-flight silent renewal so handleTokenResponse can suppress\n // its loading/error UI when the request didn't come from a user click.\n const silentInFlightRef = useRef(false);\n // setTimeout handle for the next scheduled silent renewal.\n const refreshTimerRef = useRef<number | null>(null);\n\n const clientId = getGoogleClientId();\n const hasClientId = !!clientId;\n\n const setClientId = useCallback((id: string) => {\n localStorage.setItem(CLIENT_ID_KEY, id);\n window.dispatchEvent(new Event('google-client-id-changed'));\n }, []);\n\n // Fetch user info from Google\n const fetchUserInfo = useCallback(async (token: string) => {\n try {\n const res = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (res.ok) {\n const data = await res.json();\n const userInfo: GoogleUser = { email: data.email, name: data.name, picture: data.picture };\n localStorage.setItem(USER_KEY, JSON.stringify(userInfo));\n setUser(userInfo);\n }\n } catch { /* ignore */ }\n }, []);\n\n const cancelRefreshTimer = useCallback(() => {\n if (refreshTimerRef.current !== null) {\n window.clearTimeout(refreshTimerRef.current);\n refreshTimerRef.current = null;\n }\n }, []);\n\n // Schedule a silent refresh to fire ~60s before the current token expires.\n // Google reissues a fresh token without showing UI when the user's Google\n // session is still active and they previously granted consent.\n const scheduleSilentRefresh = useCallback(() => {\n cancelRefreshTimer();\n const expiry = parseInt(localStorage.getItem(TOKEN_EXPIRY_KEY) || '0', 10);\n if (!expiry) return;\n const delay = Math.max(0, expiry - Date.now() - 60_000);\n refreshTimerRef.current = window.setTimeout(() => {\n refreshTimerRef.current = null;\n if (!clientRef.current) return;\n silentInFlightRef.current = true;\n try {\n clientRef.current.requestAccessToken({ prompt: '' });\n } catch {\n silentInFlightRef.current = false;\n }\n }, delay);\n }, [cancelRefreshTimer]);\n\n const handleTokenResponse = useCallback((response: any) => {\n const wasSilent = silentInFlightRef.current;\n silentInFlightRef.current = false;\n if (response.error) {\n if (wasSilent) {\n // Silent renewal failed (user signed out of Google, revoked access,\n // session expired, etc.). Drop the token quietly — the consumer\n // sees `isConnected = false` and the Connect button reappears.\n localStorage.removeItem(TOKEN_KEY);\n localStorage.removeItem(TOKEN_EXPIRY_KEY);\n setAccessToken(null);\n } else {\n setError(response.error);\n }\n setLoading(false);\n return;\n }\n const token = response.access_token;\n const expiresIn = response.expires_in || 3600;\n localStorage.setItem(TOKEN_KEY, token);\n localStorage.setItem(TOKEN_EXPIRY_KEY, String(Date.now() + expiresIn * 1000));\n setAccessToken(token);\n setError(null);\n setLoading(false);\n if (!wasSilent) fetchUserInfo(token);\n // Chain the next silent refresh.\n scheduleSilentRefresh();\n }, [fetchUserInfo, scheduleSilentRefresh]);\n\n // Initialize GIS client. Once ready, schedule a silent refresh if we\n // already hold a valid token (e.g. user just reopened the tab with time\n // left on the clock) — and if the token has actually expired, request a\n // fresh one silently so they don't have to click Connect again.\n useEffect(() => {\n if (!clientId) return;\n loadGisScript().then(() => {\n const google = (window as any).google;\n if (!google?.accounts?.oauth2) return;\n clientRef.current = google.accounts.oauth2.initTokenClient({\n client_id: clientId,\n scope: SCOPES,\n callback: handleTokenResponse,\n });\n if (isTokenValid()) {\n scheduleSilentRefresh();\n } else if (localStorage.getItem(TOKEN_KEY)) {\n // We had a token last session but it's now expired. Try silent\n // renewal — succeeds if the Google session is still alive.\n silentInFlightRef.current = true;\n try {\n clientRef.current.requestAccessToken({ prompt: '' });\n } catch {\n silentInFlightRef.current = false;\n }\n }\n }).catch(err => setError(err.message));\n return () => cancelRefreshTimer();\n }, [clientId, handleTokenResponse, scheduleSilentRefresh, cancelRefreshTimer]);\n\n const connect = useCallback(() => {\n if (!clientRef.current) {\n setError('Google client not initialized. Check your Client ID.');\n return;\n }\n setLoading(true);\n setError(null);\n clientRef.current.requestAccessToken({ prompt: 'consent' });\n }, []);\n\n const disconnect = useCallback(() => {\n cancelRefreshTimer();\n const token = localStorage.getItem(TOKEN_KEY);\n if (token) {\n const google = (window as any).google;\n if (google?.accounts?.oauth2) {\n google.accounts.oauth2.revoke(token);\n }\n }\n localStorage.removeItem(TOKEN_KEY);\n localStorage.removeItem(TOKEN_EXPIRY_KEY);\n localStorage.removeItem(USER_KEY);\n setAccessToken(null);\n setUser(null);\n }, [cancelRefreshTimer]);\n\n // Belt-and-suspenders expiry check. Most expiries are caught by the\n // scheduled refresh above; this fires every 30s if the timer ever\n // misses (e.g. setTimeout drift after long sleep).\n useEffect(() => {\n const interval = setInterval(() => {\n if (accessToken && !isTokenValid()) {\n setAccessToken(null);\n // Try one silent refresh before giving up.\n if (clientRef.current && !silentInFlightRef.current) {\n silentInFlightRef.current = true;\n try { clientRef.current.requestAccessToken({ prompt: '' }); }\n catch { silentInFlightRef.current = false; }\n }\n }\n }, 30000);\n return () => clearInterval(interval);\n }, [accessToken]);\n\n return {\n isConnected: !!accessToken && isTokenValid(),\n user,\n accessToken,\n loading,\n error,\n connect,\n disconnect,\n getClientId: () => clientId,\n setClientId,\n hasClientId,\n };\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEmailUnreadCount } from './chunk-PDFQNHW7.js';
|
|
2
2
|
import { formatDate } from './chunk-NSU7OHPC.js';
|
|
3
3
|
export { formatDate } from './chunk-NSU7OHPC.js';
|
|
4
|
-
import { useGoogleAuth } from './chunk-
|
|
4
|
+
import { useGoogleAuth } from './chunk-46LICZUM.js';
|
|
5
5
|
import { useShellPrefs } from './chunk-36VM54SC.js';
|
|
6
6
|
export { ShellPrefsProvider, useLocalStoragePrefs, useShellPrefs } from './chunk-36VM54SC.js';
|
|
7
7
|
import { playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, playLogout, setSoundForType, previewSound, setAllSounds } from './chunk-D7PYW2QS.js';
|
|
@@ -651,7 +651,7 @@ function StatusBadge({ status }) {
|
|
|
651
651
|
}
|
|
652
652
|
|
|
653
653
|
// src/version.ts
|
|
654
|
-
var VERSION = "0.1.
|
|
654
|
+
var VERSION = "0.1.53" ;
|
|
655
655
|
var APP_VERSION = VERSION;
|
|
656
656
|
|
|
657
657
|
// src/changelog.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-os-shell",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.53",
|
|
4
4
|
"description": "Desktop-style React UI shell — windows, taskbar, start menu, sticky notes, frosted glass theming, and 17 bundled apps including a PDF Preview viewer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Victor Y. Mau",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useGoogleAuth.ts"],"names":[],"mappings":"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","file":"chunk-5O2KEISQ.js","sourcesContent":["/**\n * Google OAuth2 hook for Gmail, Calendar, and Gemini access.\n *\n * Uses Google Identity Services (GIS) to get an access token with combined scopes.\n * Requires a Google Cloud OAuth2 Client ID configured in System Settings.\n *\n * Scopes requested:\n * - Gmail: read, compose, send, modify\n * - Calendar: read, write events\n * - Gemini: generative language (via Vertex AI or Google AI)\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\n\nconst SCOPES = [\n 'https://www.googleapis.com/auth/gmail.readonly',\n 'https://www.googleapis.com/auth/gmail.compose',\n 'https://www.googleapis.com/auth/gmail.send',\n 'https://www.googleapis.com/auth/gmail.modify',\n 'https://www.googleapis.com/auth/calendar.readonly',\n 'https://www.googleapis.com/auth/calendar.events',\n 'https://www.googleapis.com/auth/generative-language.retriever',\n].join(' ');\n\nconst TOKEN_KEY = 'google_access_token';\nconst TOKEN_EXPIRY_KEY = 'google_token_expiry';\nconst USER_KEY = 'google_user_info';\nconst CLIENT_ID_KEY = 'google_oauth_client_id';\n\ninterface GoogleUser {\n email: string;\n name: string;\n picture: string;\n}\n\ninterface GoogleAuthState {\n isConnected: boolean;\n user: GoogleUser | null;\n accessToken: string | null;\n loading: boolean;\n error: string | null;\n connect: () => void;\n disconnect: () => void;\n getClientId: () => string;\n setClientId: (id: string) => void;\n hasClientId: boolean;\n}\n\n// Load GIS script\nlet gisLoaded = false;\nlet gisLoadPromise: Promise<void> | null = null;\n\nfunction loadGisScript(): Promise<void> {\n if (gisLoaded) return Promise.resolve();\n if (gisLoadPromise) return gisLoadPromise;\n\n gisLoadPromise = new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = 'https://accounts.google.com/gsi/client';\n script.async = true;\n script.defer = true;\n script.onload = () => { gisLoaded = true; resolve(); };\n script.onerror = () => reject(new Error('Failed to load Google Identity Services'));\n document.head.appendChild(script);\n });\n return gisLoadPromise;\n}\n\nfunction isTokenValid(): boolean {\n const expiry = localStorage.getItem(TOKEN_EXPIRY_KEY);\n if (!expiry) return false;\n return Date.now() < parseInt(expiry, 10) - 60000; // 1 min buffer\n}\n\nexport function getGoogleAccessToken(): string | null {\n if (!isTokenValid()) return null;\n return localStorage.getItem(TOKEN_KEY);\n}\n\nexport function getGoogleClientId(): string {\n return localStorage.getItem(CLIENT_ID_KEY) || '';\n}\n\nexport default function useGoogleAuth(): GoogleAuthState {\n const [accessToken, setAccessToken] = useState<string | null>(() => isTokenValid() ? localStorage.getItem(TOKEN_KEY) : null);\n const [user, setUser] = useState<GoogleUser | null>(() => {\n try { const u = localStorage.getItem(USER_KEY); return u ? JSON.parse(u) : null; } catch { return null; }\n });\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const clientRef = useRef<any>(null);\n\n const clientId = getGoogleClientId();\n const hasClientId = !!clientId;\n\n const setClientId = useCallback((id: string) => {\n localStorage.setItem(CLIENT_ID_KEY, id);\n window.dispatchEvent(new Event('google-client-id-changed'));\n }, []);\n\n // Fetch user info from Google\n const fetchUserInfo = useCallback(async (token: string) => {\n try {\n const res = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (res.ok) {\n const data = await res.json();\n const userInfo: GoogleUser = { email: data.email, name: data.name, picture: data.picture };\n localStorage.setItem(USER_KEY, JSON.stringify(userInfo));\n setUser(userInfo);\n }\n } catch { /* ignore */ }\n }, []);\n\n const handleTokenResponse = useCallback((response: any) => {\n if (response.error) {\n setError(response.error);\n setLoading(false);\n return;\n }\n const token = response.access_token;\n const expiresIn = response.expires_in || 3600;\n localStorage.setItem(TOKEN_KEY, token);\n localStorage.setItem(TOKEN_EXPIRY_KEY, String(Date.now() + expiresIn * 1000));\n setAccessToken(token);\n setError(null);\n setLoading(false);\n fetchUserInfo(token);\n }, [fetchUserInfo]);\n\n // Initialize GIS client\n useEffect(() => {\n if (!clientId) return;\n loadGisScript().then(() => {\n const google = (window as any).google;\n if (!google?.accounts?.oauth2) return;\n clientRef.current = google.accounts.oauth2.initTokenClient({\n client_id: clientId,\n scope: SCOPES,\n callback: handleTokenResponse,\n });\n }).catch(err => setError(err.message));\n }, [clientId, handleTokenResponse]);\n\n const connect = useCallback(() => {\n if (!clientRef.current) {\n setError('Google client not initialized. Check your Client ID.');\n return;\n }\n setLoading(true);\n setError(null);\n clientRef.current.requestAccessToken({ prompt: 'consent' });\n }, []);\n\n const disconnect = useCallback(() => {\n const token = localStorage.getItem(TOKEN_KEY);\n if (token) {\n // Revoke token\n const google = (window as any).google;\n if (google?.accounts?.oauth2) {\n google.accounts.oauth2.revoke(token);\n }\n }\n localStorage.removeItem(TOKEN_KEY);\n localStorage.removeItem(TOKEN_EXPIRY_KEY);\n localStorage.removeItem(USER_KEY);\n setAccessToken(null);\n setUser(null);\n }, []);\n\n // Check token expiry periodically\n useEffect(() => {\n const interval = setInterval(() => {\n if (accessToken && !isTokenValid()) {\n setAccessToken(null);\n }\n }, 30000);\n return () => clearInterval(interval);\n }, [accessToken]);\n\n return {\n isConnected: !!accessToken && isTokenValid(),\n user,\n accessToken,\n loading,\n error,\n connect,\n disconnect,\n getClientId: () => clientId,\n setClientId,\n hasClientId,\n };\n}\n"]}
|