@dalgoridim/headless-cms 0.3.1 → 0.4.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/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.d.cts +11 -11
- package/dist/client/index.d.ts +11 -11
- package/package.json +6 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
import { Auth, GoogleAuthProvider, User } from 'firebase/auth';
|
|
4
4
|
|
|
@@ -22,7 +22,7 @@ interface FirebaseAuthContextValue {
|
|
|
22
22
|
loginWithEmail: (email: string, password: string) => Promise<void>;
|
|
23
23
|
logout: () => Promise<void>;
|
|
24
24
|
}
|
|
25
|
-
declare function FirebaseAuthProvider({ children, auth, googleProvider, cookieName, onLogout, }: FirebaseAuthProviderProps):
|
|
25
|
+
declare function FirebaseAuthProvider({ children, auth, googleProvider, cookieName, onLogout, }: FirebaseAuthProviderProps): react.JSX.Element;
|
|
26
26
|
/** Extended Firebase auth API (login/logout/user) for login pages and toolbars. */
|
|
27
27
|
declare function useFirebaseAuth(): FirebaseAuthContextValue;
|
|
28
28
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
import { Auth, GoogleAuthProvider, User } from 'firebase/auth';
|
|
4
4
|
|
|
@@ -22,7 +22,7 @@ interface FirebaseAuthContextValue {
|
|
|
22
22
|
loginWithEmail: (email: string, password: string) => Promise<void>;
|
|
23
23
|
logout: () => Promise<void>;
|
|
24
24
|
}
|
|
25
|
-
declare function FirebaseAuthProvider({ children, auth, googleProvider, cookieName, onLogout, }: FirebaseAuthProviderProps):
|
|
25
|
+
declare function FirebaseAuthProvider({ children, auth, googleProvider, cookieName, onLogout, }: FirebaseAuthProviderProps): react.JSX.Element;
|
|
26
26
|
/** Extended Firebase auth API (login/logout/user) for login pages and toolbars. */
|
|
27
27
|
declare function useFirebaseAuth(): FirebaseAuthContextValue;
|
|
28
28
|
|
|
@@ -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":"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","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 };
|
|
@@ -1,4 +1,35 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defProps = Object.defineProperties;
|
|
4
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
5
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __spreadValues = (a, b) => {
|
|
10
|
+
for (var prop in b || (b = {}))
|
|
11
|
+
if (__hasOwnProp.call(b, prop))
|
|
12
|
+
__defNormalProp(a, prop, b[prop]);
|
|
13
|
+
if (__getOwnPropSymbols)
|
|
14
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
15
|
+
if (__propIsEnum.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
}
|
|
18
|
+
return a;
|
|
19
|
+
};
|
|
20
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
21
|
+
var __objRest = (source, exclude) => {
|
|
22
|
+
var target = {};
|
|
23
|
+
for (var prop in source)
|
|
24
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
25
|
+
target[prop] = source[prop];
|
|
26
|
+
if (source != null && __getOwnPropSymbols)
|
|
27
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
28
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
29
|
+
target[prop] = source[prop];
|
|
30
|
+
}
|
|
31
|
+
return target;
|
|
32
|
+
};
|
|
2
33
|
|
|
3
34
|
// src/auth/google/client/index.tsx
|
|
4
35
|
import {
|
|
@@ -9,9 +40,13 @@ import {
|
|
|
9
40
|
useRef,
|
|
10
41
|
useState
|
|
11
42
|
} from "react";
|
|
43
|
+
import {
|
|
44
|
+
GoogleOAuthProvider,
|
|
45
|
+
GoogleLogin,
|
|
46
|
+
googleLogout
|
|
47
|
+
} from "@react-oauth/google";
|
|
12
48
|
import { CmsAuthProvider } from "@dalgoridim/headless-cms/client";
|
|
13
49
|
import { jsx } from "react/jsx-runtime";
|
|
14
|
-
var GSI_SRC = "https://accounts.google.com/gsi/client";
|
|
15
50
|
var GoogleAuthContext = createContext(
|
|
16
51
|
void 0
|
|
17
52
|
);
|
|
@@ -32,15 +67,13 @@ function decodeJwt(token) {
|
|
|
32
67
|
try {
|
|
33
68
|
const payload = token.split(".")[1];
|
|
34
69
|
const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
35
|
-
|
|
36
|
-
return data;
|
|
70
|
+
return JSON.parse(json);
|
|
37
71
|
} catch (e) {
|
|
38
72
|
return null;
|
|
39
73
|
}
|
|
40
74
|
}
|
|
41
|
-
function
|
|
75
|
+
function AuthState({
|
|
42
76
|
children,
|
|
43
|
-
clientId,
|
|
44
77
|
adminEmails,
|
|
45
78
|
cookieName = "adminToken",
|
|
46
79
|
onLogout
|
|
@@ -48,12 +81,10 @@ function GoogleAuthProvider({
|
|
|
48
81
|
const [user, setUser] = useState(null);
|
|
49
82
|
const [isAdmin, setIsAdmin] = useState(false);
|
|
50
83
|
const [isEditing, setIsEditing] = useState(false);
|
|
51
|
-
const [ready, setReady] = useState(false);
|
|
52
|
-
const [failed, setFailed] = useState(false);
|
|
53
84
|
const allowed = useRef(
|
|
54
85
|
(adminEmails != null ? adminEmails : []).map((e) => e.trim().toLowerCase())
|
|
55
86
|
);
|
|
56
|
-
const
|
|
87
|
+
const applyCredential = useCallback(
|
|
57
88
|
(token) => {
|
|
58
89
|
var _a;
|
|
59
90
|
const claims = decodeJwt(token);
|
|
@@ -77,44 +108,16 @@ function GoogleAuthProvider({
|
|
|
77
108
|
);
|
|
78
109
|
useEffect(() => {
|
|
79
110
|
const existing = readCookie(cookieName);
|
|
80
|
-
if (existing)
|
|
81
|
-
}, [cookieName,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
auto_select: false,
|
|
91
|
-
cancel_on_tap_outside: true
|
|
92
|
-
});
|
|
93
|
-
setReady(true);
|
|
94
|
-
return true;
|
|
95
|
-
}
|
|
96
|
-
if (tryInit()) return;
|
|
97
|
-
if (!document.querySelector(`script[src="${GSI_SRC}"]`)) {
|
|
98
|
-
const script = document.createElement("script");
|
|
99
|
-
script.src = GSI_SRC;
|
|
100
|
-
script.async = true;
|
|
101
|
-
script.defer = true;
|
|
102
|
-
document.head.appendChild(script);
|
|
103
|
-
}
|
|
104
|
-
const startedAt = Date.now();
|
|
105
|
-
const timer = setInterval(() => {
|
|
106
|
-
if (tryInit()) {
|
|
107
|
-
clearInterval(timer);
|
|
108
|
-
} else if (Date.now() - startedAt > 1e4) {
|
|
109
|
-
clearInterval(timer);
|
|
110
|
-
if (!cancelled) setFailed(true);
|
|
111
|
-
}
|
|
112
|
-
}, 120);
|
|
113
|
-
return () => {
|
|
114
|
-
cancelled = true;
|
|
115
|
-
clearInterval(timer);
|
|
116
|
-
};
|
|
117
|
-
}, [clientId, applyToken]);
|
|
111
|
+
if (existing) applyCredential(existing);
|
|
112
|
+
}, [cookieName, applyCredential]);
|
|
113
|
+
const logout = useCallback(() => {
|
|
114
|
+
googleLogout();
|
|
115
|
+
deleteCookie(cookieName);
|
|
116
|
+
setUser(null);
|
|
117
|
+
setIsAdmin(false);
|
|
118
|
+
setIsEditing(false);
|
|
119
|
+
onLogout == null ? void 0 : onLogout();
|
|
120
|
+
}, [cookieName, onLogout]);
|
|
118
121
|
useEffect(() => {
|
|
119
122
|
const originalFetch = window.fetch;
|
|
120
123
|
window.fetch = async (...args) => {
|
|
@@ -122,7 +125,7 @@ function GoogleAuthProvider({
|
|
|
122
125
|
if (response.status === 401) {
|
|
123
126
|
try {
|
|
124
127
|
const data = await response.clone().json();
|
|
125
|
-
if (data == null ? void 0 : data.logout)
|
|
128
|
+
if (data == null ? void 0 : data.logout) logout();
|
|
126
129
|
} catch (e) {
|
|
127
130
|
}
|
|
128
131
|
}
|
|
@@ -131,48 +134,48 @@ function GoogleAuthProvider({
|
|
|
131
134
|
return () => {
|
|
132
135
|
window.fetch = originalFetch;
|
|
133
136
|
};
|
|
134
|
-
}, []);
|
|
135
|
-
function doLogout() {
|
|
136
|
-
var _a;
|
|
137
|
-
(_a = window.google) == null ? void 0 : _a.accounts.id.disableAutoSelect();
|
|
138
|
-
deleteCookie(cookieName);
|
|
139
|
-
setUser(null);
|
|
140
|
-
setIsAdmin(false);
|
|
141
|
-
setIsEditing(false);
|
|
142
|
-
onLogout == null ? void 0 : onLogout();
|
|
143
|
-
}
|
|
144
|
-
const promptSignIn = useCallback(() => {
|
|
145
|
-
var _a;
|
|
146
|
-
if (!ready) return;
|
|
147
|
-
(_a = window.google) == null ? void 0 : _a.accounts.id.prompt();
|
|
148
|
-
}, [ready]);
|
|
149
|
-
const renderButton = useCallback(
|
|
150
|
-
(el, options) => {
|
|
151
|
-
var _a;
|
|
152
|
-
if (!ready) return;
|
|
153
|
-
(_a = window.google) == null ? void 0 : _a.accounts.id.renderButton(el, options);
|
|
154
|
-
},
|
|
155
|
-
[ready]
|
|
156
|
-
);
|
|
137
|
+
}, [logout]);
|
|
157
138
|
const toggleEdit = useCallback(() => setIsEditing((p) => !p), []);
|
|
158
139
|
return /* @__PURE__ */ jsx(
|
|
159
140
|
GoogleAuthContext.Provider,
|
|
160
141
|
{
|
|
161
|
-
value: {
|
|
162
|
-
user,
|
|
163
|
-
isAdmin,
|
|
164
|
-
isEditing,
|
|
165
|
-
toggleEdit,
|
|
166
|
-
ready,
|
|
167
|
-
failed,
|
|
168
|
-
promptSignIn,
|
|
169
|
-
renderButton,
|
|
170
|
-
logout: doLogout
|
|
171
|
-
},
|
|
142
|
+
value: { user, isAdmin, isEditing, toggleEdit, logout, applyCredential },
|
|
172
143
|
children: /* @__PURE__ */ jsx(CmsAuthProvider, { value: { isAdmin, isEditing, toggleEdit }, children })
|
|
173
144
|
}
|
|
174
145
|
);
|
|
175
146
|
}
|
|
147
|
+
function GoogleAuthProvider(_a) {
|
|
148
|
+
var _b = _a, {
|
|
149
|
+
children,
|
|
150
|
+
clientId
|
|
151
|
+
} = _b, rest = __objRest(_b, [
|
|
152
|
+
"children",
|
|
153
|
+
"clientId"
|
|
154
|
+
]);
|
|
155
|
+
return /* @__PURE__ */ jsx(GoogleOAuthProvider, { clientId, children: /* @__PURE__ */ jsx(AuthState, __spreadProps(__spreadValues({}, rest), { children })) });
|
|
156
|
+
}
|
|
157
|
+
function GoogleSignInButton({
|
|
158
|
+
theme = "filled_black",
|
|
159
|
+
shape = "pill",
|
|
160
|
+
size = "large",
|
|
161
|
+
text = "continue_with",
|
|
162
|
+
onError
|
|
163
|
+
}) {
|
|
164
|
+
const { applyCredential } = useGoogleAuth();
|
|
165
|
+
return /* @__PURE__ */ jsx(
|
|
166
|
+
GoogleLogin,
|
|
167
|
+
{
|
|
168
|
+
onSuccess: (res) => {
|
|
169
|
+
if (res.credential) applyCredential(res.credential);
|
|
170
|
+
},
|
|
171
|
+
onError,
|
|
172
|
+
theme,
|
|
173
|
+
shape,
|
|
174
|
+
size,
|
|
175
|
+
text
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
}
|
|
176
179
|
function useGoogleAuth() {
|
|
177
180
|
const ctx = useContext(GoogleAuthContext);
|
|
178
181
|
if (!ctx) {
|
|
@@ -182,6 +185,7 @@ function useGoogleAuth() {
|
|
|
182
185
|
}
|
|
183
186
|
export {
|
|
184
187
|
GoogleAuthProvider,
|
|
188
|
+
GoogleSignInButton,
|
|
185
189
|
useGoogleAuth
|
|
186
190
|
};
|
|
187
191
|
//# sourceMappingURL=index.js.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":"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","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":[]}
|
package/dist/client/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import react__default, { ReactNode } from 'react';
|
|
3
3
|
import { NestedSections, ClientStorageAdapter, PendingImage, Section, CmsAuthState } from '../index.cjs';
|
|
4
4
|
export { AuthAdapter, AuthIdentity, DataAdapter, Editable, EntityAddress, Query, QueryCondition, QueryFilter, QueryFilterGroup, QueryFilterOp, Ref, RelationConfig, SectionMap, ServerStorageAdapter, StorageAdapter } from '../index.cjs';
|
|
5
5
|
|
|
@@ -37,7 +37,7 @@ interface PageProviderProps {
|
|
|
37
37
|
/** Notification sink. Defaults to `sonner` toasts. */
|
|
38
38
|
notify?: Notifier;
|
|
39
39
|
}
|
|
40
|
-
declare const PageProvider: ({ children, initialSections, apiBasePath, storage, notify, }: PageProviderProps) =>
|
|
40
|
+
declare const PageProvider: ({ children, initialSections, apiBasePath, storage, notify, }: PageProviderProps) => react.JSX.Element;
|
|
41
41
|
declare const usePageContext: () => PageContextType;
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -49,7 +49,7 @@ declare const usePageContext: () => PageContextType;
|
|
|
49
49
|
* this context through the package's public `…/client` specifier so they share
|
|
50
50
|
* the *same* context instance as the consumer's primitives at runtime.
|
|
51
51
|
*/
|
|
52
|
-
declare const CmsAuthContext:
|
|
52
|
+
declare const CmsAuthContext: react.Context<CmsAuthState | undefined>;
|
|
53
53
|
declare function useCmsAuth(): CmsAuthState;
|
|
54
54
|
/**
|
|
55
55
|
* Controlled provider for consumers wiring their own auth. Pass the resolved
|
|
@@ -58,7 +58,7 @@ declare function useCmsAuth(): CmsAuthState;
|
|
|
58
58
|
declare function CmsAuthProvider({ value, children, }: {
|
|
59
59
|
value: CmsAuthState;
|
|
60
60
|
children: ReactNode;
|
|
61
|
-
}):
|
|
61
|
+
}): react.JSX.Element;
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* Inline-editable text primitive. Headless: it wires `contentEditable`, reads
|
|
@@ -75,13 +75,13 @@ interface ContentEditSpanProps {
|
|
|
75
75
|
sectionKey: string;
|
|
76
76
|
fieldKey: string;
|
|
77
77
|
className?: string;
|
|
78
|
-
children?:
|
|
78
|
+
children?: react__default.ReactNode;
|
|
79
79
|
/** Element/tag to render. Defaults to `span`. */
|
|
80
|
-
as?:
|
|
80
|
+
as?: react__default.ElementType;
|
|
81
81
|
/** Render the stored raw string into nodes. Defaults to plain text. */
|
|
82
|
-
renderValue?: (raw: string) =>
|
|
82
|
+
renderValue?: (raw: string) => react__default.ReactNode;
|
|
83
83
|
}
|
|
84
|
-
declare function ContentEditSpan({ collection, sectionKey, fieldKey, className, children, as, renderValue, }: ContentEditSpanProps):
|
|
84
|
+
declare function ContentEditSpan({ collection, sectionKey, fieldKey, className, children, as, renderValue, }: ContentEditSpanProps): react__default.JSX.Element;
|
|
85
85
|
|
|
86
86
|
/** State + actions handed to the {@link EditableImage} render-prop. */
|
|
87
87
|
interface EditableImageRenderState {
|
|
@@ -116,9 +116,9 @@ interface EditableImageProps {
|
|
|
116
116
|
* package ships no visual look — bring your own. When omitted, a bare
|
|
117
117
|
* unstyled `<img>` is rendered.
|
|
118
118
|
*/
|
|
119
|
-
children?: (state: EditableImageRenderState) =>
|
|
119
|
+
children?: (state: EditableImageRenderState) => react__default.ReactNode;
|
|
120
120
|
}
|
|
121
|
-
declare function EditableImage({ sectionKey, fieldKey, src, collection, docId, className, children, }: EditableImageProps):
|
|
121
|
+
declare function EditableImage({ sectionKey, fieldKey, src, collection, docId, className, children, }: EditableImageProps): react__default.JSX.Element;
|
|
122
122
|
|
|
123
123
|
interface UseMarkdownEditorOptions {
|
|
124
124
|
initialValue: string;
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import react__default, { ReactNode } from 'react';
|
|
3
3
|
import { NestedSections, ClientStorageAdapter, PendingImage, Section, CmsAuthState } from '../index.js';
|
|
4
4
|
export { AuthAdapter, AuthIdentity, DataAdapter, Editable, EntityAddress, Query, QueryCondition, QueryFilter, QueryFilterGroup, QueryFilterOp, Ref, RelationConfig, SectionMap, ServerStorageAdapter, StorageAdapter } from '../index.js';
|
|
5
5
|
|
|
@@ -37,7 +37,7 @@ interface PageProviderProps {
|
|
|
37
37
|
/** Notification sink. Defaults to `sonner` toasts. */
|
|
38
38
|
notify?: Notifier;
|
|
39
39
|
}
|
|
40
|
-
declare const PageProvider: ({ children, initialSections, apiBasePath, storage, notify, }: PageProviderProps) =>
|
|
40
|
+
declare const PageProvider: ({ children, initialSections, apiBasePath, storage, notify, }: PageProviderProps) => react.JSX.Element;
|
|
41
41
|
declare const usePageContext: () => PageContextType;
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -49,7 +49,7 @@ declare const usePageContext: () => PageContextType;
|
|
|
49
49
|
* this context through the package's public `…/client` specifier so they share
|
|
50
50
|
* the *same* context instance as the consumer's primitives at runtime.
|
|
51
51
|
*/
|
|
52
|
-
declare const CmsAuthContext:
|
|
52
|
+
declare const CmsAuthContext: react.Context<CmsAuthState | undefined>;
|
|
53
53
|
declare function useCmsAuth(): CmsAuthState;
|
|
54
54
|
/**
|
|
55
55
|
* Controlled provider for consumers wiring their own auth. Pass the resolved
|
|
@@ -58,7 +58,7 @@ declare function useCmsAuth(): CmsAuthState;
|
|
|
58
58
|
declare function CmsAuthProvider({ value, children, }: {
|
|
59
59
|
value: CmsAuthState;
|
|
60
60
|
children: ReactNode;
|
|
61
|
-
}):
|
|
61
|
+
}): react.JSX.Element;
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* Inline-editable text primitive. Headless: it wires `contentEditable`, reads
|
|
@@ -75,13 +75,13 @@ interface ContentEditSpanProps {
|
|
|
75
75
|
sectionKey: string;
|
|
76
76
|
fieldKey: string;
|
|
77
77
|
className?: string;
|
|
78
|
-
children?:
|
|
78
|
+
children?: react__default.ReactNode;
|
|
79
79
|
/** Element/tag to render. Defaults to `span`. */
|
|
80
|
-
as?:
|
|
80
|
+
as?: react__default.ElementType;
|
|
81
81
|
/** Render the stored raw string into nodes. Defaults to plain text. */
|
|
82
|
-
renderValue?: (raw: string) =>
|
|
82
|
+
renderValue?: (raw: string) => react__default.ReactNode;
|
|
83
83
|
}
|
|
84
|
-
declare function ContentEditSpan({ collection, sectionKey, fieldKey, className, children, as, renderValue, }: ContentEditSpanProps):
|
|
84
|
+
declare function ContentEditSpan({ collection, sectionKey, fieldKey, className, children, as, renderValue, }: ContentEditSpanProps): react__default.JSX.Element;
|
|
85
85
|
|
|
86
86
|
/** State + actions handed to the {@link EditableImage} render-prop. */
|
|
87
87
|
interface EditableImageRenderState {
|
|
@@ -116,9 +116,9 @@ interface EditableImageProps {
|
|
|
116
116
|
* package ships no visual look — bring your own. When omitted, a bare
|
|
117
117
|
* unstyled `<img>` is rendered.
|
|
118
118
|
*/
|
|
119
|
-
children?: (state: EditableImageRenderState) =>
|
|
119
|
+
children?: (state: EditableImageRenderState) => react__default.ReactNode;
|
|
120
120
|
}
|
|
121
|
-
declare function EditableImage({ sectionKey, fieldKey, src, collection, docId, className, children, }: EditableImageProps):
|
|
121
|
+
declare function EditableImage({ sectionKey, fieldKey, src, collection, docId, className, children, }: EditableImageProps): react__default.JSX.Element;
|
|
122
122
|
|
|
123
123
|
interface UseMarkdownEditorOptions {
|
|
124
124
|
initialValue: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dalgoridim/headless-cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Database-agnostic, inline-edit headless CMS engine for React / Next.js apps",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "dalgoridim",
|
|
@@ -119,6 +119,7 @@
|
|
|
119
119
|
"peerDependencies": {
|
|
120
120
|
"@aws-sdk/client-s3": ">=3",
|
|
121
121
|
"@aws-sdk/s3-request-presigner": ">=3",
|
|
122
|
+
"@react-oauth/google": ">=0.12",
|
|
122
123
|
"cloudinary": ">=2",
|
|
123
124
|
"firebase": ">=10",
|
|
124
125
|
"firebase-admin": ">=12",
|
|
@@ -131,6 +132,9 @@
|
|
|
131
132
|
"@aws-sdk/client-s3": {
|
|
132
133
|
"optional": true
|
|
133
134
|
},
|
|
135
|
+
"@react-oauth/google": {
|
|
136
|
+
"optional": true
|
|
137
|
+
},
|
|
134
138
|
"@aws-sdk/s3-request-presigner": {
|
|
135
139
|
"optional": true
|
|
136
140
|
},
|
|
@@ -150,10 +154,10 @@
|
|
|
150
154
|
"optional": true
|
|
151
155
|
}
|
|
152
156
|
},
|
|
153
|
-
"dependencies": {},
|
|
154
157
|
"devDependencies": {
|
|
155
158
|
"@aws-sdk/client-s3": "^3.700.0",
|
|
156
159
|
"@aws-sdk/s3-request-presigner": "^3.700.0",
|
|
160
|
+
"@react-oauth/google": "^0.13.5",
|
|
157
161
|
"@types/node": "^20.0.0",
|
|
158
162
|
"@types/pg": "^8.11.10",
|
|
159
163
|
"@types/react": "^19.0.0",
|