Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions apps/studio/src/components/auth-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ 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 { useAppDispatch, useI18nLocale } from 'src/stores';
import { userLoggedOut } from 'src/stores/auth-actions';
import { setWpcomClient } from 'src/stores/wpcom-api';
import type { WPCOM } from 'wpcom/types';

Expand Down Expand Up @@ -45,11 +46,13 @@ const AuthProvider: React.FC< AuthProviderProps > = ( { children } ) => {
const { __ } = useI18n();
const isOffline = useOffline();

const dispatch = useAppDispatch();
const authenticate = useCallback( () => getIpcApi().authenticate(), [] );

const handleInvalidToken = useCallback( async () => {
try {
void getIpcApi().logRendererMessage( 'info', 'Detected invalid token. Logging out.' );
dispatch( userLoggedOut() );
await getIpcApi().clearAuthenticationToken();
setIsAuthenticated( false );
setClient( undefined );
Expand All @@ -59,7 +62,7 @@ const AuthProvider: React.FC< AuthProviderProps > = ( { children } ) => {
console.error( 'Failed to handle invalid token:', err );
Sentry.captureException( err );
}
}, [] );
}, [ dispatch ] );

useIpcListener( 'auth-updated', ( _event, payload ) => {
if ( 'error' in payload ) {
Expand Down Expand Up @@ -110,6 +113,7 @@ const AuthProvider: React.FC< AuthProviderProps > = ( { children } ) => {
}

try {
dispatch( userLoggedOut() );
await getIpcApi().clearAuthenticationToken();
setIsAuthenticated( false );
setClient( undefined );
Expand All @@ -119,7 +123,7 @@ const AuthProvider: React.FC< AuthProviderProps > = ( { children } ) => {
console.error( err );
Sentry.captureException( err );
}
}, [ client, isOffline ] );
}, [ client, dispatch, isOffline ] );

