@dalgoridim/headless-cms 0.3.1 → 0.5.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/dist/adapters/firestore/index.cjs.map +1 -1
- package/dist/adapters/firestore/index.js.map +1 -1
- package/dist/adapters/postgres/index.cjs.map +1 -1
- package/dist/adapters/postgres/index.js.map +1 -1
- package/dist/auth/firebase/client/index.d.cts +2 -2
- package/dist/auth/firebase/client/index.d.ts +2 -2
- package/dist/auth/google/client/index.cjs +80 -81
- package/dist/auth/google/client/index.cjs.map +1 -1
- package/dist/auth/google/client/index.d.cts +28 -44
- package/dist/auth/google/client/index.d.ts +28 -44
- package/dist/auth/google/client/index.js +85 -81
- package/dist/auth/google/client/index.js.map +1 -1
- package/dist/client/index.cjs +99 -1
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +32 -13
- package/dist/client/index.d.ts +32 -13
- package/dist/client/index.js +99 -1
- package/dist/client/index.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
"use client";
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defProps = Object.defineProperties;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
5
7
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
6
9
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
11
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
12
|
+
var __spreadValues = (a, b) => {
|
|
13
|
+
for (var prop in b || (b = {}))
|
|
14
|
+
if (__hasOwnProp.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
if (__getOwnPropSymbols)
|
|
17
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
18
|
+
if (__propIsEnum.call(b, prop))
|
|
19
|
+
__defNormalProp(a, prop, b[prop]);
|
|
20
|
+
}
|
|
21
|
+
return a;
|
|
22
|
+
};
|
|
23
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
24
|
+
var __objRest = (source, exclude) => {
|
|
25
|
+
var target = {};
|
|
26
|
+
for (var prop in source)
|
|
27
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
28
|
+
target[prop] = source[prop];
|
|
29
|
+
if (source != null && __getOwnPropSymbols)
|
|
30
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
31
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
32
|
+
target[prop] = source[prop];
|
|
33
|
+
}
|
|
34
|
+
return target;
|
|
35
|
+
};
|
|
7
36
|
var __export = (target, all) => {
|
|
8
37
|
for (var name in all)
|
|
9
38
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -22,13 +51,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
22
51
|
var client_exports = {};
|
|
23
52
|
__export(client_exports, {
|
|
24
53
|
GoogleAuthProvider: () => GoogleAuthProvider,
|
|
54
|
+
GoogleSignInButton: () => GoogleSignInButton,
|
|
25
55
|
useGoogleAuth: () => useGoogleAuth
|
|
26
56
|
});
|
|
27
57
|
module.exports = __toCommonJS(client_exports);
|
|
28
58
|
var import_react = require("react");
|
|
59
|
+
var import_google = require("@react-oauth/google");
|
|
29
60
|
var import_client = require("@dalgoridim/headless-cms/client");
|
|
30
61
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
31
|
-
var GSI_SRC = "https://accounts.google.com/gsi/client";
|
|
32
62
|
var GoogleAuthContext = (0, import_react.createContext)(
|
|
33
63
|
void 0
|
|
34
64
|
);
|
|
@@ -49,15 +79,13 @@ function decodeJwt(token) {
|
|
|
49
79
|
try {
|
|
50
80
|
const payload = token.split(".")[1];
|
|
51
81
|
const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
52
|
-
|
|
53
|
-
return data;
|
|
82
|
+
return JSON.parse(json);
|
|
54
83
|
} catch (e) {
|
|
55
84
|
return null;
|
|
56
85
|
}
|
|
57
86
|
}
|
|
58
|
-
function
|
|
87
|
+
function AuthState({
|
|
59
88
|
children,
|
|
60
|
-
clientId,
|
|
61
89
|
adminEmails,
|
|
62
90
|
cookieName = "adminToken",
|
|
63
91
|
onLogout
|
|
@@ -65,12 +93,10 @@ function GoogleAuthProvider({
|
|
|
65
93
|
const [user, setUser] = (0, import_react.useState)(null);
|
|
66
94
|
const [isAdmin, setIsAdmin] = (0, import_react.useState)(false);
|
|
67
95
|
const [isEditing, setIsEditing] = (0, import_react.useState)(false);
|
|
68
|
-
const [ready, setReady] = (0, import_react.useState)(false);
|
|
69
|
-
const [failed, setFailed] = (0, import_react.useState)(false);
|
|
70
96
|
const allowed = (0, import_react.useRef)(
|
|
71
97
|
(adminEmails != null ? adminEmails : []).map((e) => e.trim().toLowerCase())
|
|
72
98
|
);
|
|
73
|
-
const
|
|
99
|
+
const applyCredential = (0, import_react.useCallback)(
|
|
74
100
|
(token) => {
|
|
75
101
|
var _a;
|
|
76
102
|
const claims = decodeJwt(token);
|
|
@@ -94,44 +120,16 @@ function GoogleAuthProvider({
|
|
|
94
120
|
);
|
|
95
121
|
(0, import_react.useEffect)(() => {
|
|
96
122
|
const existing = readCookie(cookieName);
|
|
97
|
-
if (existing)
|
|
98
|
-
}, [cookieName,
|
|
99
|
-
(0, import_react.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
auto_select: false,
|
|
108
|
-
cancel_on_tap_outside: true
|
|
109
|
-
});
|
|
110
|
-
setReady(true);
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
if (tryInit()) return;
|
|
114
|
-
if (!document.querySelector(`script[src="${GSI_SRC}"]`)) {
|
|
115
|
-
const script = document.createElement("script");
|
|
116
|
-
script.src = GSI_SRC;
|
|
117
|
-
script.async = true;
|
|
118
|
-
script.defer = true;
|
|
119
|
-
document.head.appendChild(script);
|
|
120
|
-
}
|
|
121
|
-
const startedAt = Date.now();
|
|
122
|
-
const timer = setInterval(() => {
|
|
123
|
-
if (tryInit()) {
|
|
124
|
-
clearInterval(timer);
|
|
125
|
-
} else if (Date.now() - startedAt > 1e4) {
|
|
126
|
-
clearInterval(timer);
|
|
127
|
-
if (!cancelled) setFailed(true);
|
|
128
|
-
}
|
|
129
|
-
}, 120);
|
|
130
|
-
return () => {
|
|
131
|
-
cancelled = true;
|
|
132
|
-
clearInterval(timer);
|
|
133
|
-
};
|
|
134
|
-
}, [clientId, applyToken]);
|
|
123
|
+
if (existing) applyCredential(existing);
|
|
124
|
+
}, [cookieName, applyCredential]);
|
|
125
|
+
const logout = (0, import_react.useCallback)(() => {
|
|
126
|
+
(0, import_google.googleLogout)();
|
|
127
|
+
deleteCookie(cookieName);
|
|
128
|
+
setUser(null);
|
|
129
|
+
setIsAdmin(false);
|
|
130
|
+
setIsEditing(false);
|
|
131
|
+
onLogout == null ? void 0 : onLogout();
|
|
132
|
+
}, [cookieName, onLogout]);
|
|
135
133
|
(0, import_react.useEffect)(() => {
|
|
136
134
|
const originalFetch = window.fetch;
|
|
137
135
|
window.fetch = async (...args) => {
|
|
@@ -139,7 +137,7 @@ function GoogleAuthProvider({
|
|
|
139
137
|
if (response.status === 401) {
|
|
140
138
|
try {
|
|
141
139
|
const data = await response.clone().json();
|
|
142
|
-
if (data == null ? void 0 : data.logout)
|
|
140
|
+
if (data == null ? void 0 : data.logout) logout();
|
|
143
141
|
} catch (e) {
|
|
144
142
|
}
|
|
145
143
|
}
|
|
@@ -148,48 +146,48 @@ function GoogleAuthProvider({
|
|
|
148
146
|
return () => {
|
|
149
147
|
window.fetch = originalFetch;
|
|
150
148
|
};
|
|
151
|
-
}, []);
|
|
152
|
-
function doLogout() {
|
|
153
|
-
var _a;
|
|
154
|
-
(_a = window.google) == null ? void 0 : _a.accounts.id.disableAutoSelect();
|
|
155
|
-
deleteCookie(cookieName);
|
|
156
|
-
setUser(null);
|
|
157
|
-
setIsAdmin(false);
|
|
158
|
-
setIsEditing(false);
|
|
159
|
-
onLogout == null ? void 0 : onLogout();
|
|
160
|
-
}
|
|
161
|
-
const promptSignIn = (0, import_react.useCallback)(() => {
|
|
162
|
-
var _a;
|
|
163
|
-
if (!ready) return;
|
|
164
|
-
(_a = window.google) == null ? void 0 : _a.accounts.id.prompt();
|
|
165
|
-
}, [ready]);
|
|
166
|
-
const renderButton = (0, import_react.useCallback)(
|
|
167
|
-
(el, options) => {
|
|
168
|
-
var _a;
|
|
169
|
-
if (!ready) return;
|
|
170
|
-
(_a = window.google) == null ? void 0 : _a.accounts.id.renderButton(el, options);
|
|
171
|
-
},
|
|
172
|
-
[ready]
|
|
173
|
-
);
|
|
149
|
+
}, [logout]);
|
|
174
150
|
const toggleEdit = (0, import_react.useCallback)(() => setIsEditing((p) => !p), []);
|
|
175
151
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
176
152
|
GoogleAuthContext.Provider,
|
|
177
153
|
{
|
|
178
|
-
value: {
|
|
179
|
-
user,
|
|
180
|
-
isAdmin,
|
|
181
|
-
isEditing,
|
|
182
|
-
toggleEdit,
|
|
183
|
-
ready,
|
|
184
|
-
failed,
|
|
185
|
-
promptSignIn,
|
|
186
|
-
renderButton,
|
|
187
|
-
logout: doLogout
|
|
188
|
-
},
|
|
154
|
+
value: { user, isAdmin, isEditing, toggleEdit, logout, applyCredential },
|
|
189
155
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_client.CmsAuthProvider, { value: { isAdmin, isEditing, toggleEdit }, children })
|
|
190
156
|
}
|
|
191
157
|
);
|
|
192
158
|
}
|
|
159
|
+
function GoogleAuthProvider(_a) {
|
|
160
|
+
var _b = _a, {
|
|
161
|
+
children,
|
|
162
|
+
clientId
|
|
163
|
+
} = _b, rest = __objRest(_b, [
|
|
164
|
+
"children",
|
|
165
|
+
"clientId"
|
|
166
|
+
]);
|
|
167
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_google.GoogleOAuthProvider, { clientId, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthState, __spreadProps(__spreadValues({}, rest), { children })) });
|
|
168
|
+
}
|
|
169
|
+
function GoogleSignInButton({
|
|
170
|
+
theme = "filled_black",
|
|
171
|
+
shape = "pill",
|
|
172
|
+
size = "large",
|
|
173
|
+
text = "continue_with",
|
|
174
|
+
onError
|
|
175
|
+
}) {
|
|
176
|
+
const { applyCredential } = useGoogleAuth();
|
|
177
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
178
|
+
import_google.GoogleLogin,
|
|
179
|
+
{
|
|
180
|
+
onSuccess: (res) => {
|
|
181
|
+
if (res.credential) applyCredential(res.credential);
|
|
182
|
+
},
|
|
183
|
+
onError,
|
|
184
|
+
theme,
|
|
185
|
+
shape,
|
|
186
|
+
size,
|
|
187
|
+
text
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
}
|
|
193
191
|
function useGoogleAuth() {
|
|
194
192
|
const ctx = (0, import_react.useContext)(GoogleAuthContext);
|
|
195
193
|
if (!ctx) {
|
|
@@ -200,6 +198,7 @@ function useGoogleAuth() {
|
|
|
200
198
|
// Annotate the CommonJS export names for ESM import in node:
|
|
201
199
|
0 && (module.exports = {
|
|
202
200
|
GoogleAuthProvider,
|
|
201
|
+
GoogleSignInButton,
|
|
203
202
|
useGoogleAuth
|
|
204
203
|
});
|
|
205
204
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/auth/google/client/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\n// Imported via the public specifier so we share the SAME context instance as\n// the consumer's edit primitives at runtime (see tsup `external` + tsconfig paths).\nimport { CmsAuthProvider } from \"@dalgoridim/headless-cms/client\";\n\n/**\n * Client provider for **Google Identity Services** sign-in — the Firebase-free\n * counterpart to `FirebaseAuthProvider`. It loads the GSI script, lets the user\n * sign in with Google, stashes the resulting ID token in a cookie for the\n * server `googleAuth` adapter to verify, and feeds the shared CMS auth context\n * so the edit primitives light up. Admin status is optimistic on the client\n * (via `adminEmails`); the server gate remains authoritative.\n */\n\nconst GSI_SRC = \"https://accounts.google.com/gsi/client\";\n\n/** Minimal slice of the Google Identity Services API we use. */\ninterface GsiCredentialResponse {\n credential: string;\n}\ninterface GsiButtonOptions {\n type?: \"standard\" | \"icon\";\n theme?: \"outline\" | \"filled_blue\" | \"filled_black\";\n size?: \"small\" | \"medium\" | \"large\";\n text?: \"signin_with\" | \"signup_with\" | \"continue_with\" | \"signin\";\n shape?: \"rectangular\" | \"pill\" | \"circle\" | \"square\";\n width?: number;\n}\ninterface GsiClient {\n accounts: {\n id: {\n initialize: (config: {\n client_id: string;\n callback: (res: GsiCredentialResponse) => void;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n }) => void;\n prompt: () => void;\n renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;\n disableAutoSelect: () => void;\n };\n };\n}\n\ndeclare global {\n interface Window {\n google?: GsiClient;\n }\n}\n\nexport interface GoogleUser {\n sub: string;\n email?: string;\n name?: string;\n picture?: string;\n}\n\nexport interface GoogleAuthProviderProps {\n children: ReactNode;\n /** OAuth 2.0 Web client ID (from Google Cloud Console → Credentials). */\n clientId: string;\n /**\n * Optional admin allowlist for *optimistic* client-side `isAdmin`. The server\n * `googleAuth` adapter still enforces the real gate. Omit to treat any\n * successful sign-in as optimistically admin (server will correct via 401).\n */\n adminEmails?: string[];\n /** Cookie name for the ID token. Default `adminToken`. */\n cookieName?: string;\n /** Called when a 401 `{ logout: true }` response is intercepted. */\n onLogout?: () => void;\n}\n\nexport interface GoogleAuthContextValue {\n user: GoogleUser | null;\n isAdmin: boolean;\n isEditing: boolean;\n toggleEdit: () => void;\n /** True once the GSI script has loaded and the client is initialized. */\n ready: boolean;\n /** True if GSI failed to load within the timeout (blocked/offline). */\n failed: boolean;\n /** Trigger Google One Tap / sign-in prompt. */\n promptSignIn: () => void;\n /** Render the official Google button into `el` (most reliable trigger). */\n renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;\n logout: () => void;\n}\n\nconst GoogleAuthContext = createContext<GoogleAuthContextValue | undefined>(\n undefined,\n);\n\nfunction setCookie(name: string, value: string) {\n document.cookie = `${name}=${encodeURIComponent(value)}; path=/; samesite=lax`;\n}\nfunction deleteCookie(name: string) {\n document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;\n}\nfunction readCookie(name: string): string | null {\n for (const part of document.cookie.split(\";\")) {\n const [k, ...v] = part.trim().split(\"=\");\n if (k === name) return decodeURIComponent(v.join(\"=\"));\n }\n return null;\n}\n\n/** Decode a JWT payload client-side (no verification — the server does that). */\nfunction decodeJwt(token: string): (GoogleUser & { exp?: number }) | null {\n try {\n const payload = token.split(\".\")[1];\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const data = JSON.parse(json) as {\n sub: string;\n email?: string;\n name?: string;\n picture?: string;\n exp?: number;\n };\n return data;\n } catch {\n return null;\n }\n}\n\nexport function GoogleAuthProvider({\n children,\n clientId,\n adminEmails,\n cookieName = \"adminToken\",\n onLogout,\n}: GoogleAuthProviderProps) {\n const [user, setUser] = useState<GoogleUser | null>(null);\n const [isAdmin, setIsAdmin] = useState(false);\n const [isEditing, setIsEditing] = useState(false);\n const [ready, setReady] = useState(false);\n const [failed, setFailed] = useState(false);\n const allowed = useRef(\n (adminEmails ?? []).map((e) => e.trim().toLowerCase()),\n );\n\n const applyToken = useCallback(\n (token: string) => {\n const claims = decodeJwt(token);\n if (!claims) return;\n if (claims.exp && claims.exp * 1000 <= Date.now()) {\n deleteCookie(cookieName);\n return;\n }\n const email = claims.email?.toLowerCase();\n // Optimistic: if no allowlist supplied, trust the sign-in and let the\n // server correct a non-admin via the 401 interceptor below.\n const admin =\n allowed.current.length === 0\n ? true\n : !!email && allowed.current.includes(email);\n setCookie(cookieName, token);\n setUser({\n sub: claims.sub,\n email: claims.email,\n name: claims.name,\n picture: claims.picture,\n });\n setIsAdmin(admin);\n },\n [cookieName],\n );\n\n // Restore an existing session from the cookie on mount.\n useEffect(() => {\n const existing = readCookie(cookieName);\n if (existing) applyToken(existing);\n }, [cookieName, applyToken]);\n\n // Load the GSI script and initialize the client. Polling (rather than relying\n // on a `load` event) makes this robust against React's dev double-mount, a\n // cached/already-loaded script, and missed load events — any of which could\n // otherwise leave `ready` stuck false and the UI spinning forever.\n useEffect(() => {\n let cancelled = false;\n\n function tryInit(): boolean {\n if (cancelled || !window.google?.accounts?.id) return false;\n window.google.accounts.id.initialize({\n client_id: clientId,\n callback: (res) => applyToken(res.credential),\n auto_select: false,\n cancel_on_tap_outside: true,\n });\n setReady(true);\n return true;\n }\n\n if (tryInit()) return;\n\n // Ensure the script tag exists exactly once across mounts.\n if (!document.querySelector(`script[src=\"${GSI_SRC}\"]`)) {\n const script = document.createElement(\"script\");\n script.src = GSI_SRC;\n script.async = true;\n script.defer = true;\n document.head.appendChild(script);\n }\n\n const startedAt = Date.now();\n const timer = setInterval(() => {\n if (tryInit()) {\n clearInterval(timer);\n } else if (Date.now() - startedAt > 10_000) {\n // Blocked (ad/privacy extension) or offline — surface a failure so the\n // UI can show a fallback instead of an endless spinner.\n clearInterval(timer);\n if (!cancelled) setFailed(true);\n }\n }, 120);\n\n return () => {\n cancelled = true;\n clearInterval(timer);\n };\n }, [clientId, applyToken]);\n\n // Intercept admin 401s so an expired/forbidden session forces sign-out.\n useEffect(() => {\n const originalFetch = window.fetch;\n window.fetch = async (...args: Parameters<typeof fetch>) => {\n const response = await originalFetch(...args);\n if (response.status === 401) {\n try {\n const data = await response.clone().json();\n if (data?.logout) doLogout();\n } catch {\n /* not a JSON body — ignore */\n }\n }\n return response;\n };\n return () => {\n window.fetch = originalFetch;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n function doLogout() {\n window.google?.accounts.id.disableAutoSelect();\n deleteCookie(cookieName);\n setUser(null);\n setIsAdmin(false);\n setIsEditing(false);\n onLogout?.();\n }\n\n const promptSignIn = useCallback(() => {\n if (!ready) return;\n window.google?.accounts.id.prompt();\n }, [ready]);\n\n const renderButton = useCallback(\n (el: HTMLElement, options?: GsiButtonOptions) => {\n if (!ready) return;\n window.google?.accounts.id.renderButton(el, options);\n },\n [ready],\n );\n\n const toggleEdit = useCallback(() => setIsEditing((p) => !p), []);\n\n return (\n <GoogleAuthContext.Provider\n value={{\n user,\n isAdmin,\n isEditing,\n toggleEdit,\n ready,\n failed,\n promptSignIn,\n renderButton,\n logout: doLogout,\n }}\n >\n <CmsAuthProvider value={{ isAdmin, isEditing, toggleEdit }}>\n {children}\n </CmsAuthProvider>\n </GoogleAuthContext.Provider>\n );\n}\n\n/** Google auth API (user/login/logout) for login pages and toolbars. */\nexport function useGoogleAuth(): GoogleAuthContextValue {\n const ctx = useContext(GoogleAuthContext);\n if (!ctx) {\n throw new Error(\"useGoogleAuth must be used within a GoogleAuthProvider\");\n }\n return ctx;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,mBAQO;AAGP,oBAAgC;AAsR1B;AA3QN,IAAM,UAAU;AA2EhB,IAAM,wBAAoB;AAAA,EACxB;AACF;AAEA,SAAS,UAAU,MAAc,OAAe;AAC9C,WAAS,SAAS,GAAG,IAAI,IAAI,mBAAmB,KAAK,CAAC;AACxD;AACA,SAAS,aAAa,MAAc;AAClC,WAAS,SAAS,GAAG,IAAI;AAC3B;AACA,SAAS,WAAW,MAA6B;AAC/C,aAAW,QAAQ,SAAS,OAAO,MAAM,GAAG,GAAG;AAC7C,UAAM,CAAC,GAAG,GAAG,CAAC,IAAI,KAAK,KAAK,EAAE,MAAM,GAAG;AACvC,QAAI,MAAM,KAAM,QAAO,mBAAmB,EAAE,KAAK,GAAG,CAAC;AAAA,EACvD;AACA,SAAO;AACT;AAGA,SAAS,UAAU,OAAuD;AACxE,MAAI;AACF,UAAM,UAAU,MAAM,MAAM,GAAG,EAAE,CAAC;AAClC,UAAM,OAAO,KAAK,QAAQ,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,CAAC;AAC/D,UAAM,OAAO,KAAK,MAAM,IAAI;AAO5B,WAAO;AAAA,EACT,SAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AACF,GAA4B;AAC1B,QAAM,CAAC,MAAM,OAAO,QAAI,uBAA4B,IAAI;AACxD,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,KAAK;AAC5C,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAS,KAAK;AAChD,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,KAAK;AACxC,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAAS,KAAK;AAC1C,QAAM,cAAU;AAAA,KACb,oCAAe,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC;AAAA,EACvD;AAEA,QAAM,iBAAa;AAAA,IACjB,CAAC,UAAkB;AAxJvB;AAyJM,YAAM,SAAS,UAAU,KAAK;AAC9B,UAAI,CAAC,OAAQ;AACb,UAAI,OAAO,OAAO,OAAO,MAAM,OAAQ,KAAK,IAAI,GAAG;AACjD,qBAAa,UAAU;AACvB;AAAA,MACF;AACA,YAAM,SAAQ,YAAO,UAAP,mBAAc;AAG5B,YAAM,QACJ,QAAQ,QAAQ,WAAW,IACvB,OACA,CAAC,CAAC,SAAS,QAAQ,QAAQ,SAAS,KAAK;AAC/C,gBAAU,YAAY,KAAK;AAC3B,cAAQ;AAAA,QACN,KAAK,OAAO;AAAA,QACZ,OAAO,OAAO;AAAA,QACd,MAAM,OAAO;AAAA,QACb,SAAS,OAAO;AAAA,MAClB,CAAC;AACD,iBAAW,KAAK;AAAA,IAClB;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AAGA,8BAAU,MAAM;AACd,UAAM,WAAW,WAAW,UAAU;AACtC,QAAI,SAAU,YAAW,QAAQ;AAAA,EACnC,GAAG,CAAC,YAAY,UAAU,CAAC;AAM3B,8BAAU,MAAM;AACd,QAAI,YAAY;AAEhB,aAAS,UAAmB;AA/LhC;AAgMM,UAAI,aAAa,GAAC,kBAAO,WAAP,mBAAe,aAAf,mBAAyB,IAAI,QAAO;AACtD,aAAO,OAAO,SAAS,GAAG,WAAW;AAAA,QACnC,WAAW;AAAA,QACX,UAAU,CAAC,QAAQ,WAAW,IAAI,UAAU;AAAA,QAC5C,aAAa;AAAA,QACb,uBAAuB;AAAA,MACzB,CAAC;AACD,eAAS,IAAI;AACb,aAAO;AAAA,IACT;AAEA,QAAI,QAAQ,EAAG;AAGf,QAAI,CAAC,SAAS,cAAc,eAAe,OAAO,IAAI,GAAG;AACvD,YAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,aAAO,MAAM;AACb,aAAO,QAAQ;AACf,aAAO,QAAQ;AACf,eAAS,KAAK,YAAY,MAAM;AAAA,IAClC;AAEA,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,QAAQ,YAAY,MAAM;AAC9B,UAAI,QAAQ,GAAG;AACb,sBAAc,KAAK;AAAA,MACrB,WAAW,KAAK,IAAI,IAAI,YAAY,KAAQ;AAG1C,sBAAc,KAAK;AACnB,YAAI,CAAC,UAAW,WAAU,IAAI;AAAA,MAChC;AAAA,IACF,GAAG,GAAG;AAEN,WAAO,MAAM;AACX,kBAAY;AACZ,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,UAAU,UAAU,CAAC;AAGzB,8BAAU,MAAM;AACd,UAAM,gBAAgB,OAAO;AAC7B,WAAO,QAAQ,UAAU,SAAmC;AAC1D,YAAM,WAAW,MAAM,cAAc,GAAG,IAAI;AAC5C,UAAI,SAAS,WAAW,KAAK;AAC3B,YAAI;AACF,gBAAM,OAAO,MAAM,SAAS,MAAM,EAAE,KAAK;AACzC,cAAI,6BAAM,OAAQ,UAAS;AAAA,QAC7B,SAAQ;AAAA,QAER;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,WAAO,MAAM;AACX,aAAO,QAAQ;AAAA,IACjB;AAAA,EAEF,GAAG,CAAC,CAAC;AAEL,WAAS,WAAW;AA7PtB;AA8PI,iBAAO,WAAP,mBAAe,SAAS,GAAG;AAC3B,iBAAa,UAAU;AACvB,YAAQ,IAAI;AACZ,eAAW,KAAK;AAChB,iBAAa,KAAK;AAClB;AAAA,EACF;AAEA,QAAM,mBAAe,0BAAY,MAAM;AAtQzC;AAuQI,QAAI,CAAC,MAAO;AACZ,iBAAO,WAAP,mBAAe,SAAS,GAAG;AAAA,EAC7B,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,mBAAe;AAAA,IACnB,CAAC,IAAiB,YAA+B;AA5QrD;AA6QM,UAAI,CAAC,MAAO;AACZ,mBAAO,WAAP,mBAAe,SAAS,GAAG,aAAa,IAAI;AAAA,IAC9C;AAAA,IACA,CAAC,KAAK;AAAA,EACR;AAEA,QAAM,iBAAa,0BAAY,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC;AAEhE,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,MACV;AAAA,MAEA,sDAAC,iCAAgB,OAAO,EAAE,SAAS,WAAW,WAAW,GACtD,UACH;AAAA;AAAA,EACF;AAEJ;AAGO,SAAS,gBAAwC;AACtD,QAAM,UAAM,yBAAW,iBAAiB;AACxC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AACA,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/auth/google/client/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n GoogleOAuthProvider,\n GoogleLogin,\n googleLogout,\n type CredentialResponse,\n} from \"@react-oauth/google\";\n// Imported via the public specifier so we share the SAME context instance as\n// the consumer's edit primitives at runtime (see tsup `external` + tsconfig paths).\nimport { CmsAuthProvider } from \"@dalgoridim/headless-cms/client\";\n\n/**\n * Client provider for **Google sign-in**, built on `@react-oauth/google` (the\n * official React wrapper for Google Identity Services). It handles the GSI\n * script + button rendering robustly — including remounts, so the button shows\n * reliably every time a sign-in dialog opens. The resulting ID token is stashed\n * in a cookie for the server `googleAuth` adapter to verify; admin status is\n * optimistic on the client (via `adminEmails`), with the server gate\n * authoritative.\n *\n * Render `<GoogleSignInButton />` anywhere inside the provider to show the\n * official Google button.\n */\n\nexport interface GoogleUser {\n sub: string;\n email?: string;\n name?: string;\n picture?: string;\n}\n\nexport interface GoogleAuthProviderProps {\n children: ReactNode;\n /** OAuth 2.0 Web client ID (from Google Cloud Console → Credentials). */\n clientId: string;\n /**\n * Optional admin allowlist for *optimistic* client-side `isAdmin`. The server\n * `googleAuth` adapter still enforces the real gate. Omit to treat any\n * successful sign-in as optimistically admin (server will correct via 401).\n */\n adminEmails?: string[];\n /** Cookie name for the ID token. Default `adminToken`. */\n cookieName?: string;\n /** Called when a 401 `{ logout: true }` response is intercepted. */\n onLogout?: () => void;\n}\n\nexport interface GoogleAuthContextValue {\n user: GoogleUser | null;\n isAdmin: boolean;\n isEditing: boolean;\n toggleEdit: () => void;\n logout: () => void;\n /** Feed a Google ID token credential (used by `GoogleSignInButton`). */\n applyCredential: (credential: string) => void;\n}\n\nconst GoogleAuthContext = createContext<GoogleAuthContextValue | undefined>(\n undefined,\n);\n\nfunction setCookie(name: string, value: string) {\n document.cookie = `${name}=${encodeURIComponent(value)}; path=/; samesite=lax`;\n}\nfunction deleteCookie(name: string) {\n document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;\n}\nfunction readCookie(name: string): string | null {\n for (const part of document.cookie.split(\";\")) {\n const [k, ...v] = part.trim().split(\"=\");\n if (k === name) return decodeURIComponent(v.join(\"=\"));\n }\n return null;\n}\n\n/** Decode a JWT payload client-side (no verification — the server does that). */\nfunction decodeJwt(token: string): (GoogleUser & { exp?: number }) | null {\n try {\n const payload = token.split(\".\")[1];\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n return JSON.parse(json);\n } catch {\n return null;\n }\n}\n\nfunction AuthState({\n children,\n adminEmails,\n cookieName = \"adminToken\",\n onLogout,\n}: Omit<GoogleAuthProviderProps, \"clientId\">) {\n const [user, setUser] = useState<GoogleUser | null>(null);\n const [isAdmin, setIsAdmin] = useState(false);\n const [isEditing, setIsEditing] = useState(false);\n const allowed = useRef(\n (adminEmails ?? []).map((e) => e.trim().toLowerCase()),\n );\n\n const applyCredential = useCallback(\n (token: string) => {\n const claims = decodeJwt(token);\n if (!claims) return;\n if (claims.exp && claims.exp * 1000 <= Date.now()) {\n deleteCookie(cookieName);\n return;\n }\n const email = claims.email?.toLowerCase();\n // Optimistic: if no allowlist supplied, trust the sign-in and let the\n // server correct a non-admin via the 401 interceptor below.\n const admin =\n allowed.current.length === 0\n ? true\n : !!email && allowed.current.includes(email);\n setCookie(cookieName, token);\n setUser({\n sub: claims.sub,\n email: claims.email,\n name: claims.name,\n picture: claims.picture,\n });\n setIsAdmin(admin);\n },\n [cookieName],\n );\n\n // Restore an existing session from the cookie on mount.\n useEffect(() => {\n const existing = readCookie(cookieName);\n if (existing) applyCredential(existing);\n }, [cookieName, applyCredential]);\n\n const logout = useCallback(() => {\n googleLogout();\n deleteCookie(cookieName);\n setUser(null);\n setIsAdmin(false);\n setIsEditing(false);\n onLogout?.();\n }, [cookieName, onLogout]);\n\n // Intercept admin 401s so an expired/forbidden session forces sign-out.\n useEffect(() => {\n const originalFetch = window.fetch;\n window.fetch = async (...args: Parameters<typeof fetch>) => {\n const response = await originalFetch(...args);\n if (response.status === 401) {\n try {\n const data = await response.clone().json();\n if (data?.logout) logout();\n } catch {\n /* not a JSON body — ignore */\n }\n }\n return response;\n };\n return () => {\n window.fetch = originalFetch;\n };\n }, [logout]);\n\n const toggleEdit = useCallback(() => setIsEditing((p) => !p), []);\n\n return (\n <GoogleAuthContext.Provider\n value={{ user, isAdmin, isEditing, toggleEdit, logout, applyCredential }}\n >\n <CmsAuthProvider value={{ isAdmin, isEditing, toggleEdit }}>\n {children}\n </CmsAuthProvider>\n </GoogleAuthContext.Provider>\n );\n}\n\nexport function GoogleAuthProvider({\n children,\n clientId,\n ...rest\n}: GoogleAuthProviderProps) {\n return (\n <GoogleOAuthProvider clientId={clientId}>\n <AuthState {...rest}>{children}</AuthState>\n </GoogleOAuthProvider>\n );\n}\n\nexport interface GoogleSignInButtonProps {\n /** Forwarded to the underlying `<GoogleLogin>` (theme, shape, size, text…). */\n theme?: \"outline\" | \"filled_blue\" | \"filled_black\";\n shape?: \"rectangular\" | \"pill\" | \"circle\" | \"square\";\n size?: \"small\" | \"medium\" | \"large\";\n text?: \"signin_with\" | \"signup_with\" | \"continue_with\" | \"signin\";\n onError?: () => void;\n}\n\n/** The official Google sign-in button. Must be rendered inside a provider. */\nexport function GoogleSignInButton({\n theme = \"filled_black\",\n shape = \"pill\",\n size = \"large\",\n text = \"continue_with\",\n onError,\n}: GoogleSignInButtonProps) {\n const { applyCredential } = useGoogleAuth();\n return (\n <GoogleLogin\n onSuccess={(res: CredentialResponse) => {\n if (res.credential) applyCredential(res.credential);\n }}\n onError={onError}\n theme={theme}\n shape={shape}\n size={size}\n text={text}\n />\n );\n}\n\n/** Google auth API (user/logout) for login pages and toolbars. */\nexport function useGoogleAuth(): GoogleAuthContextValue {\n const ctx = useContext(GoogleAuthContext);\n if (!ctx) {\n throw new Error(\"useGoogleAuth must be used within a GoogleAuthProvider\");\n }\n return ctx;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,38 +1,18 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
interface GsiClient {
|
|
17
|
-
accounts: {
|
|
18
|
-
id: {
|
|
19
|
-
initialize: (config: {
|
|
20
|
-
client_id: string;
|
|
21
|
-
callback: (res: GsiCredentialResponse) => void;
|
|
22
|
-
auto_select?: boolean;
|
|
23
|
-
cancel_on_tap_outside?: boolean;
|
|
24
|
-
}) => void;
|
|
25
|
-
prompt: () => void;
|
|
26
|
-
renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;
|
|
27
|
-
disableAutoSelect: () => void;
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
declare global {
|
|
32
|
-
interface Window {
|
|
33
|
-
google?: GsiClient;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
4
|
+
/**
|
|
5
|
+
* Client provider for **Google sign-in**, built on `@react-oauth/google` (the
|
|
6
|
+
* official React wrapper for Google Identity Services). It handles the GSI
|
|
7
|
+
* script + button rendering robustly — including remounts, so the button shows
|
|
8
|
+
* reliably every time a sign-in dialog opens. The resulting ID token is stashed
|
|
9
|
+
* in a cookie for the server `googleAuth` adapter to verify; admin status is
|
|
10
|
+
* optimistic on the client (via `adminEmails`), with the server gate
|
|
11
|
+
* authoritative.
|
|
12
|
+
*
|
|
13
|
+
* Render `<GoogleSignInButton />` anywhere inside the provider to show the
|
|
14
|
+
* official Google button.
|
|
15
|
+
*/
|
|
36
16
|
interface GoogleUser {
|
|
37
17
|
sub: string;
|
|
38
18
|
email?: string;
|
|
@@ -59,18 +39,22 @@ interface GoogleAuthContextValue {
|
|
|
59
39
|
isAdmin: boolean;
|
|
60
40
|
isEditing: boolean;
|
|
61
41
|
toggleEdit: () => void;
|
|
62
|
-
/** True once the GSI script has loaded and the client is initialized. */
|
|
63
|
-
ready: boolean;
|
|
64
|
-
/** True if GSI failed to load within the timeout (blocked/offline). */
|
|
65
|
-
failed: boolean;
|
|
66
|
-
/** Trigger Google One Tap / sign-in prompt. */
|
|
67
|
-
promptSignIn: () => void;
|
|
68
|
-
/** Render the official Google button into `el` (most reliable trigger). */
|
|
69
|
-
renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;
|
|
70
42
|
logout: () => void;
|
|
43
|
+
/** Feed a Google ID token credential (used by `GoogleSignInButton`). */
|
|
44
|
+
applyCredential: (credential: string) => void;
|
|
45
|
+
}
|
|
46
|
+
declare function GoogleAuthProvider({ children, clientId, ...rest }: GoogleAuthProviderProps): react.JSX.Element;
|
|
47
|
+
interface GoogleSignInButtonProps {
|
|
48
|
+
/** Forwarded to the underlying `<GoogleLogin>` (theme, shape, size, text…). */
|
|
49
|
+
theme?: "outline" | "filled_blue" | "filled_black";
|
|
50
|
+
shape?: "rectangular" | "pill" | "circle" | "square";
|
|
51
|
+
size?: "small" | "medium" | "large";
|
|
52
|
+
text?: "signin_with" | "signup_with" | "continue_with" | "signin";
|
|
53
|
+
onError?: () => void;
|
|
71
54
|
}
|
|
72
|
-
|
|
73
|
-
|
|
55
|
+
/** The official Google sign-in button. Must be rendered inside a provider. */
|
|
56
|
+
declare function GoogleSignInButton({ theme, shape, size, text, onError, }: GoogleSignInButtonProps): react.JSX.Element;
|
|
57
|
+
/** Google auth API (user/logout) for login pages and toolbars. */
|
|
74
58
|
declare function useGoogleAuth(): GoogleAuthContextValue;
|
|
75
59
|
|
|
76
|
-
export { type GoogleAuthContextValue, GoogleAuthProvider, type GoogleAuthProviderProps, type GoogleUser, useGoogleAuth };
|
|
60
|
+
export { type GoogleAuthContextValue, GoogleAuthProvider, type GoogleAuthProviderProps, GoogleSignInButton, type GoogleSignInButtonProps, type GoogleUser, useGoogleAuth };
|
|
@@ -1,38 +1,18 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
interface GsiClient {
|
|
17
|
-
accounts: {
|
|
18
|
-
id: {
|
|
19
|
-
initialize: (config: {
|
|
20
|
-
client_id: string;
|
|
21
|
-
callback: (res: GsiCredentialResponse) => void;
|
|
22
|
-
auto_select?: boolean;
|
|
23
|
-
cancel_on_tap_outside?: boolean;
|
|
24
|
-
}) => void;
|
|
25
|
-
prompt: () => void;
|
|
26
|
-
renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;
|
|
27
|
-
disableAutoSelect: () => void;
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
declare global {
|
|
32
|
-
interface Window {
|
|
33
|
-
google?: GsiClient;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
4
|
+
/**
|
|
5
|
+
* Client provider for **Google sign-in**, built on `@react-oauth/google` (the
|
|
6
|
+
* official React wrapper for Google Identity Services). It handles the GSI
|
|
7
|
+
* script + button rendering robustly — including remounts, so the button shows
|
|
8
|
+
* reliably every time a sign-in dialog opens. The resulting ID token is stashed
|
|
9
|
+
* in a cookie for the server `googleAuth` adapter to verify; admin status is
|
|
10
|
+
* optimistic on the client (via `adminEmails`), with the server gate
|
|
11
|
+
* authoritative.
|
|
12
|
+
*
|
|
13
|
+
* Render `<GoogleSignInButton />` anywhere inside the provider to show the
|
|
14
|
+
* official Google button.
|
|
15
|
+
*/
|
|
36
16
|
interface GoogleUser {
|
|
37
17
|
sub: string;
|
|
38
18
|
email?: string;
|
|
@@ -59,18 +39,22 @@ interface GoogleAuthContextValue {
|
|
|
59
39
|
isAdmin: boolean;
|
|
60
40
|
isEditing: boolean;
|
|
61
41
|
toggleEdit: () => void;
|
|
62
|
-
/** True once the GSI script has loaded and the client is initialized. */
|
|
63
|
-
ready: boolean;
|
|
64
|
-
/** True if GSI failed to load within the timeout (blocked/offline). */
|
|
65
|
-
failed: boolean;
|
|
66
|
-
/** Trigger Google One Tap / sign-in prompt. */
|
|
67
|
-
promptSignIn: () => void;
|
|
68
|
-
/** Render the official Google button into `el` (most reliable trigger). */
|
|
69
|
-
renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;
|
|
70
42
|
logout: () => void;
|
|
43
|
+
/** Feed a Google ID token credential (used by `GoogleSignInButton`). */
|
|
44
|
+
applyCredential: (credential: string) => void;
|
|
45
|
+
}
|
|
46
|
+
declare function GoogleAuthProvider({ children, clientId, ...rest }: GoogleAuthProviderProps): react.JSX.Element;
|
|
47
|
+
interface GoogleSignInButtonProps {
|
|
48
|
+
/** Forwarded to the underlying `<GoogleLogin>` (theme, shape, size, text…). */
|
|
49
|
+
theme?: "outline" | "filled_blue" | "filled_black";
|
|
50
|
+
shape?: "rectangular" | "pill" | "circle" | "square";
|
|
51
|
+
size?: "small" | "medium" | "large";
|
|
52
|
+
text?: "signin_with" | "signup_with" | "continue_with" | "signin";
|
|
53
|
+
onError?: () => void;
|
|
71
54
|
}
|
|
72
|
-
|
|
73
|
-
|
|
55
|
+
/** The official Google sign-in button. Must be rendered inside a provider. */
|
|
56
|
+
declare function GoogleSignInButton({ theme, shape, size, text, onError, }: GoogleSignInButtonProps): react.JSX.Element;
|
|
57
|
+
/** Google auth API (user/logout) for login pages and toolbars. */
|
|
74
58
|
declare function useGoogleAuth(): GoogleAuthContextValue;
|
|
75
59
|
|
|
76
|
-
export { type GoogleAuthContextValue, GoogleAuthProvider, type GoogleAuthProviderProps, type GoogleUser, useGoogleAuth };
|
|
60
|
+
export { type GoogleAuthContextValue, GoogleAuthProvider, type GoogleAuthProviderProps, GoogleSignInButton, type GoogleSignInButtonProps, type GoogleUser, useGoogleAuth };
|