diff --git a/apps/studio/src/components/auth-provider.tsx b/apps/studio/src/components/auth-provider.tsx
deleted file mode 100644
index 8afb587e36..0000000000
--- a/apps/studio/src/components/auth-provider.tsx
+++ /dev/null
@@ -1,223 +0,0 @@
-import * as Sentry from '@sentry/electron/renderer';
-import wpcomFactory from '@studio/common/lib/wpcom-factory';
-import wpcomXhrRequest from '@studio/common/lib/wpcom-xhr-request-factory';
-import { useI18n } from '@wordpress/react-i18n';
-import { createContext, useState, useEffect, useMemo, useCallback, ReactNode } from 'react';
-import { useIpcListener } from 'src/hooks/use-ipc-listener';
-import { useOffline } from 'src/hooks/use-offline';
-import { getIpcApi } from 'src/lib/get-ipc-api';
-import { isInvalidTokenError } from 'src/lib/is-invalid-oauth-token-error';
-import { useI18nLocale } from 'src/stores';
-import { setWpcomClient } from 'src/stores/wpcom-api';
-import type { WPCOM } from 'wpcom/types';
-
-export interface AuthContextType {
- client: WPCOM | undefined;
- isAuthenticated: boolean;
- authenticate: () => void; // Adjust based on the actual implementation
- logout: () => Promise< void >; // Adjust based on the actual implementation
- user?: { id: number; email: string; displayName: string };
-}
-
-interface AuthProviderProps {
- children: ReactNode;
-}
-
-interface WpcomParams extends Record< string, unknown > {
- query?: string;
- apiNamespace?: string;
-}
-
-export const AuthContext = createContext< AuthContextType >( {
- client: undefined,
- isAuthenticated: false,
- authenticate: () => {
- // Placeholder for authenticate logic. Just to avoid lint error
- },
- logout: () => Promise.resolve(),
-} );
-
-const AuthProvider: React.FC< AuthProviderProps > = ( { children } ) => {
- const [ isAuthenticated, setIsAuthenticated ] = useState( false );
- const [ client, setClient ] = useState< WPCOM | undefined >( undefined );
- const [ user, setUser ] = useState< AuthContextType[ 'user' ] >( undefined );
- const locale = useI18nLocale();
- const { __ } = useI18n();
- const isOffline = useOffline();
-
- const authenticate = useCallback( () => getIpcApi().authenticate(), [] );
-
- const handleInvalidToken = useCallback( async () => {
- try {
- void getIpcApi().logRendererMessage( 'info', 'Detected invalid token. Logging out.' );
- await getIpcApi().clearAuthenticationToken();
- setIsAuthenticated( false );
- setClient( undefined );
- setWpcomClient( undefined );
- setUser( undefined );
- } catch ( err ) {
- console.error( 'Failed to handle invalid token:', err );
- Sentry.captureException( err );
- }
- }, [] );
-
- useIpcListener( 'auth-updated', ( _event, payload ) => {
- if ( 'error' in payload ) {
- let title: string = __( 'Authentication error' );
- let message: string = __( 'Please try again.' );
-
- // User has denied access to the authorization dialog.
- if ( payload.error instanceof Error && payload.error.message.includes( 'access_denied' ) ) {
- title = __( 'Authorization denied' );
- message = __(
- 'It looks like you denied the authorization request. To proceed, please click "Approve"'
- );
- }
-
- void getIpcApi().showErrorMessageBox( { title, message } );
- return;
- }
-
- const { token } = payload;
- const newClient = createWpcomClient( token.accessToken, locale, handleInvalidToken );
-
- setIsAuthenticated( true );
- setClient( newClient );
- setWpcomClient( newClient );
- setUser( {
- id: token.id,
- email: token.email,
- displayName: token.displayName || '',
- } );
- } );
-
- const logout = useCallback( async () => {
- if ( ! isOffline && client ) {
- try {
- await client.req.del( {
- apiNamespace: 'wpcom/v2',
- path: '/studio-app/token',
- // wpcom.req.del defaults to POST; explicitly send HTTP DELETE for v2
- method: 'DELETE',
- } );
- console.log( 'Token revoked' );
- } catch ( err ) {
- console.error( 'Failed to revoke token:', err );
- Sentry.captureException( err );
- }
- } else if ( isOffline ) {
- console.log( 'Offline: Skipping token revocation request' );
- }
-
- try {
- await getIpcApi().clearAuthenticationToken();
- setIsAuthenticated( false );
- setClient( undefined );
- setWpcomClient( undefined );
- setUser( undefined );
- } catch ( err ) {
- console.error( err );
- Sentry.captureException( err );
- }
- }, [ client, isOffline ] );
-
- useEffect( () => {
- async function run() {
- try {
- const token = await getIpcApi().getAuthenticationToken();
-
- if ( ! token ) {
- setIsAuthenticated( false );
- return;
- }
-
- const newClient = createWpcomClient( token.accessToken, locale, handleInvalidToken );
-
- setIsAuthenticated( true );
- setClient( newClient );
- setWpcomClient( newClient );
- setUser( {
- id: token.id,
- email: token.email,
- displayName: token.displayName || '',
- } );
- } catch ( err ) {
- console.error( err );
- Sentry.captureException( err );
- }
- }
- void run();
- }, [ locale, handleInvalidToken ] );
-
- // Memoize the context value to avoid unnecessary renders
- const contextValue: AuthContextType = useMemo(
- () => ( {
- client,
- isAuthenticated,
- authenticate,
- logout,
- user,
- } ),
- [ client, isAuthenticated, authenticate, logout, user ]
- );
-
- return { children };
-};
-
-function createWpcomClient(
- token?: string,
- locale?: string,
- onInvalidToken?: () => Promise< void >
-): WPCOM {
- let isAuthErrorDialogOpen = false;
- const handleInvalidTokenError = async ( response: unknown ) => {
- if ( isInvalidTokenError( response ) && onInvalidToken && ! isAuthErrorDialogOpen ) {
- isAuthErrorDialogOpen = true;
- await onInvalidToken();
- await getIpcApi().showMessageBox( {
- type: 'error',
- message: 'Session Expired',
- detail: 'Your session has expired. Please log in again.',
- } );
- isAuthErrorDialogOpen = false;
- }
- };
-
- const addLocaleToParams = ( params: WpcomParams ) => {
- if ( locale && locale !== 'en' ) {
- const queryParams = new URLSearchParams(
- 'query' in params && typeof params.query === 'string' ? params.query : ''
- );
- const localeParamName =
- 'apiNamespace' in params && typeof params.apiNamespace === 'string' ? '_locale' : 'locale';
- queryParams.set( localeParamName, locale );
-
- Object.assign( params, {
- query: queryParams.toString(),
- } );
- }
- return params;
- };
-
- // Wrap the request handler to add locale and error handling before passing to wpcomFactory
- const wrappedRequestHandler = (
- params: object,
- callback: ( err: unknown, response?: unknown, headers?: unknown ) => void
- ) => {
- const modifiedParams = addLocaleToParams( params as WpcomParams );
- const wrappedCallback = ( err: unknown, response: unknown, headers: unknown ) => {
- if ( err ) {
- void handleInvalidTokenError( err );
- }
- if ( typeof callback === 'function' ) {
- callback( err, response, headers );
- }
- };
-
- return wpcomXhrRequest( modifiedParams, wrappedCallback );
- };
-
- return wpcomFactory( token, wrappedRequestHandler );
-}
-
-export default AuthProvider;
diff --git a/apps/studio/src/components/root.tsx b/apps/studio/src/components/root.tsx
index d23e6b0959..e3e6bced51 100644
--- a/apps/studio/src/components/root.tsx
+++ b/apps/studio/src/components/root.tsx
@@ -5,7 +5,6 @@ import { I18nProvider } from '@wordpress/react-i18n';
import { useEffect } from 'react';
import { Provider as ReduxProvider } from 'react-redux';
import App from 'src/components/app';
-import AuthProvider from 'src/components/auth-provider';
import CrashTester from 'src/components/crash-tester';
import ErrorBoundary from 'src/components/error-boundary';
import { WordPressStyles } from 'src/components/wordpress-styles';
@@ -35,21 +34,19 @@ const Root = () => {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/studio/src/components/tests/content-tab-assistant.test.tsx b/apps/studio/src/components/tests/content-tab-assistant.test.tsx
index 6d5251cbfa..4d6b3a2f5f 100644
--- a/apps/studio/src/components/tests/content-tab-assistant.test.tsx
+++ b/apps/studio/src/components/tests/content-tab-assistant.test.tsx
@@ -7,12 +7,12 @@ import nock from 'nock';
import { Provider } from 'react-redux';
import { Dispatch } from 'redux';
import { vi } from 'vitest';
-import { AuthContext, AuthContextType } from 'src/components/auth-provider';
import {
ContentTabAssistant,
MIMIC_CONVERSATION_DELAY,
} from 'src/components/content-tab-assistant';
import { LOCAL_STORAGE_CHAT_MESSAGES_KEY, CLEAR_HISTORY_REMINDER_TIME } from 'src/constants';
+import { AuthContextType, useAuth } from 'src/hooks/use-auth';
import { useGetWpVersion } from 'src/hooks/use-get-wp-version';
import { useOffline } from 'src/hooks/use-offline';
import { ThemeDetailsProvider } from 'src/hooks/use-theme-details';
@@ -28,6 +28,7 @@ store.replaceReducer( testReducer );
vi.mock( 'src/hooks/use-offline' );
vi.mock( 'src/lib/get-ipc-api' );
vi.mock( 'src/hooks/use-get-wp-version' );
+vi.mock( 'src/hooks/use-auth' );
vi.mock( 'src/lib/app-globals', () => ( {
getAppGlobals: () => ( {
@@ -129,14 +130,13 @@ describe( 'ContentTabAssistant', () => {
logout,
...auth,
};
+ vi.mocked( useAuth ).mockReturnValue( authContextValue );
return (
-
-
-
-
-
+
+
+
);
};
diff --git a/apps/studio/src/hooks/use-auth.ts b/apps/studio/src/hooks/use-auth.ts
index 6ed814d1d5..686543bb7f 100644
--- a/apps/studio/src/hooks/use-auth.ts
+++ b/apps/studio/src/hooks/use-auth.ts
@@ -1,12 +1,37 @@
-import { useContext } from 'react';
-import { AuthContext, type AuthContextType } from 'src/components/auth-provider';
+import { useCallback } from 'react';
+import { useOffline } from 'src/hooks/use-offline';
+import { getIpcApi } from 'src/lib/get-ipc-api';
+import { useAppDispatch, useRootSelector } from 'src/stores';
+import { authLogout, selectIsAuthenticated, selectUser } from 'src/stores/auth-slice';
+import { getWpcomClient } from 'src/stores/wpcom-client';
+import type { AuthUser } from 'src/stores/auth-slice';
+import type { WPCOM } from 'wpcom/types';
+
+export interface AuthContextType {
+ client: WPCOM | undefined;
+ isAuthenticated: boolean;
+ authenticate: () => void;
+ logout: () => Promise< void >;
+ user?: AuthUser;
+}
export const useAuth = (): AuthContextType => {
- const context = useContext( AuthContext );
+ const dispatch = useAppDispatch();
+ const isOffline = useOffline();
+ const isAuthenticated = useRootSelector( selectIsAuthenticated );
+ const user = useRootSelector( selectUser );
+
+ const authenticate = useCallback( () => getIpcApi().authenticate(), [] );
- if ( ! context ) {
- throw new Error( 'useAuth must be used within an AuthProvider' );
- }
+ const logout = useCallback( async () => {
+ await dispatch( authLogout( { isOffline } ) );
+ }, [ dispatch, isOffline ] );
- return context;
+ return {
+ client: isAuthenticated ? getWpcomClient() : undefined,
+ isAuthenticated,
+ authenticate,
+ logout,
+ user,
+ };
};
diff --git a/apps/studio/src/modules/preview-site/components/create-preview-button.tsx b/apps/studio/src/modules/preview-site/components/create-preview-button.tsx
index 9b8cf12ccd..3bdd3972f9 100644
--- a/apps/studio/src/modules/preview-site/components/create-preview-button.tsx
+++ b/apps/studio/src/modules/preview-site/components/create-preview-button.tsx
@@ -1,10 +1,10 @@
import { DEMO_SITE_SIZE_LIMIT_GB } from '@studio/common/constants';
import { __, sprintf } from '@wordpress/i18n';
import { useI18n } from '@wordpress/react-i18n';
-import { AuthContextType } from 'src/components/auth-provider';
import Button from 'src/components/button';
import offlineIcon from 'src/components/offline-icon';
import { Tooltip } from 'src/components/tooltip';
+import { AuthContextType } from 'src/hooks/use-auth';
import { useGetWpVersion } from 'src/hooks/use-get-wp-version';
import { useOffline } from 'src/hooks/use-offline';
import { useSiteSize } from 'src/hooks/use-site-size';
diff --git a/apps/studio/src/stores/auth-slice.ts b/apps/studio/src/stores/auth-slice.ts
new file mode 100644
index 0000000000..7af9e14180
--- /dev/null
+++ b/apps/studio/src/stores/auth-slice.ts
@@ -0,0 +1,214 @@
+import { createAsyncThunk, createSlice, isAnyOf } from '@reduxjs/toolkit';
+import * as Sentry from '@sentry/electron/renderer';
+import wpcomFactory from '@studio/common/lib/wpcom-factory';
+import wpcomXhrRequest from '@studio/common/lib/wpcom-xhr-request-factory';
+import { __ } from '@wordpress/i18n';
+import { getIpcApi } from 'src/lib/get-ipc-api';
+import { isInvalidTokenError } from 'src/lib/is-invalid-oauth-token-error';
+import { store, RootState } from 'src/stores';
+import { getWpcomClient, setWpcomClient } from 'src/stores/wpcom-client';
+import type { StoredToken } from 'src/lib/oauth';
+import type { WPCOM } from 'wpcom/types';
+
+interface WpcomParams extends Record< string, unknown > {
+ query?: string;
+ apiNamespace?: string;
+}
+
+export type AuthUser = { id: number; email: string; displayName: string };
+
+type AuthState = {
+ isAuthenticated: boolean;
+ user: AuthUser | null;
+};
+
+const initialState: AuthState = {
+ isAuthenticated: false,
+ user: null,
+};
+
+function createWpcomClient( token?: string, locale?: string, onInvalidToken?: () => void ): WPCOM {
+ const addLocaleToParams = ( params: WpcomParams ) => {
+ if ( locale && locale !== 'en' ) {
+ const queryParams = new URLSearchParams(
+ 'query' in params && typeof params.query === 'string' ? params.query : ''
+ );
+ const localeParamName =
+ 'apiNamespace' in params && typeof params.apiNamespace === 'string' ? '_locale' : 'locale';
+ queryParams.set( localeParamName, locale );
+
+ Object.assign( params, {
+ query: queryParams.toString(),
+ } );
+ }
+ return params;
+ };
+
+ const wrappedRequestHandler = (
+ params: object,
+ callback: ( err: unknown, response?: unknown, headers?: unknown ) => void
+ ) => {
+ const modifiedParams = addLocaleToParams( params as WpcomParams );
+ const wrappedCallback = ( err: unknown, response: unknown, headers: unknown ) => {
+ if ( err && isInvalidTokenError( err ) && onInvalidToken && ! isAuthErrorDialogOpen ) {
+ onInvalidToken();
+ }
+ if ( typeof callback === 'function' ) {
+ callback( err, response, headers );
+ }
+ };
+
+ return wpcomXhrRequest( modifiedParams, wrappedCallback );
+ };
+
+ return wpcomFactory( token, wrappedRequestHandler );
+}
+
+const createTypedAsyncThunk = createAsyncThunk.withTypes< { state: RootState } >();
+
+let isAuthErrorDialogOpen = false;
+
+export const handleInvalidToken = createTypedAsyncThunk( 'auth/handleInvalidToken', async () => {
+ if ( isAuthErrorDialogOpen ) {
+ return;
+ }
+ isAuthErrorDialogOpen = true;
+ try {
+ void getIpcApi().logRendererMessage( 'info', 'Detected invalid token. Logging out.' );
+ await getIpcApi().clearAuthenticationToken();
+ setWpcomClient( undefined );
+ await getIpcApi().showMessageBox( {
+ type: 'error',
+ message: __( 'Session Expired' ),
+ detail: __( 'Your session has expired. Please log in again.' ),
+ } );
+ } catch ( err ) {
+ console.error( 'Failed to handle invalid token:', err );
+ Sentry.captureException( err );
+ } finally {
+ isAuthErrorDialogOpen = false;
+ }
+} );
+
+export const initializeAuth = createTypedAsyncThunk(
+ 'auth/initialize',
+ async ( { locale }: { locale?: string } ) => {
+ try {
+ const token = await getIpcApi().getAuthenticationToken();
+
+ if ( ! token ) {
+ return null;
+ }
+
+ const client = createWpcomClient( token.accessToken, locale, () =>
+ store.dispatch( handleInvalidToken() )
+ );
+ setWpcomClient( client );
+
+ return {
+ id: token.id,
+ email: token.email,
+ displayName: token.displayName || '',
+ };
+ } catch ( err ) {
+ console.error( err );
+ Sentry.captureException( err );
+ return null;
+ }
+ }
+);
+
+export const authTokenReceived = createTypedAsyncThunk(
+ 'auth/tokenReceived',
+ async ( { token, locale }: { token: StoredToken; locale?: string } ) => {
+ const client = createWpcomClient( token.accessToken, locale, () =>
+ store.dispatch( handleInvalidToken() )
+ );
+ setWpcomClient( client );
+
+ return {
+ id: token.id,
+ email: token.email,
+ displayName: token.displayName || '',
+ };
+ }
+);
+
+export const authLogout = createTypedAsyncThunk(
+ 'auth/logout',
+ async ( { isOffline }: { isOffline: boolean } ) => {
+ const client = getWpcomClient();
+
+ if ( ! isOffline && client ) {
+ try {
+ await client.req.del( {
+ apiNamespace: 'wpcom/v2',
+ path: '/studio-app/token',
+ method: 'DELETE',
+ } );
+ console.log( 'Token revoked' );
+ } catch ( err ) {
+ console.error( 'Failed to revoke token:', err );
+ Sentry.captureException( err );
+ }
+ } else if ( isOffline ) {
+ console.log( 'Offline: Skipping token revocation request' );
+ }
+
+ try {
+ await getIpcApi().clearAuthenticationToken();
+ setWpcomClient( undefined );
+ } catch ( err ) {
+ console.error( err );
+ Sentry.captureException( err );
+ }
+ }
+);
+
+const authSlice = createSlice( {
+ name: 'auth',
+ initialState,
+ reducers: {},
+ extraReducers: ( builder ) => {
+ builder
+ .addCase( initializeAuth.fulfilled, ( state, action ) => {
+ state.isAuthenticated = !! action.payload;
+ state.user = action.payload ?? null;
+ } )
+ .addCase( authTokenReceived.fulfilled, ( state, action ) => {
+ state.isAuthenticated = true;
+ state.user = action.payload;
+ } )
+ .addMatcher( isAnyOf( handleInvalidToken.fulfilled, authLogout.fulfilled ), ( state ) => {
+ state.isAuthenticated = false;
+ state.user = null;
+ } );
+ },
+} );
+
+export const selectIsAuthenticated = ( state: RootState ) => state.auth.isAuthenticated;
+export const selectUser = ( state: RootState ) => state.auth.user ?? undefined;
+
+export function initializeAuthIpcListeners() {
+ window.ipcListener.subscribe( 'auth-updated', ( _event, payload ) => {
+ if ( 'error' in payload ) {
+ let title: string = __( 'Authentication error' );
+ let message: string = __( 'Please try again.' );
+
+ if ( payload.error instanceof Error && payload.error.message.includes( 'access_denied' ) ) {
+ title = __( 'Authorization denied' );
+ message = __(
+ 'It looks like you denied the authorization request. To proceed, please click "Approve"'
+ );
+ }
+
+ void getIpcApi().showErrorMessageBox( { title, message } );
+ return;
+ }
+
+ const locale = store.getState().i18n.locale;
+ void store.dispatch( authTokenReceived( { token: payload.token, locale } ) );
+ } );
+}
+
+export default authSlice.reducer;
diff --git a/apps/studio/src/stores/index.ts b/apps/studio/src/stores/index.ts
index eed7e4a25b..e91c913107 100644
--- a/apps/studio/src/stores/index.ts
+++ b/apps/studio/src/stores/index.ts
@@ -14,10 +14,11 @@ import {
} from 'src/hooks/use-sync-states-progress-info';
import { getIpcApi } from 'src/lib/get-ipc-api';
import { appVersionApi } from 'src/stores/app-version-api';
+import authReducer, { initializeAuth, initializeAuthIpcListeners } from 'src/stores/auth-slice';
import { betaFeaturesReducer, loadBetaFeatures } from 'src/stores/beta-features-slice';
import { certificateTrustApi } from 'src/stores/certificate-trust-api';
import { reducer as chatReducer } from 'src/stores/chat-slice';
-import i18nReducer from 'src/stores/i18n-slice';
+import i18nReducer, { initializeUserLocale, saveUserLocale } from 'src/stores/i18n-slice';
import { installedAppsApi } from 'src/stores/installed-apps-api';
import onboardingReducer from 'src/stores/onboarding-slice';
import {
@@ -34,11 +35,13 @@ import {
} from 'src/stores/sync/sync-operations-slice';
import { wpcomSitesApi } from 'src/stores/sync/wpcom-sites';
import uiReducer from 'src/stores/ui-slice';
-import { getWpcomClient, wpcomApi, wpcomPublicApi } from 'src/stores/wpcom-api';
+import { wpcomApi, wpcomPublicApi } from 'src/stores/wpcom-api';
+import { getWpcomClient } from 'src/stores/wpcom-client';
import { wordpressVersionsApi } from './wordpress-versions-api';
import type { SupportedLocale } from '@studio/common/lib/locale';
export type RootState = {
+ auth: ReturnType< typeof authReducer >;
appVersionApi: ReturnType< typeof appVersionApi.reducer >;
betaFeatures: ReturnType< typeof betaFeaturesReducer >;
chat: ReturnType< typeof chatReducer >;
@@ -290,6 +293,15 @@ async function startPullPoller( selectedSiteId: string, remoteSiteId: number ) {
}
}
+// Initialize auth once locale is loaded, and re-initialize when locale changes
+startAppListening( {
+ matcher: isAnyOf( initializeUserLocale.fulfilled, saveUserLocale.fulfilled ),
+ effect( action, listenerApi ) {
+ const { locale } = listenerApi.getState().i18n;
+ void listenerApi.dispatch( initializeAuth( { locale } ) );
+ },
+} );
+
// Poll push progress when state enters a pollable status
startAppListening( {
actionCreator: syncOperationsActions.updatePushState,
@@ -321,6 +333,7 @@ startAppListening( {
} );
export const rootReducer = combineReducers( {
+ auth: authReducer,
appVersionApi: appVersionApi.reducer,
betaFeatures: betaFeaturesReducer,
chat: chatReducer,
@@ -360,6 +373,7 @@ setupListeners( store.dispatch );
// Initialize beta features on store initialization, but skip in test environment
if ( process.env.NODE_ENV !== 'test' ) {
+ initializeAuthIpcListeners();
void store.dispatch( loadBetaFeatures() );
}
diff --git a/apps/studio/src/stores/sync/wpcom-sites.ts b/apps/studio/src/stores/sync/wpcom-sites.ts
index 9d1f634b56..c7fd2f0048 100644
--- a/apps/studio/src/stores/sync/wpcom-sites.ts
+++ b/apps/studio/src/stores/sync/wpcom-sites.ts
@@ -5,7 +5,7 @@ import { getIpcApi } from 'src/lib/get-ipc-api';
import { reconcileConnectedSites } from 'src/modules/sync/lib/reconcile-connected-sites';
import { getSyncSupport, isPressableSite } from 'src/modules/sync/lib/sync-support';
import { withOfflineCheck } from 'src/stores/utils/with-offline-check';
-import { getWpcomClient } from 'src/stores/wpcom-api';
+import { getWpcomClient } from 'src/stores/wpcom-client';
import type { SyncSite, SyncSupport } from 'src/modules/sync/types';
// Schema for WordPress.com sites endpoint
diff --git a/apps/studio/src/stores/wpcom-api.ts b/apps/studio/src/stores/wpcom-api.ts
index 176f12f16f..cfb6f582ae 100644
--- a/apps/studio/src/stores/wpcom-api.ts
+++ b/apps/studio/src/stores/wpcom-api.ts
@@ -5,8 +5,8 @@ import wpcomFactory from '@studio/common/lib/wpcom-factory';
import wpcomXhrRequest from '@studio/common/lib/wpcom-xhr-request-factory';
import { z } from 'zod';
import { withOfflineCheck, withOfflineCheckMutation } from 'src/stores/utils/with-offline-check';
+import { getWpcomClient } from 'src/stores/wpcom-client';
import type { BaseQueryFn, FetchBaseQueryError } from '@reduxjs/toolkit/query';
-import type { WPCOM } from 'wpcom/types';
const welcomeMessageSchema = z.object( {
messages: z.array( z.string() ),
@@ -70,24 +70,15 @@ const blueprintSchema = z.object( {
export type Blueprint = z.infer< typeof blueprintSchema >;
-let wpcomClient: WPCOM | undefined;
const publicWpcomClient = wpcomFactory( wpcomXhrRequest );
-export const setWpcomClient = ( client: WPCOM | undefined ) => {
- wpcomClient = client;
-};
-
-export const getWpcomClient = (): WPCOM | undefined => {
- return wpcomClient;
-};
-
const wpcomBaseQuery: BaseQueryFn<
{ path: string; apiNamespace?: string },
unknown,
FetchBaseQueryError
> = async ( args ) => {
try {
- const response = await wpcomClient!.req.get( args );
+ const response = await getWpcomClient()!.req.get( args );
return { data: response };
} catch ( error ) {
return {
@@ -247,7 +238,7 @@ function withWpcomClientCheck< TResult, TArg >(
return ( arg, options = {} ) => {
return useQueryHook( arg, {
...options,
- skip: ! wpcomClient || options?.skip,
+ skip: ! getWpcomClient() || options?.skip,
} );
};
}
@@ -258,7 +249,7 @@ function withWpcomClientCheckMutation< TResult, TArg >(
return ( options = {} ) => {
const [ trigger, result ] = useMutationHook( options );
const wrappedTrigger = ( ( ...args: Parameters< typeof trigger > ) => {
- if ( ! wpcomClient ) {
+ if ( ! getWpcomClient() ) {
return Promise.reject( new Error( 'Not authenticated' ) ) as ReturnType< typeof trigger >;
}
return trigger( ...args );
diff --git a/apps/studio/src/stores/wpcom-client.ts b/apps/studio/src/stores/wpcom-client.ts
new file mode 100644
index 0000000000..9f8fe34b43
--- /dev/null
+++ b/apps/studio/src/stores/wpcom-client.ts
@@ -0,0 +1,11 @@
+import type { WPCOM } from 'wpcom/types';
+
+let wpcomClient: WPCOM | undefined;
+
+export const setWpcomClient = ( client: WPCOM | undefined ) => {
+ wpcomClient = client;
+};
+
+export const getWpcomClient = (): WPCOM | undefined => {
+ return wpcomClient;
+};