@ph-cms/client-sdk 0.1.4 → 0.1.6

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.
@@ -14,6 +14,9 @@ class FirebaseAuthProvider {
14
14
  this.refreshToken = localStorage.getItem(`${this.storageKeyPrefix}refresh_token`);
15
15
  }
16
16
  }
17
+ hasToken() {
18
+ return this.accessToken !== null || this.refreshToken !== null;
19
+ }
17
20
  setTokens(accessToken, refreshToken) {
18
21
  this.accessToken = accessToken;
19
22
  this.refreshToken = refreshToken;
@@ -5,6 +5,12 @@ export interface AuthProvider {
5
5
  * Should handle refreshing if necessary (or return null/throw if expired and can't refresh).
6
6
  */
7
7
  getToken(): Promise<string | null>;
8
+ /**
9
+ * Returns true if the provider currently holds a token (access or refresh).
10
+ * This is a synchronous check used to decide whether to attempt API calls
11
+ * that require authentication (e.g. /auth/me).
12
+ */
13
+ hasToken(): boolean;
8
14
  /**
9
15
  * Sets a callback to be called when the token is known to be expired by the external world (e.g. 401 response).
10
16
  */
@@ -11,6 +11,7 @@ export declare class LocalAuthProvider implements AuthProvider {
11
11
  refreshToken: string;
12
12
  }>) | undefined);
13
13
  setTokens(accessToken: string, refreshToken: string): void;
14
+ hasToken(): boolean;
14
15
  getToken(): Promise<string | null>;
15
16
  onTokenExpired(callback: () => Promise<void>): void;
16
17
  logout(): Promise<void>;
@@ -22,6 +22,9 @@ class LocalAuthProvider {
22
22
  localStorage.setItem(`${this.storageKeyPrefix}refresh_token`, refreshToken);
23
23
  }
24
24
  }