useEffect( () => {
async function run() {
Expand Down
13 changes: 12 additions & 1 deletion apps/studio/src/hooks/use-import-export.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as Sentry from '@sentry/electron/renderer';
import { __, sprintf } from '@wordpress/i18n';
import { createContext, useMemo, useState, useCallback, useContext } from 'react';
import { createContext, useEffect, useMemo, useState, useCallback, useContext } from 'react';
import { WP_CLI_IMPORT_EXPORT_RESPONSE_TIMEOUT_IN_HRS } from 'src/constants';
import { useAuth } from 'src/hooks/use-auth';
import { useIpcListener } from 'src/hooks/use-ipc-listener';
import { useSiteDetails } from 'src/hooks/use-site-details';
import { simplifyErrorForDisplay } from 'src/lib/error-formatting';
Expand All @@ -26,6 +27,7 @@ export type ImportProgressState = {
statusMessage: string;
progress: number;
isNewSite?: boolean;
isError?: boolean;
};
};

Expand Down Expand Up @@ -76,6 +78,14 @@ export const ImportExportProvider = ( { children }: { children: React.ReactNode
const [ importState, setImportState ] = useState< ImportProgressState >( {} );
const [ exportState, setExportState ] = useState< ExportProgressState >( {} );
const { startServer, stopServer, updateSite } = useSiteDetails();
const { isAuthenticated } = useAuth();

useEffect( () => {
if ( ! isAuthenticated ) {
setImportState( {} );
setExportState( {} );
}
}, [ isAuthenticated ] );

const importFile = useCallback(
async (
Expand Down Expand Up @@ -339,6 +349,7 @@ export const ImportExportProvider = ( { children }: { children: React.ReactNode
[ siteId ]: {
...currentProgress,
statusMessage: __( 'Import failed. Please try again.' ),
isError: true,
},
} ) );
break;
Expand Down
12 changes: 8 additions & 4 deletions apps/studio/src/modules/sync/components/sync-connected-sites.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,15 @@ const SyncConnectedSitesSectionItem = ( {
const sitePullState = useRootSelector(
syncOperationsSelectors.selectPullState( selectedSite.id, connectedSite.id )
);
const pullImportFailed =
sitePullState?.status.key === 'importing' &&
importState[ connectedSite.localSiteId ]?.isError === true;
const isPulling =
sitePullState?.status.key === 'in-progress' ||
sitePullState?.status.key === 'downloading' ||
sitePullState?.status.key === 'importing';
const isPullError = sitePullState?.status.key === 'failed';
( sitePullState?.status.key === 'in-progress' ||
sitePullState?.status.key === 'downloading' ||
sitePullState?.status.key === 'importing' ) &&
! pullImportFailed;
const isPullError = sitePullState?.status.key === 'failed' || pullImportFailed;
const hasPullFinished = sitePullState?.status.key === 'finished';
const hasPullCancelled = sitePullState?.status.key === 'cancelled';
const pullImportState = importState[ connectedSite.localSiteId ];
Expand Down
3 changes: 3 additions & 0 deletions apps/studio/src/stores/auth-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createAction } from '@reduxjs/toolkit';

export const userLoggedOut = createAction( 'auth/userLoggedOut' );
34 changes: 34 additions & 0 deletions apps/studio/src/stores/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ 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 { userLoggedOut } from 'src/stores/auth-actions';
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';
Expand Down Expand Up @@ -182,6 +183,39 @@ startAppListening( {
},
} );

// Clear all sync state when user logs out
startAppListening( {
actionCreator: userLoggedOut,
effect( action, listenerApi ) {
const state = listenerApi.getState();

for ( const pullState of Object.values( state.syncOperations.pullStates ) ) {
void store.dispatch(
syncOperationsThunks.cancelPull( {
selectedSiteId: pullState.selectedSite.id,
remoteSiteId: pullState.remoteSiteId,
displayNotification: false,
} )
);
}

for ( const pushState of Object.values( state.syncOperations.pushStates ) ) {
void store.dispatch(
syncOperationsThunks.cancelPush( {
selectedSiteId: pushState.selectedSite.id,
remoteSiteId: pushState.remoteSiteId,
displayNotification: false,
} )
);
}

// Reset authenticated RTK Query caches
listenerApi.dispatch( connectedSitesApi.util.resetApiState() );
listenerApi.dispatch( wpcomSitesApi.util.resetApiState() );
listenerApi.dispatch( wpcomApi.util.resetApiState() );
},
} );

const PUSH_POLLING_KEYS = [ 'creatingRemoteBackup', 'applyingChanges', 'finishing' ];
const SYNC_POLLING_INTERVAL = 3000;

Expand Down
4 changes: 4 additions & 0 deletions apps/studio/src/stores/sync/connected-sites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { getIpcApi } from 'src/lib/get-ipc-api';
import { RootState } from 'src/stores';
import { userLoggedOut } from 'src/stores/auth-actions';
import type { SyncSite, SyncModalMode } from 'src/modules/sync/types';

type ConnectedSitesState = {
Expand Down Expand Up @@ -60,6 +61,9 @@ const connectedSitesSlice = createSlice( {
delete state.loadingSiteIds[ action.payload ];
},
},
extraReducers: ( builder ) => {
builder.addCase( userLoggedOut, () => getInitialState() );
},
} );

export const connectedSitesActions = connectedSitesSlice.actions;
Expand Down
33 changes: 23 additions & 10 deletions apps/studio/src/stores/sync/sync-operations-slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { generateStateId } from 'src/hooks/sync-sites/use-pull-push-states';
import { getIpcApi } from 'src/lib/get-ipc-api';
import { getHostnameFromUrl } from 'src/lib/url-utils';
import { store } from 'src/stores';
import { userLoggedOut } from 'src/stores/auth-actions';
import { connectedSitesApi } from 'src/stores/sync/connected-sites';
import type {
PullStateProgressInfo,
Expand Down Expand Up @@ -155,6 +156,7 @@ const syncOperationsSlice = createSlice( {
},
extraReducers: ( builder ) => {
builder
.addCase( userLoggedOut, () => initialState )
.addCase( pushSiteThunk.rejected, ( state, action ) => {
const { connectedSite, selectedSite } = action.meta.arg;
const stateId = generateStateId( selectedSite.id, connectedSite.id );
Expand Down Expand Up @@ -270,11 +272,15 @@ const createTypedAsyncThunk = createAsyncThunk.withTypes< {
type CancelOperationPayload = {
selectedSiteId: string;
remoteSiteId: number;
displayNotification?: boolean;
};

const cancelPushThunk = createTypedAsyncThunk(
'syncOperations/cancelPush',
async ( { selectedSiteId, remoteSiteId }: CancelOperationPayload, { dispatch } ) => {
async (
{ selectedSiteId, remoteSiteId, displayNotification = true }: CancelOperationPayload,
{ dispatch }
) => {
const operationId = generateStateId( selectedSiteId, remoteSiteId );
const abortCallback = PUSH_SITE_ABORT_CALLBACKS.get( operationId );

Expand All @@ -289,16 +295,21 @@ const cancelPushThunk = createTypedAsyncThunk(
} )
);

getIpcApi().showNotification( {
title: __( 'Push cancelled' ),
body: __( 'The push operation has been cancelled.' ),
} );
if ( displayNotification ) {
getIpcApi().showNotification( {
title: __( 'Push cancelled' ),
body: __( 'The push operation has been cancelled.' ),
} );
}
}
);

const cancelPullThunk = createTypedAsyncThunk(
'syncOperations/cancelPull',
async ( { selectedSiteId, remoteSiteId }: CancelOperationPayload, { dispatch } ) => {
async (
{ selectedSiteId, remoteSiteId, displayNotification = true }: CancelOperationPayload,
{ dispatch }
) => {
const operationId = generateStateId( selectedSiteId, remoteSiteId );
getIpcApi().cancelSyncOperation( operationId );

Expand All @@ -316,10 +327,12 @@ const cancelPullThunk = createTypedAsyncThunk(
// Ignore errors if file doesn't exist
} );

getIpcApi().showNotification( {
title: __( 'Pull cancelled' ),
body: __( 'The pull operation has been cancelled.' ),
} );
if ( displayNotification ) {
getIpcApi().showNotification( {
title: __( 'Pull cancelled' ),
body: __( 'The pull operation has been cancelled.' ),
} );
}
}
);

Expand Down
2 changes: 2 additions & 0 deletions apps/studio/src/stores/sync/sync-slice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createSlice } from '@reduxjs/toolkit';
import { TreeNode } from 'src/components/tree-view';
import { userLoggedOut } from 'src/stores/auth-actions';
import { fetchRemoteFileTree } from './sync-api';

type RemoteFileTreeState = {
Expand Down Expand Up @@ -33,6 +34,7 @@ const syncSlice = createSlice( {
},
extraReducers: ( builder ) => {
builder
.addCase( userLoggedOut, () => initialState )
.addCase( fetchRemoteFileTree.pending, ( state ) => {
state.remoteFileTrees.loading = true;
state.remoteFileTrees.error = null;
Expand Down
Loading