react-os-shell 0.2.68 → 0.3.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/README.md +35 -15
- package/dist/{Browser-UGZQMWKW.js → Browser-H5KDP5OH.js} +3 -3
- package/dist/{Browser-UGZQMWKW.js.map → Browser-H5KDP5OH.js.map} +1 -1
- package/dist/{Calculator-3ZXNXWDH.js → Calculator-QQ7NF53Q.js} +4 -4
- package/dist/{Calculator-3ZXNXWDH.js.map → Calculator-QQ7NF53Q.js.map} +1 -1
- package/dist/{Calendar-ON4AQ54T.js → Calendar-J6L7FGHS.js} +175 -108
- package/dist/Calendar-J6L7FGHS.js.map +1 -0
- package/dist/{CurrencyConverter-ACTLK72N.js → CurrencyConverter-YHOGBUPH.js} +4 -4
- package/dist/{CurrencyConverter-ACTLK72N.js.map → CurrencyConverter-YHOGBUPH.js.map} +1 -1
- package/dist/{Documents-STDXQ7I4.js → Documents-6DYALASM.js} +3 -3
- package/dist/{Documents-STDXQ7I4.js.map → Documents-6DYALASM.js.map} +1 -1
- package/dist/Email-U2U5Z4DL.js +475 -0
- package/dist/Email-U2U5Z4DL.js.map +1 -0
- package/dist/Files-T62M4V5I.js +11 -0
- package/dist/{Files-ASKLEUNU.js.map → Files-T62M4V5I.js.map} +1 -1
- package/dist/{Minesweeper-CZNIO75H.js → Minesweeper-S2JHXYLX.js} +3 -3
- package/dist/{Minesweeper-CZNIO75H.js.map → Minesweeper-S2JHXYLX.js.map} +1 -1
- package/dist/{Notepad-ZYYH4ZXN.js → Notepad-2YF7X3XO.js} +3 -3
- package/dist/{Notepad-ZYYH4ZXN.js.map → Notepad-2YF7X3XO.js.map} +1 -1
- package/dist/{PomodoroTimer-2MNIEAUM.js → PomodoroTimer-3J7Z3NVQ.js} +4 -4
- package/dist/{PomodoroTimer-2MNIEAUM.js.map → PomodoroTimer-3J7Z3NVQ.js.map} +1 -1
- package/dist/Preview-WM6ZP5PZ.js +8 -0
- package/dist/{Preview-U72UL74H.js.map → Preview-WM6ZP5PZ.js.map} +1 -1
- package/dist/Spreadsheet-ZIE2SXAF.js +6 -0
- package/dist/{Spreadsheet-T7Y7PRD6.js.map → Spreadsheet-ZIE2SXAF.js.map} +1 -1
- package/dist/{TodoList-7JZ2SLDI.js → TodoList-QGXCDEIE.js} +18 -204
- package/dist/TodoList-QGXCDEIE.js.map +1 -0
- package/dist/{Weather-DYCTCB6T.js → Weather-2GFPSZ5V.js} +4 -4
- package/dist/{Weather-DYCTCB6T.js.map → Weather-2GFPSZ5V.js.map} +1 -1
- package/dist/{WorldClock-XL4OMFOY.js → WorldClock-P4JR5I6X.js} +4 -4
- package/dist/{WorldClock-XL4OMFOY.js.map → WorldClock-P4JR5I6X.js.map} +1 -1
- package/dist/apps/index.d.ts +2 -5
- package/dist/apps/index.js +23 -26
- package/dist/apps/index.js.map +1 -1
- package/dist/chunk-57B3WALN.js +114 -0
- package/dist/chunk-57B3WALN.js.map +1 -0
- package/dist/{chunk-4SHZ7BZO.js → chunk-62FC2FHC.js} +92 -21
- package/dist/chunk-62FC2FHC.js.map +1 -0
- package/dist/{chunk-FXAOT23O.js → chunk-ATQVRDDQ.js} +3 -3
- package/dist/{chunk-FXAOT23O.js.map → chunk-ATQVRDDQ.js.map} +1 -1
- package/dist/{chunk-GP4Y3VCB.js → chunk-KMGWSDEI.js} +480 -4
- package/dist/chunk-KMGWSDEI.js.map +1 -0
- package/dist/{chunk-SU6XVJND.js → chunk-O6FJZAFM.js} +3 -3
- package/dist/{chunk-SU6XVJND.js.map → chunk-O6FJZAFM.js.map} +1 -1
- package/dist/{chunk-YL47AVBA.js → chunk-SEV7UXGN.js} +4 -4
- package/dist/{chunk-YL47AVBA.js.map → chunk-SEV7UXGN.js.map} +1 -1
- package/dist/{chunk-L2AFKNSQ.js → chunk-ZBRFMK3E.js} +4 -4
- package/dist/{chunk-L2AFKNSQ.js.map → chunk-ZBRFMK3E.js.map} +1 -1
- package/dist/index.d.ts +55 -1
- package/dist/index.js +241 -135
- package/dist/index.js.map +1 -1
- package/package.json +10 -3
- package/dist/Calendar-ON4AQ54T.js.map +0 -1
- package/dist/Email-5WL3TWC6.js +0 -1883
- package/dist/Email-5WL3TWC6.js.map +0 -1
- package/dist/Files-ASKLEUNU.js +0 -12
- package/dist/GeminiChat-XTEBZIVK.js +0 -184
- package/dist/GeminiChat-XTEBZIVK.js.map +0 -1
- package/dist/Preview-U72UL74H.js +0 -8
- package/dist/Spreadsheet-T7Y7PRD6.js +0 -7
- package/dist/TodoList-7JZ2SLDI.js.map +0 -1
- package/dist/chunk-4SHZ7BZO.js.map +0 -1
- package/dist/chunk-5VXRBUEH.js +0 -104
- package/dist/chunk-5VXRBUEH.js.map +0 -1
- package/dist/chunk-GP4Y3VCB.js.map +0 -1
- package/dist/chunk-MUXDKEOC.js +0 -485
- package/dist/chunk-MUXDKEOC.js.map +0 -1
- package/dist/chunk-MVWEL34Y.js +0 -209
- package/dist/chunk-MVWEL34Y.js.map +0 -1
package/dist/chunk-MVWEL34Y.js
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
-
|
|
3
|
-
// src/hooks/useGoogleAuth.ts
|
|
4
|
-
var SCOPES = [
|
|
5
|
-
"https://www.googleapis.com/auth/gmail.readonly",
|
|
6
|
-
"https://www.googleapis.com/auth/gmail.compose",
|
|
7
|
-
"https://www.googleapis.com/auth/gmail.send",
|
|
8
|
-
"https://www.googleapis.com/auth/gmail.modify",
|
|
9
|
-
"https://www.googleapis.com/auth/calendar.readonly",
|
|
10
|
-
"https://www.googleapis.com/auth/calendar.events",
|
|
11
|
-
"https://www.googleapis.com/auth/tasks",
|
|
12
|
-
"https://www.googleapis.com/auth/generative-language.retriever"
|
|
13
|
-
].join(" ");
|
|
14
|
-
var TOKEN_KEY = "google_access_token";
|
|
15
|
-
var TOKEN_EXPIRY_KEY = "google_token_expiry";
|
|
16
|
-
var USER_KEY = "google_user_info";
|
|
17
|
-
var CLIENT_ID_KEY = "google_oauth_client_id";
|
|
18
|
-
var gisLoaded = false;
|
|
19
|
-
var gisLoadPromise = null;
|
|
20
|
-
function loadGisScript() {
|
|
21
|
-
if (gisLoaded) return Promise.resolve();
|
|
22
|
-
if (gisLoadPromise) return gisLoadPromise;
|
|
23
|
-
gisLoadPromise = new Promise((resolve, reject) => {
|
|
24
|
-
const script = document.createElement("script");
|
|
25
|
-
script.src = "https://accounts.google.com/gsi/client";
|
|
26
|
-
script.async = true;
|
|
27
|
-
script.defer = true;
|
|
28
|
-
script.onload = () => {
|
|
29
|
-
gisLoaded = true;
|
|
30
|
-
resolve();
|
|
31
|
-
};
|
|
32
|
-
script.onerror = () => reject(new Error("Failed to load Google Identity Services"));
|
|
33
|
-
document.head.appendChild(script);
|
|
34
|
-
});
|
|
35
|
-
return gisLoadPromise;
|
|
36
|
-
}
|
|
37
|
-
function isTokenValid() {
|
|
38
|
-
const expiry = localStorage.getItem(TOKEN_EXPIRY_KEY);
|
|
39
|
-
if (!expiry) return false;
|
|
40
|
-
return Date.now() < parseInt(expiry, 10) - 6e4;
|
|
41
|
-
}
|
|
42
|
-
function getGoogleAccessToken() {
|
|
43
|
-
if (!isTokenValid()) return null;
|
|
44
|
-
return localStorage.getItem(TOKEN_KEY);
|
|
45
|
-
}
|
|
46
|
-
function getGoogleClientId() {
|
|
47
|
-
return localStorage.getItem(CLIENT_ID_KEY) || "";
|
|
48
|
-
}
|
|
49
|
-
function useGoogleAuth() {
|
|
50
|
-
const [accessToken, setAccessToken] = useState(() => isTokenValid() ? localStorage.getItem(TOKEN_KEY) : null);
|
|
51
|
-
const [user, setUser] = useState(() => {
|
|
52
|
-
try {
|
|
53
|
-
const u = localStorage.getItem(USER_KEY);
|
|
54
|
-
return u ? JSON.parse(u) : null;
|
|
55
|
-
} catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
const [loading, setLoading] = useState(false);
|
|
60
|
-
const [error, setError] = useState(null);
|
|
61
|
-
const clientRef = useRef(null);
|
|
62
|
-
const silentInFlightRef = useRef(false);
|
|
63
|
-
const refreshTimerRef = useRef(null);
|
|
64
|
-
const clientId = getGoogleClientId();
|
|
65
|
-
const hasClientId = !!clientId;
|
|
66
|
-
const setClientId = useCallback((id) => {
|
|
67
|
-
localStorage.setItem(CLIENT_ID_KEY, id);
|
|
68
|
-
window.dispatchEvent(new Event("google-client-id-changed"));
|
|
69
|
-
}, []);
|
|
70
|
-
const fetchUserInfo = useCallback(async (token) => {
|
|
71
|
-
try {
|
|
72
|
-
const res = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", {
|
|
73
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
74
|
-
});
|
|
75
|
-
if (res.ok) {
|
|
76
|
-
const data = await res.json();
|
|
77
|
-
const userInfo = { email: data.email, name: data.name, picture: data.picture };
|
|
78
|
-
localStorage.setItem(USER_KEY, JSON.stringify(userInfo));
|
|
79
|
-
setUser(userInfo);
|
|
80
|
-
}
|
|
81
|
-
} catch {
|
|
82
|
-
}
|
|
83
|
-
}, []);
|
|
84
|
-
const cancelRefreshTimer = useCallback(() => {
|
|
85
|
-
if (refreshTimerRef.current !== null) {
|
|
86
|
-
window.clearTimeout(refreshTimerRef.current);
|
|
87
|
-
refreshTimerRef.current = null;
|
|
88
|
-
}
|
|
89
|
-
}, []);
|
|
90
|
-
const scheduleSilentRefresh = useCallback(() => {
|
|
91
|
-
cancelRefreshTimer();
|
|
92
|
-
const expiry = parseInt(localStorage.getItem(TOKEN_EXPIRY_KEY) || "0", 10);
|
|
93
|
-
if (!expiry) return;
|
|
94
|
-
const delay = Math.max(0, expiry - Date.now() - 6e4);
|
|
95
|
-
refreshTimerRef.current = window.setTimeout(() => {
|
|
96
|
-
refreshTimerRef.current = null;
|
|
97
|
-
if (!clientRef.current) return;
|
|
98
|
-
silentInFlightRef.current = true;
|
|
99
|
-
try {
|
|
100
|
-
clientRef.current.requestAccessToken({ prompt: "" });
|
|
101
|
-
} catch {
|
|
102
|
-
silentInFlightRef.current = false;
|
|
103
|
-
}
|
|
104
|
-
}, delay);
|
|
105
|
-
}, [cancelRefreshTimer]);
|
|
106
|
-
const handleTokenResponse = useCallback((response) => {
|
|
107
|
-
const wasSilent = silentInFlightRef.current;
|
|
108
|
-
silentInFlightRef.current = false;
|
|
109
|
-
if (response.error) {
|
|
110
|
-
if (wasSilent) {
|
|
111
|
-
localStorage.removeItem(TOKEN_KEY);
|
|
112
|
-
localStorage.removeItem(TOKEN_EXPIRY_KEY);
|
|
113
|
-
setAccessToken(null);
|
|
114
|
-
} else {
|
|
115
|
-
setError(response.error);
|
|
116
|
-
}
|
|
117
|
-
setLoading(false);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
const token = response.access_token;
|
|
121
|
-
const expiresIn = response.expires_in || 3600;
|
|
122
|
-
localStorage.setItem(TOKEN_KEY, token);
|
|
123
|
-
localStorage.setItem(TOKEN_EXPIRY_KEY, String(Date.now() + expiresIn * 1e3));
|
|
124
|
-
setAccessToken(token);
|
|
125
|
-
setError(null);
|
|
126
|
-
setLoading(false);
|
|
127
|
-
if (!wasSilent) fetchUserInfo(token);
|
|
128
|
-
scheduleSilentRefresh();
|
|
129
|
-
}, [fetchUserInfo, scheduleSilentRefresh]);
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
if (!clientId) return;
|
|
132
|
-
loadGisScript().then(() => {
|
|
133
|
-
const google = window.google;
|
|
134
|
-
if (!google?.accounts?.oauth2) return;
|
|
135
|
-
clientRef.current = google.accounts.oauth2.initTokenClient({
|
|
136
|
-
client_id: clientId,
|
|
137
|
-
scope: SCOPES,
|
|
138
|
-
callback: handleTokenResponse
|
|
139
|
-
});
|
|
140
|
-
if (isTokenValid()) {
|
|
141
|
-
scheduleSilentRefresh();
|
|
142
|
-
} else if (localStorage.getItem(TOKEN_KEY)) {
|
|
143
|
-
silentInFlightRef.current = true;
|
|
144
|
-
try {
|
|
145
|
-
clientRef.current.requestAccessToken({ prompt: "" });
|
|
146
|
-
} catch {
|
|
147
|
-
silentInFlightRef.current = false;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}).catch((err) => setError(err.message));
|
|
151
|
-
return () => cancelRefreshTimer();
|
|
152
|
-
}, [clientId, handleTokenResponse, scheduleSilentRefresh, cancelRefreshTimer]);
|
|
153
|
-
const connect = useCallback(() => {
|
|
154
|
-
if (!clientRef.current) {
|
|
155
|
-
setError("Google client not initialized. Check your Client ID.");
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
setLoading(true);
|
|
159
|
-
setError(null);
|
|
160
|
-
clientRef.current.requestAccessToken({ prompt: "consent" });
|
|
161
|
-
}, []);
|
|
162
|
-
const disconnect = useCallback(() => {
|
|
163
|
-
cancelRefreshTimer();
|
|
164
|
-
const token = localStorage.getItem(TOKEN_KEY);
|
|
165
|
-
if (token) {
|
|
166
|
-
const google = window.google;
|
|
167
|
-
if (google?.accounts?.oauth2) {
|
|
168
|
-
google.accounts.oauth2.revoke(token);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
localStorage.removeItem(TOKEN_KEY);
|
|
172
|
-
localStorage.removeItem(TOKEN_EXPIRY_KEY);
|
|
173
|
-
localStorage.removeItem(USER_KEY);
|
|
174
|
-
setAccessToken(null);
|
|
175
|
-
setUser(null);
|
|
176
|
-
}, [cancelRefreshTimer]);
|
|
177
|
-
useEffect(() => {
|
|
178
|
-
const interval = setInterval(() => {
|
|
179
|
-
if (accessToken && !isTokenValid()) {
|
|
180
|
-
setAccessToken(null);
|
|
181
|
-
if (clientRef.current && !silentInFlightRef.current) {
|
|
182
|
-
silentInFlightRef.current = true;
|
|
183
|
-
try {
|
|
184
|
-
clientRef.current.requestAccessToken({ prompt: "" });
|
|
185
|
-
} catch {
|
|
186
|
-
silentInFlightRef.current = false;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}, 3e4);
|
|
191
|
-
return () => clearInterval(interval);
|
|
192
|
-
}, [accessToken]);
|
|
193
|
-
return {
|
|
194
|
-
isConnected: !!accessToken && isTokenValid(),
|
|
195
|
-
user,
|
|
196
|
-
accessToken,
|
|
197
|
-
loading,
|
|
198
|
-
error,
|
|
199
|
-
connect,
|
|
200
|
-
disconnect,
|
|
201
|
-
getClientId: () => clientId,
|
|
202
|
-
setClientId,
|
|
203
|
-
hasClientId
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export { getGoogleAccessToken, useGoogleAuth };
|
|
208
|
-
//# sourceMappingURL=chunk-MVWEL34Y.js.map
|
|
209
|
-
//# sourceMappingURL=chunk-MVWEL34Y.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useGoogleAuth.ts"],"names":[],"mappings":";;;AAeA,IAAM,MAAA,GAAS;AAAA,EACb,gDAAA;AAAA,EACA,+CAAA;AAAA,EACA,4CAAA;AAAA,EACA,8CAAA;AAAA,EACA,mDAAA;AAAA,EACA,iDAAA;AAAA,EACA,uCAAA;AAAA,EACA;AACF,CAAA,CAAE,KAAK,GAAG,CAAA;AAEV,IAAM,SAAA,GAAY,qBAAA;AAClB,IAAM,gBAAA,GAAmB,qBAAA;AACzB,IAAM,QAAA,GAAW,kBAAA;AACjB,IAAM,aAAA,GAAgB,wBAAA;AAsBtB,IAAI,SAAA,GAAY,KAAA;AAChB,IAAI,cAAA,GAAuC,IAAA;AAE3C,SAAS,aAAA,GAA+B;AACtC,EAAA,IAAI,SAAA,EAAW,OAAO,OAAA,CAAQ,OAAA,EAAQ;AACtC,EAAA,IAAI,gBAAgB,OAAO,cAAA;AAE3B,EAAA,cAAA,GAAiB,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AAChD,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AAC9C,IAAA,MAAA,CAAO,GAAA,GAAM,wCAAA;AACb,IAAA,MAAA,CAAO,KAAA,GAAQ,IAAA;AACf,IAAA,MAAA,CAAO,KAAA,GAAQ,IAAA;AACf,IAAA,MAAA,CAAO,SAAS,MAAM;AAAE,MAAA,SAAA,GAAY,IAAA;AAAM,MAAA,OAAA,EAAQ;AAAA,IAAG,CAAA;AACrD,IAAA,MAAA,CAAO,UAAU,MAAM,MAAA,CAAO,IAAI,KAAA,CAAM,yCAAyC,CAAC,CAAA;AAClF,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,MAAM,CAAA;AAAA,EAClC,CAAC,CAAA;AACD,EAAA,OAAO,cAAA;AACT;AAEA,SAAS,YAAA,GAAwB;AAC/B,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,CAAQ,gBAAgB,CAAA;AACpD,EAAA,IAAI,CAAC,QAAQ,OAAO,KAAA;AACpB,EAAA,OAAO,KAAK,GAAA,EAAI,GAAI,QAAA,CAAS,MAAA,EAAQ,EAAE,CAAA,GAAI,GAAA;AAC7C;AAEO,SAAS,oBAAA,GAAsC;AACpD,EAAA,IAAI,CAAC,YAAA,EAAa,EAAG,OAAO,IAAA;AAC5B,EAAA,OAAO,YAAA,CAAa,QAAQ,SAAS,CAAA;AACvC;AAEO,SAAS,iBAAA,GAA4B;AAC1C,EAAA,OAAO,YAAA,CAAa,OAAA,CAAQ,aAAa,CAAA,IAAK,EAAA;AAChD;AAEe,SAAR,aAAA,GAAkD;AACvD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,QAAA,CAAwB,MAAM,YAAA,EAAa,GAAI,YAAA,CAAa,OAAA,CAAQ,SAAS,CAAA,GAAI,IAAI,CAAA;AAC3H,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAA4B,MAAM;AACxD,IAAA,IAAI;AAAE,MAAA,MAAM,CAAA,GAAI,YAAA,CAAa,OAAA,CAAQ,QAAQ,CAAA;AAAG,MAAA,OAAO,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA,GAAI,IAAA;AAAA,IAAM,CAAA,CAAA,MAAQ;AAAE,MAAA,OAAO,IAAA;AAAA,IAAM;AAAA,EAC1G,CAAC,CAAA;AACD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AACtD,EAAA,MAAM,SAAA,GAAY,OAAY,IAAI,CAAA;AAGlC,EAAA,MAAM,iBAAA,GAAoB,OAAO,KAAK,CAAA;AAEtC,EAAA,MAAM,eAAA,GAAkB,OAAsB,IAAI,CAAA;AAElD,EAAA,MAAM,WAAW,iBAAA,EAAkB;AACnC,EAAA,MAAM,WAAA,GAAc,CAAC,CAAC,QAAA;AAEtB,EAAA,MAAM,WAAA,GAAc,WAAA,CAAY,CAAC,EAAA,KAAe;AAC9C,IAAA,YAAA,CAAa,OAAA,CAAQ,eAAe,EAAE,CAAA;AACtC,IAAA,MAAA,CAAO,aAAA,CAAc,IAAI,KAAA,CAAM,0BAA0B,CAAC,CAAA;AAAA,EAC5D,CAAA,EAAG,EAAE,CAAA;AAGL,EAAA,MAAM,aAAA,GAAgB,WAAA,CAAY,OAAO,KAAA,KAAkB;AACzD,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,+CAAA,EAAiD;AAAA,QACvE,OAAA,EAAS,EAAE,aAAA,EAAe,CAAA,OAAA,EAAU,KAAK,CAAA,CAAA;AAAG,OAC7C,CAAA;AACD,MAAA,IAAI,IAAI,EAAA,EAAI;AACV,QAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,QAAA,MAAM,QAAA,GAAuB,EAAE,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,MAAM,IAAA,CAAK,IAAA,EAAM,OAAA,EAAS,IAAA,CAAK,OAAA,EAAQ;AACzF,QAAA,YAAA,CAAa,OAAA,CAAQ,QAAA,EAAU,IAAA,CAAK,SAAA,CAAU,QAAQ,CAAC,CAAA;AACvD,QAAA,OAAA,CAAQ,QAAQ,CAAA;AAAA,MAClB;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAAe;AAAA,EACzB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,kBAAA,GAAqB,YAAY,MAAM;AAC3C,IAAA,IAAI,eAAA,CAAgB,YAAY,IAAA,EAAM;AACpC,MAAA,MAAA,CAAO,YAAA,CAAa,gBAAgB,OAAO,CAAA;AAC3C,MAAA,eAAA,CAAgB,OAAA,GAAU,IAAA;AAAA,IAC5B;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAKL,EAAA,MAAM,qBAAA,GAAwB,YAAY,MAAM;AAC9C,IAAA,kBAAA,EAAmB;AACnB,IAAA,MAAM,SAAS,QAAA,CAAS,YAAA,CAAa,QAAQ,gBAAgB,CAAA,IAAK,KAAK,EAAE,CAAA;AACzE,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,CAAA,EAAG,SAAS,IAAA,CAAK,GAAA,KAAQ,GAAM,CAAA;AACtD,IAAA,eAAA,CAAgB,OAAA,GAAU,MAAA,CAAO,UAAA,CAAW,MAAM;AAChD,MAAA,eAAA,CAAgB,OAAA,GAAU,IAAA;AAC1B,MAAA,IAAI,CAAC,UAAU,OAAA,EAAS;AACxB,MAAA,iBAAA,CAAkB,OAAA,GAAU,IAAA;AAC5B,MAAA,IAAI;AACF,QAAA,SAAA,CAAU,OAAA,CAAQ,kBAAA,CAAmB,EAAE,MAAA,EAAQ,IAAI,CAAA;AAAA,MACrD,CAAA,CAAA,MAAQ;AACN,QAAA,iBAAA,CAAkB,OAAA,GAAU,KAAA;AAAA,MAC9B;AAAA,IACF,GAAG,KAAK,CAAA;AAAA,EACV,CAAA,EAAG,CAAC,kBAAkB,CAAC,CAAA;AAEvB,EAAA,MAAM,mBAAA,GAAsB,WAAA,CAAY,CAAC,QAAA,KAAkB;AACzD,IAAA,MAAM,YAAY,iBAAA,CAAkB,OAAA;AACpC,IAAA,iBAAA,CAAkB,OAAA,GAAU,KAAA;AAC5B,IAAA,IAAI,SAAS,KAAA,EAAO;AAClB,MAAA,IAAI,SAAA,EAAW;AAIb,QAAA,YAAA,CAAa,WAAW,SAAS,CAAA;AACjC,QAAA,YAAA,CAAa,WAAW,gBAAgB,CAAA;AACxC,QAAA,cAAA,CAAe,IAAI,CAAA;AAAA,MACrB,CAAA,MAAO;AACL,QAAA,QAAA,CAAS,SAAS,KAAK,CAAA;AAAA,MACzB;AACA,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,QAAQ,QAAA,CAAS,YAAA;AACvB,IAAA,MAAM,SAAA,GAAY,SAAS,UAAA,IAAc,IAAA;AACzC,IAAA,YAAA,CAAa,OAAA,CAAQ,WAAW,KAAK,CAAA;AACrC,IAAA,YAAA,CAAa,OAAA,CAAQ,kBAAkB,MAAA,CAAO,IAAA,CAAK,KAAI,GAAI,SAAA,GAAY,GAAI,CAAC,CAAA;AAC5E,IAAA,cAAA,CAAe,KAAK,CAAA;AACpB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,UAAA,CAAW,KAAK,CAAA;AAChB,IAAA,IAAI,CAAC,SAAA,EAAW,aAAA,CAAc,KAAK,CAAA;AAEnC,IAAA,qBAAA,EAAsB;AAAA,EACxB,CAAA,EAAG,CAAC,aAAA,EAAe,qBAAqB,CAAC,CAAA;AAMzC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,QAAA,EAAU;AACf,IAAA,aAAA,EAAc,CAAE,KAAK,MAAM;AACzB,MAAA,MAAM,SAAU,MAAA,CAAe,MAAA;AAC/B,MAAA,IAAI,CAAC,MAAA,EAAQ,QAAA,EAAU,MAAA,EAAQ;AAC/B,MAAA,SAAA,CAAU,OAAA,GAAU,MAAA,CAAO,QAAA,CAAS,MAAA,CAAO,eAAA,CAAgB;AAAA,QACzD,SAAA,EAAW,QAAA;AAAA,QACX,KAAA,EAAO,MAAA;AAAA,QACP,QAAA,EAAU;AAAA,OACX,CAAA;AACD,MAAA,IAAI,cAAa,EAAG;AAClB,QAAA,qBAAA,EAAsB;AAAA,MACxB,CAAA,MAAA,IAAW,YAAA,CAAa,OAAA,CAAQ,SAAS,CAAA,EAAG;AAG1C,QAAA,iBAAA,CAAkB,OAAA,GAAU,IAAA;AAC5B,QAAA,IAAI;AACF,UAAA,SAAA,CAAU,OAAA,CAAQ,kBAAA,CAAmB,EAAE,MAAA,EAAQ,IAAI,CAAA;AAAA,QACrD,CAAA,CAAA,MAAQ;AACN,UAAA,iBAAA,CAAkB,OAAA,GAAU,KAAA;AAAA,QAC9B;AAAA,MACF;AAAA,IACF,CAAC,CAAA,CAAE,KAAA,CAAM,SAAO,QAAA,CAAS,GAAA,CAAI,OAAO,CAAC,CAAA;AACrC,IAAA,OAAO,MAAM,kBAAA,EAAmB;AAAA,EAClC,GAAG,CAAC,QAAA,EAAU,mBAAA,EAAqB,qBAAA,EAAuB,kBAAkB,CAAC,CAAA;AAE7E,EAAA,MAAM,OAAA,GAAU,YAAY,MAAM;AAChC,IAAA,IAAI,CAAC,UAAU,OAAA,EAAS;AACtB,MAAA,QAAA,CAAS,sDAAsD,CAAA;AAC/D,MAAA;AAAA,IACF;AACA,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,SAAA,CAAU,OAAA,CAAQ,kBAAA,CAAmB,EAAE,MAAA,EAAQ,WAAW,CAAA;AAAA,EAC5D,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,UAAA,GAAa,YAAY,MAAM;AACnC,IAAA,kBAAA,EAAmB;AACnB,IAAA,MAAM,KAAA,GAAQ,YAAA,CAAa,OAAA,CAAQ,SAAS,CAAA;AAC5C,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,MAAM,SAAU,MAAA,CAAe,MAAA;AAC/B,MAAA,IAAI,MAAA,EAAQ,UAAU,MAAA,EAAQ;AAC5B,QAAA,MAAA,CAAO,QAAA,CAAS,MAAA,CAAO,MAAA,CAAO,KAAK,CAAA;AAAA,MACrC;AAAA,IACF;AACA,IAAA,YAAA,CAAa,WAAW,SAAS,CAAA;AACjC,IAAA,YAAA,CAAa,WAAW,gBAAgB,CAAA;AACxC,IAAA,YAAA,CAAa,WAAW,QAAQ,CAAA;AAChC,IAAA,cAAA,CAAe,IAAI,CAAA;AACnB,IAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,EACd,CAAA,EAAG,CAAC,kBAAkB,CAAC,CAAA;AAKvB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,QAAA,GAAW,YAAY,MAAM;AACjC,MAAA,IAAI,WAAA,IAAe,CAAC,YAAA,EAAa,EAAG;AAClC,QAAA,cAAA,CAAe,IAAI,CAAA;AAEnB,QAAA,IAAI,SAAA,CAAU,OAAA,IAAW,CAAC,iBAAA,CAAkB,OAAA,EAAS;AACnD,UAAA,iBAAA,CAAkB,OAAA,GAAU,IAAA;AAC5B,UAAA,IAAI;AAAE,YAAA,SAAA,CAAU,OAAA,CAAQ,kBAAA,CAAmB,EAAE,MAAA,EAAQ,IAAI,CAAA;AAAA,UAAG,CAAA,CAAA,MACtD;AAAE,YAAA,iBAAA,CAAkB,OAAA,GAAU,KAAA;AAAA,UAAO;AAAA,QAC7C;AAAA,MACF;AAAA,IACF,GAAG,GAAK,CAAA;AACR,IAAA,OAAO,MAAM,cAAc,QAAQ,CAAA;AAAA,EACrC,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,EAAA,OAAO;AAAA,IACL,WAAA,EAAa,CAAC,CAAC,WAAA,IAAe,YAAA,EAAa;AAAA,IAC3C,IAAA;AAAA,IACA,WAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA;AAAA,IACA,aAAa,MAAM,QAAA;AAAA,IACnB,WAAA;AAAA,IACA;AAAA,GACF;AACF","file":"chunk-MVWEL34Y.js","sourcesContent":["/**\n * Google OAuth2 hook for Gmail, Calendar, Tasks, 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 * - Tasks: read/write (Todo List app)\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/tasks',\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"]}
|