25
+ hasToken() {
26
+ return this.accessToken !== null || this.refreshToken !== null;
27
+ }
25
28
  async getToken() {
26
29
  // Ideally check expiration here using jwt-decode, but for now return what we have.
27
30
  // If it's expired, the API will return 401, triggering the interceptor which calls onTokenExpired.
package/dist/client.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { AxiosInstance } from 'axios';
2
2
  import { AuthProvider } from './auth/interfaces';
3
3
  import { AuthModule } from './modules/auth';
4
- import { ContentModule } from './modules/content';
5
4
  import { ChannelModule } from './modules/channel';
6
- import { TermsModule } from './modules/terms';
5
+ import { ContentModule } from './modules/content';
7
6
  import { MediaModule } from './modules/media';
7
+ import { TermsModule } from './modules/terms';
8
8
  export interface PHCMSClientConfig {
9
9
  baseURL: string;
10
10
  apiPrefix?: string;
@@ -19,5 +19,7 @@ export declare class PHCMSClient {
19
19
  readonly channel: ChannelModule;
20
20
  readonly terms: TermsModule;
21
21
  readonly media: MediaModule;
22
+ /** Exposes the auth provider so UI layers can check token state synchronously. */
23
+ get authProvider(): AuthProvider | undefined;
22
24
  constructor(config: PHCMSClientConfig);
23
25
  }
package/dist/client.js CHANGED
@@ -7,11 +7,15 @@ exports.PHCMSClient = void 0;
7
7
  const axios_1 = __importDefault(require("axios"));
8
8
  const errors_1 = require("./errors");
9
9
  const auth_1 = require("./modules/auth");
10
- const content_1 = require("./modules/content");
11
10
  const channel_1 = require("./modules/channel");
12
- const terms_1 = require("./modules/terms");
11
+ const content_1 = require("./modules/content");
13
12
  const media_1 = require("./modules/media");
13
+ const terms_1 = require("./modules/terms");
14
14
  class PHCMSClient {
15
+ /** Exposes the auth provider so UI layers can check token state synchronously. */
16
+ get authProvider() {
17
+ return this.config.auth;
18
+ }
15
19
  constructor(config) {
16
20
  this.config = config;
17
21
  const normalizedApiPrefix = `/${(config.apiPrefix || '/api').replace(/^\/+|\/+$/g, '')}`;
package/dist/context.d.ts CHANGED
@@ -1,12 +1,14 @@
1
- import React, { ReactNode } from 'react';
1
+ import { UserDto } from '@ph-cms/api-contract';
2
2
  import { QueryClient } from '@tanstack/react-query';
3
+ import React, { ReactNode } from 'react';
3
4
  import { PHCMSClient } from './client';
4
- import { UserDto } from '@ph-cms/api-contract';
5
5
  export interface PHCMSContextType {
6
6
  client: PHCMSClient;
7
7
  user: UserDto | null;
8
8
  isAuthenticated: boolean;
9
9
  isLoading: boolean;
10
+ /** Manually trigger a refetch of the current user profile. */
11
+ refreshUser: () => Promise<void>;
10
12
  }
11
13
  export interface PHCMSProviderProps {
12
14
  client: PHCMSClient;
package/dist/context.js CHANGED
@@ -34,40 +34,71 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.usePHCMS = exports.usePHCMSContext = exports.PHCMSProvider = void 0;
37
- const react_1 = __importStar(require("react"));
38
37
  const react_query_1 = require("@tanstack/react-query");
38
+ const react_1 = __importStar(require("react"));
39
39
  const PHCMSContext = (0, react_1.createContext)(null);
40
+ const AUTH_ME_QUERY_KEY = ['auth', 'me'];
40
41
  /**
41
42
  * Internal component to handle auth state and provide to context
42
43
  */
43
44
  const PHCMSInternalProvider = ({ client, children }) => {
44
- const { data: user, isLoading, isError } = (0, react_query_1.useQuery)({
45
- queryKey: ['auth', 'me'],
45
+ const queryClient = (0, react_query_1.useQueryClient)();
46
+ // Track token presence in state to trigger re-renders when it changes.
47
+ // Initial value from provider.
48
+ const [hasToken, setHasToken] = (0, react_1.useState)(() => client.authProvider?.hasToken() ?? false);
49
+ // The 'me' query is only enabled if we have a token.
50
+ const { data: user, isLoading, isError, refetch } = (0, react_query_1.useQuery)({
51
+ queryKey: [...AUTH_ME_QUERY_KEY],
46
52
  queryFn: () => client.auth.me(),
53
+ enabled: hasToken,
47
54
  retry: false,
48
55
  staleTime: 1000 * 60 * 5,
49
56
  });
57
+ // Function to update token state and optionally refetch
58
+ const refreshUser = (0, react_1.useCallback)(async () => {
59
+ const currentHasToken = client.authProvider?.hasToken() ?? false;
60
+ setHasToken(currentHasToken);
61
+ if (currentHasToken) {
62
+ await refetch();
63
+ }
64
+ else {
65
+ // If no token, clear the query data
66
+ queryClient.setQueryData([...AUTH_ME_QUERY_KEY], null);
67
+ }
68
+ }, [client.authProvider, refetch, queryClient]);
69
+ // Effect to sync state if provider changes externally (rare but possible)
70
+ (0, react_1.useEffect)(() => {
71
+ const interval = setInterval(() => {
72
+ const currentHasToken = client.authProvider?.hasToken() ?? false;
73
+ if (currentHasToken !== hasToken) {
74
+ setHasToken(currentHasToken);
75
+ }
76
+ }, 2000); // Check every 2s as a fallback
77
+ return () => clearInterval(interval);
78
+ }, [client.authProvider, hasToken]);
50
79
  const value = (0, react_1.useMemo)(() => ({
51
80
  client,
52
81
  user: user || null,
53
- isAuthenticated: !!user && !isError,
54
- isLoading,
55
- }), [client, user, isError, isLoading]);
56
- return (react_1.default.createElement(PHCMSContext.Provider, { value: value }, children));
82
+ isAuthenticated: !!user && !isError && hasToken,
83
+ isLoading: hasToken ? isLoading : false,
84
+ refreshUser,
85
+ }), [client, user, isError, isLoading, hasToken, refreshUser]);
86
+ return react_1.default.createElement(PHCMSContext.Provider, { value: value }, children);
57
87
  };
58
88
  /**
59
89
  * Root Provider for PH-CMS
60
90
  * Automatically includes QueryClientProvider
61
91
  */
62
92
  const PHCMSProvider = ({ client, queryClient, children }) => {
63
- const internalQueryClient = (0, react_1.useMemo)(() => queryClient ?? new react_query_1.QueryClient({
64
- defaultOptions: {
65
- queries: {
66
- refetchOnWindowFocus: false,
67
- retry: false,
93
+ const internalQueryClient = (0, react_1.useMemo)(() => queryClient ??
94
+ new react_query_1.QueryClient({
95
+ defaultOptions: {
96
+ queries: {
97
+ refetchOnWindowFocus: false,
98
+ retry: false,
99
+ },
68
100
  },
69
- },
70
- }), [queryClient]);
101
+ }), [queryClient]);
71
102
  return (react_1.default.createElement(react_query_1.QueryClientProvider, { client: internalQueryClient },
72
103
  react_1.default.createElement(PHCMSInternalProvider, { client: client }, children)));
73
104
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Unified Auth Hook
3
- * Returns both the current auth status and the actions
3
+ * Returns both the current auth status and the actions.
4
4
  */
5
5
  export declare const useAuth: () => {
6
6
  user: {
@@ -128,6 +128,30 @@ export declare const useAuth: () => {
128
128
  email: string;
129
129
  password: string;
130
130
  }, unknown>;
131
+ loginWithFirebaseStatus: import("@tanstack/react-query").UseMutationResult<{
132
+ refreshToken: string;
133
+ user: {
134
+ uid: string;
135
+ email: string;
136
+ username: string | null;
137
+ display_name: string;
138
+ avatar_url: string | null;
139
+ phone_number: string | null;
140
+ email_verified_at: string | null;
141
+ phone_verified_at: string | null;
142
+ locale: string;
143
+ timezone: string;
144
+ status: string;
145
+ role: string[];
146
+ profile_data: Record<string, any>;
147
+ last_login_at: string | null;
148
+ created_at: string;
149
+ updated_at: string;
150
+ };
151
+ accessToken: string;
152
+ }, Error, {
153
+ idToken: string;
154
+ }, unknown>;
131
155
  registerStatus: import("@tanstack/react-query").UseMutationResult<{
132
156
  refreshToken: string;
133
157
  user: {
@@ -3,36 +3,37 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useLogout = exports.useLogin = exports.useUser = exports.useAuth = void 0;
4
4
  const react_query_1 = require("@tanstack/react-query");
5
5
  const context_1 = require("../context");
6
+ const AUTH_ME_QUERY_KEY = ['auth', 'me'];
6
7
  /**
7
8
  * Unified Auth Hook
8
- * Returns both the current auth status and the actions
9
+ * Returns both the current auth status and the actions.
9
10
  */
10
11
  const useAuth = () => {
11
- const { user, isAuthenticated, isLoading } = (0, context_1.usePHCMSContext)();
12
+ const { user, isAuthenticated, isLoading, refreshUser } = (0, context_1.usePHCMSContext)();
12
13
  const client = (0, context_1.usePHCMS)();
13
14
  const queryClient = (0, react_query_1.useQueryClient)();
14
15
  const loginMutation = (0, react_query_1.useMutation)({
15
16
  mutationFn: (data) => client.auth.login(data),
16
- onSuccess: () => {
17
- queryClient.invalidateQueries({ queryKey: ['auth', 'me'] });
17
+ onSuccess: async () => {
18
+ await refreshUser();
18
19
  },
19
20
  });
20
21
  const loginWithFirebaseMutation = (0, react_query_1.useMutation)({
21
22
  mutationFn: (data) => client.auth.loginWithFirebase(data),
22
- onSuccess: () => {
23
- queryClient.invalidateQueries({ queryKey: ['auth', 'me'] });
23
+ onSuccess: async () => {
24
+ await refreshUser();
24
25
  },
25
26
  });
26
27
  const registerMutation = (0, react_query_1.useMutation)({
27
28
  mutationFn: (data) => client.auth.register(data),
28
- onSuccess: () => {
29
- queryClient.invalidateQueries({ queryKey: ['auth', 'me'] });
29
+ onSuccess: async () => {
30
+ await refreshUser();
30
31
  },
31
32
  });
32
33
  const logoutMutation = (0, react_query_1.useMutation)({
33
34
  mutationFn: () => client.auth.logout(),
34
- onSuccess: () => {
35
- queryClient.setQueryData(['auth', 'me'], null);
35
+ onSuccess: async () => {
36
+ await refreshUser(); // This will clear state since hasToken will be false
36
37
  queryClient.invalidateQueries();
37
38
  },
38
39
  });
@@ -46,6 +47,7 @@ const useAuth = () => {
46
47
  logout: logoutMutation.mutateAsync,
47
48
  // Access to mutation objects for loading/error states if needed
48
49
  loginStatus: loginMutation,
50
+ loginWithFirebaseStatus: loginWithFirebaseMutation,
49
51
  registerStatus: registerMutation,
50
52
  logoutStatus: logoutMutation,
51
53
  };
@@ -0,0 +1,108 @@
1
+ import type { Auth } from 'firebase/auth';
2
+ import React from 'react';
3
+ export interface UseFirebaseAuthSyncOptions {
4
+ /**
5
+ * Firebase Auth instance.
6
+ * Must be provided so the hook can subscribe to `onAuthStateChanged`.
7
+ */
8
+ firebaseAuth: Auth;
9
+ /**
10
+ * If true, automatically call `logout()` on the PH-CMS side
11
+ * when the Firebase user signs out.
12
+ * @default true
13
+ */
14
+ logoutOnFirebaseSignOut?: boolean;
15
+ /**
16
+ * Called when the sync exchange succeeds.
17
+ */
18
+ onSyncSuccess?: () => void;
19
+ /**
20
+ * Called when the sync exchange fails.
21
+ */
22
+ onSyncError?: (error: unknown) => void;
23
+ }
24
+ export interface UseFirebaseAuthSyncReturn {
25
+ /** Whether a token-exchange request is currently in flight. */
26
+ isSyncing: boolean;
27
+ }
28
+ /**
29
+ * `useFirebaseAuthSync`
30
+ *
31
+ * Subscribes to Firebase `onAuthStateChanged` and automatically synchronizes
32
+ * the Firebase authentication state with the PH-CMS backend:
33
+ *
34
+ * - **Firebase user signs in** and PH-CMS is not yet authenticated →
35
+ * obtains a Firebase ID token and calls `loginWithFirebase({ idToken })`
36
+ * which exchanges the token for PH-CMS access/refresh tokens and then
37
+ * fetches the user profile via `/auth/me`.
38
+ *
39
+ * - **Firebase user signs out** and PH-CMS is still authenticated →
40
+ * calls `logout()` on PH-CMS (configurable via `logoutOnFirebaseSignOut`).
41
+ *
42
+ * The hook must be rendered **inside** `<PHCMSProvider>` because it relies
43
+ * on context values (`isAuthenticated`, `isLoading`) and the `loginWithFirebase`
44
+ * / `logout` actions exposed by the auth module.
45
+ *
46
+ * ### Example
47
+ *
48
+ * ```tsx
49
+ * import { useFirebaseAuthSync } from '@ph-cms/client-sdk';
50
+ * import { getAuth } from 'firebase/auth';
51
+ *
52
+ * const firebaseAuth = getAuth(firebaseApp);
53
+ *
54
+ * function App() {
55
+ * useFirebaseAuthSync({ firebaseAuth });
56
+ * return <MainContent />;
57
+ * }
58
+ * ```
59
+ */
60
+ export declare const useFirebaseAuthSync: (options: UseFirebaseAuthSyncOptions) => UseFirebaseAuthSyncReturn;
61
+ export interface FirebaseAuthSyncProps {
62
+ /**
63
+ * Firebase Auth instance.
64
+ */
65
+ firebaseAuth: Auth;
66
+ /**
67
+ * If true, automatically call `logout()` on the PH-CMS side
68
+ * when the Firebase user signs out.
69
+ * @default true
70
+ */
71
+ logoutOnFirebaseSignOut?: boolean;
72
+ /**
73
+ * Called when the sync exchange succeeds.
74
+ */
75
+ onSyncSuccess?: () => void;
76
+ /**
77
+ * Called when the sync exchange fails.
78
+ */
79
+ onSyncError?: (error: unknown) => void;
80
+ children: React.ReactNode;
81
+ }
82
+ /**
83
+ * `<FirebaseAuthSync>`
84
+ *
85
+ * A convenience wrapper component that calls `useFirebaseAuthSync` internally.
86
+ * Place it **inside** `<PHCMSProvider>` and wrap the part of the tree that
87
+ * should wait for or react to Firebase↔PH-CMS auth synchronization.
88
+ *
89
+ * ### Example
90
+ *
91
+ * ```tsx
92
+ * import { PHCMSProvider, FirebaseAuthSync } from '@ph-cms/client-sdk';
93
+ * import { getAuth } from 'firebase/auth';
94
+ *
95
+ * const firebaseAuth = getAuth(firebaseApp);
96
+ *
97
+ * function App() {
98
+ * return (
99
+ * <PHCMSProvider client={client}>
100
+ * <FirebaseAuthSync firebaseAuth={firebaseAuth}>
101
+ * <MainContent />
102
+ * </FirebaseAuthSync>
103
+ * </PHCMSProvider>
104
+ * );
105
+ * }
106
+ * ```
107
+ */
108
+ export declare const FirebaseAuthSync: React.FC<FirebaseAuthSyncProps>;
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FirebaseAuthSync = exports.useFirebaseAuthSync = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const context_1 = require("../context");
39
+ /**
40
+ * `useFirebaseAuthSync`
41
+ *
42
+ * Subscribes to Firebase `onAuthStateChanged` and automatically synchronizes
43
+ * the Firebase authentication state with the PH-CMS backend:
44
+ *
45
+ * - **Firebase user signs in** and PH-CMS is not yet authenticated →
46
+ * obtains a Firebase ID token and calls `loginWithFirebase({ idToken })`
47
+ * which exchanges the token for PH-CMS access/refresh tokens and then
48
+ * fetches the user profile via `/auth/me`.
49
+ *
50
+ * - **Firebase user signs out** and PH-CMS is still authenticated →
51
+ * calls `logout()` on PH-CMS (configurable via `logoutOnFirebaseSignOut`).
52
+ *
53
+ * The hook must be rendered **inside** `<PHCMSProvider>` because it relies
54
+ * on context values (`isAuthenticated`, `isLoading`) and the `loginWithFirebase`
55
+ * / `logout` actions exposed by the auth module.
56
+ *
57
+ * ### Example
58
+ *
59
+ * ```tsx
60
+ * import { useFirebaseAuthSync } from '@ph-cms/client-sdk';
61
+ * import { getAuth } from 'firebase/auth';
62
+ *
63
+ * const firebaseAuth = getAuth(firebaseApp);
64
+ *
65
+ * function App() {
66
+ * useFirebaseAuthSync({ firebaseAuth });
67
+ * return <MainContent />;
68
+ * }
69
+ * ```
70
+ */
71
+ const useFirebaseAuthSync = (options) => {
72
+ const { firebaseAuth, logoutOnFirebaseSignOut = true, onSyncSuccess, onSyncError, } = options;
73
+ const { client, isAuthenticated, isLoading, refreshUser } = (0, context_1.usePHCMSContext)();
74
+ // Use refs for values that change frequently but should not re-trigger
75
+ // the effect (which would tear down and re-create the Firebase listener).
76
+ const isAuthenticatedRef = (0, react_1.useRef)(isAuthenticated);
77
+ const isLoadingRef = (0, react_1.useRef)(isLoading);
78
+ const syncInProgressRef = (0, react_1.useRef)(false);
79
+ const isSyncingStateRef = (0, react_1.useRef)(false);
80
+ const [isSyncing, setIsSyncing] = react_1.default.useState(false);
81
+ // Keep refs up to date.
82
+ (0, react_1.useEffect)(() => {
83
+ isAuthenticatedRef.current = isAuthenticated;
84
+ }, [isAuthenticated]);
85
+ (0, react_1.useEffect)(() => {
86
+ isLoadingRef.current = isLoading;
87
+ }, [isLoading]);
88
+ // Stable callback refs so the effect closure doesn't go stale.
89
+ const onSyncSuccessRef = (0, react_1.useRef)(onSyncSuccess);
90
+ const onSyncErrorRef = (0, react_1.useRef)(onSyncError);
91
+ (0, react_1.useEffect)(() => {
92
+ onSyncSuccessRef.current = onSyncSuccess;
93
+ }, [onSyncSuccess]);
94
+ (0, react_1.useEffect)(() => {
95
+ onSyncErrorRef.current = onSyncError;
96
+ }, [onSyncError]);
97
+ const handleFirebaseUser = (0, react_1.useCallback)(async (fbUser) => {
98
+ if (fbUser) {
99
+ // Firebase user is signed in.
100
+ // Only exchange if PH-CMS is not already authenticated and not loading.
101
+ if (!isAuthenticatedRef.current &&
102
+ !isLoadingRef.current &&
103
+ !syncInProgressRef.current) {
104
+ syncInProgressRef.current = true;
105
+ isSyncingStateRef.current = true;
106
+ setIsSyncing(true);
107
+ try {
108
+ const idToken = await fbUser.getIdToken();
109
+ await client.auth.loginWithFirebase({ idToken });
110
+ // loginWithFirebase stores the tokens in the provider.
111
+ // Now refresh the context so `isAuthenticated` and `user` update.
112
+ await refreshUser();
113
+ onSyncSuccessRef.current?.();
114
+ }
115
+ catch (error) {
116
+ console.error('[useFirebaseAuthSync] Token exchange failed:', error);
117
+ onSyncErrorRef.current?.(error);
118
+ }
119
+ finally {
120
+ syncInProgressRef.current = false;
121
+ isSyncingStateRef.current = false;
122
+ setIsSyncing(false);
123
+ }
124
+ }
125
+ }
126
+ else {
127
+ // Firebase user signed out.
128
+ if (logoutOnFirebaseSignOut && isAuthenticatedRef.current) {
129
+ try {
130
+ await client.auth.logout();
131
+ await refreshUser();
132
+ }
133
+ catch (error) {
134
+ console.error('[useFirebaseAuthSync] Logout failed:', error);
135
+ }
136
+ }
137
+ }
138
+ },
139
+ // `client`, `refreshUser`, and `logoutOnFirebaseSignOut` are stable
140
+ // across renders (client is a singleton, refreshUser is memoized,
141
+ // logoutOnFirebaseSignOut is typically a constant).
142
+ [client, refreshUser, logoutOnFirebaseSignOut]);
143
+ (0, react_1.useEffect)(() => {
144
+ // Dynamically import the listener so that this file has zero runtime
145
+ // dependency on firebase/auth at the module level. The `Auth` object
146
+ // passed in already carries the implementation; we just need the
147
+ // `onAuthStateChanged` function reference from it.
148
+ //
149
+ // Firebase Auth's `onAuthStateChanged` is available on the Auth object
150
+ // via the modular API through the standalone function, but we can also
151
+ // call it as `firebaseAuth.onAuthStateChanged(...)` which works on the
152
+ // Auth instance directly (compat style). However, the recommended
153
+ // modular approach is to use the standalone function.
154
+ //
155
+ // To avoid directly importing from 'firebase/auth' at the top level
156
+ // (since it's an optional peer dep), we use a dynamic import wrapped
157
+ // in a try-catch. If the import fails, we fall back to a noop.
158
+ let unsubscribe = null;
159
+ let cancelled = false;
160
+ const setup = async () => {
161
+ try {
162
+ // Dynamic import so builds without firebase don't break.
163
+ const firebaseAuthModule = await Promise.resolve().then(() => __importStar(require('firebase/auth')));
164
+ if (cancelled)
165
+ return;
166
+ unsubscribe = firebaseAuthModule.onAuthStateChanged(firebaseAuth, (fbUser) => {
167
+ // Fire and forget; errors are caught inside handleFirebaseUser.
168
+ void handleFirebaseUser(fbUser);
169
+ });
170
+ }
171
+ catch {
172
+ // firebase/auth is not installed — this is fine, it's optional.
173
+ console.warn('[useFirebaseAuthSync] firebase/auth could not be imported. ' +
174
+ 'Make sure it is installed if you intend to use Firebase auth sync.');
175
+ }
176
+ };
177
+ void setup();
178
+ return () => {
179
+ cancelled = true;
180
+ unsubscribe?.();
181
+ };
182
+ }, [firebaseAuth, handleFirebaseUser]);
183
+ return { isSyncing };
184
+ };
185
+ exports.useFirebaseAuthSync = useFirebaseAuthSync;
186
+ /**
187
+ * `<FirebaseAuthSync>`
188
+ *
189
+ * A convenience wrapper component that calls `useFirebaseAuthSync` internally.
190
+ * Place it **inside** `<PHCMSProvider>` and wrap the part of the tree that
191
+ * should wait for or react to Firebase↔PH-CMS auth synchronization.
192
+ *
193
+ * ### Example
194
+ *
195
+ * ```tsx
196
+ * import { PHCMSProvider, FirebaseAuthSync } from '@ph-cms/client-sdk';
197
+ * import { getAuth } from 'firebase/auth';
198
+ *
199
+ * const firebaseAuth = getAuth(firebaseApp);
200
+ *
201
+ * function App() {
202
+ * return (
203
+ * <PHCMSProvider client={client}>
204
+ * <FirebaseAuthSync firebaseAuth={firebaseAuth}>
205
+ * <MainContent />
206
+ * </FirebaseAuthSync>
207
+ * </PHCMSProvider>
208
+ * );
209
+ * }
210
+ * ```
211
+ */
212
+ const FirebaseAuthSync = ({ firebaseAuth, logoutOnFirebaseSignOut, onSyncSuccess, onSyncError, children, }) => {
213
+ (0, exports.useFirebaseAuthSync)({
214
+ firebaseAuth,
215
+ logoutOnFirebaseSignOut,
216
+ onSyncSuccess,
217
+ onSyncError,
218
+ });
219
+ return react_1.default.createElement(react_1.default.Fragment, null, children);
220
+ };
221
+ exports.FirebaseAuthSync = FirebaseAuthSync;
package/dist/index.d.ts CHANGED
@@ -1,17 +1,18 @@
1
+ export * from './auth/firebase-provider';
1
2
  export * from './auth/interfaces';
2
3
  export * from './auth/local-provider';
3
- export * from './auth/firebase-provider';
4
- export * from './errors';
5
4
  export * from './client';
5
+ export * from './errors';
6
6
  export * from './modules/auth';
7
- export * from './modules/content';
8
7
  export * from './modules/channel';
9
- export * from './modules/terms';
8
+ export * from './modules/content';
10
9
  export * from './modules/media';
10
+ export * from './modules/terms';
11
11
  export * from './context';
12
12
  export * from './hooks/useAuth';
13
- export * from './hooks/useContent';
14
13
  export * from './hooks/useChannel';
15
- export * from './hooks/useTerms';
14
+ export * from './hooks/useContent';
15
+ export * from './hooks/useFirebaseAuthSync';
16
16
  export * from './hooks/useMedia';
17
+ export * from './hooks/useTerms';
17
18
  export * from './types';
package/dist/index.js CHANGED
@@ -14,20 +14,21 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./auth/firebase-provider"), exports);
17
18
  __exportStar(require("./auth/interfaces"), exports);
18
19
  __exportStar(require("./auth/local-provider"), exports);
19
- __exportStar(require("./auth/firebase-provider"), exports);
20
- __exportStar(require("./errors"), exports);
21
20
  __exportStar(require("./client"), exports);
21
+ __exportStar(require("./errors"), exports);
22
22
  __exportStar(require("./modules/auth"), exports);
23
- __exportStar(require("./modules/content"), exports);
24
23
  __exportStar(require("./modules/channel"), exports);
25
- __exportStar(require("./modules/terms"), exports);
24
+ __exportStar(require("./modules/content"), exports);
26
25
  __exportStar(require("./modules/media"), exports);
26
+ __exportStar(require("./modules/terms"), exports);
27
27
  __exportStar(require("./context"), exports);
28
28
  __exportStar(require("./hooks/useAuth"), exports);
29
- __exportStar(require("./hooks/useContent"), exports);
30
29
  __exportStar(require("./hooks/useChannel"), exports);
31
- __exportStar(require("./hooks/useTerms"), exports);
30
+ __exportStar(require("./hooks/useContent"), exports);
31
+ __exportStar(require("./hooks/useFirebaseAuthSync"), exports);
32
32
  __exportStar(require("./hooks/useMedia"), exports);
33
+ __exportStar(require("./hooks/useTerms"), exports);
33
34
  __exportStar(require("./types"), exports);