Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d47341d
Clear sync-related Redux state when user logs out
gcsecsey Mar 4, 2026
c680af7
Treat failed import during sync pull as a pull error
gcsecsey Mar 13, 2026
e667920
Simplify logout cleanup in ImportExportProvider to use direct isAuthe…
gcsecsey Mar 13, 2026
99884e0
Merge branch 'trunk' of github.com:Automattic/studio into gcsecsey/st…
gcsecsey Mar 13, 2026
7cf6787
Merge branch 'trunk' into gcsecsey/stu-166-clear-sync-on-logout
gcsecsey Mar 13, 2026
f816995
Use cancel thunks for sync cleanup on logout instead of manual cancel…
gcsecsey Mar 16, 2026
0c6e92f
Merge branch 'trunk' into gcsecsey/stu-166-clear-sync-on-logout
gcsecsey Mar 16, 2026
0d93adc
Merge branch 'trunk' into gcsecsey/stu-166-clear-sync-on-logout
gcsecsey Mar 17, 2026
625fc86
Use getOriginalState() in userLoggedOut listener to fix cancel thunks…
gcsecsey Mar 19, 2026
677d49d
Clear wpcomClient before dispatching userLoggedOut to prevent error m…
gcsecsey Mar 19, 2026
20bde45
Merge branch 'trunk' into gcsecsey/stu-166-clear-sync-on-logout
gcsecsey Mar 19, 2026
34bd922
keep connected sites cache on logout
gcsecsey Mar 19, 2026
11a1a05
Merge branch 'gcsecsey/stu-166-clear-sync-on-logout' of github.com:Au…
gcsecsey Mar 19, 2026
58e5ecf
Abort poller signals in cancel thunks to prevent error modals during …
gcsecsey Mar 19, 2026
e09fa02
Move setWpcomClient into userLoggedOut listener
gcsecsey Mar 20, 2026
42fcee6
Merge branch 'trunk' of github.com:Automattic/studio into gcsecsey/st…
gcsecsey Apr 9, 2026
86cb91a
Merge branch 'trunk' of github.com:Automattic/studio into gcsecsey/st…
gcsecsey Apr 9, 2026
ad7bc86
Move sync poller logic from stores/index.ts into sync-operations-slic…
gcsecsey Apr 9, 2026
a833912
Merge branch 'trunk' into gcsecsey/stu-166-clear-sync-on-logout
gcsecsey Apr 10, 2026
d94ad64
Merge branch 'trunk' of github.com:Automattic/studio into gcsecsey/st…
gcsecsey Apr 10, 2026
b4603f5
Merge branch 'gcsecsey/stu-166-clear-sync-on-logout' of github.com:Au…
gcsecsey Apr 10, 2026
8e1a594
Avoid setting cancelled state when logging out
gcsecsey Apr 15, 2026
e32c175
Enable import/export while push is running remotely
gcsecsey Apr 15, 2026
1ea5e8f
Saving uncommitted changes before archiving
Apr 17, 2026
e5f06f8
Merge branch 'trunk' into gcsecsey/stu-166-clear-sync-on-logout
gcsecsey Apr 22, 2026
9490492
Merge branch 'gcsecsey/stu-166-clear-sync-on-logout' of github.com:Au…
gcsecsey May 6, 2026
8a04a4b
Abort sync operations directly from userLoggedOut listener
gcsecsey May 6, 2026
2b4a33e
Skip import/export reset on logout if a pull is in flight
gcsecsey May 6, 2026
d0ffd83
Merge branch 'trunk' into gcsecsey/stu-166-clear-sync-on-logout
gcsecsey May 6, 2026
3149b6b
Merge branch 'trunk' of github.com:Automattic/studio into gcsecsey/st…
gcsecsey Jun 1, 2026
337d9e3
Remove protocol-handler leftovers accidentally swept in by Conductor …
gcsecsey Jun 1, 2026
f57a08e
Move local-sync-work selectors into syncOperationsSelectors
gcsecsey Jun 1, 2026
dceb859
Merge branch 'trunk' of github.com:Automattic/studio into gcsecsey/st…
gcsecsey Jun 2, 2026
912d789
Handle logout during a sync pull cleanly (no error dialog, no orphane…
gcsecsey Jun 2, 2026
e852ab8
Move logic into Redux slice selectors
fredrikekelund Jun 3, 2026
76977ec
Corrections
fredrikekelund Jun 3, 2026
831efb6
Correction
fredrikekelund Jun 3, 2026
a73a48e
Merge branch 'trunk' into gcsecsey/stu-166-clear-sync-on-logout
fredrikekelund Jun 3, 2026
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 ] );
Comment thread
gcsecsey marked this conversation as resolved.

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' );
37 changes: 37 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 @@ -184,6 +185,42 @@ startAppListening( {
},
} );

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

// Collect all operation IDs from state and pollers
const allStateIds = new Set( [
...Object.keys( state.syncOperations.pushStates ),
...Object.keys( state.syncOperations.pullStates ),
...PUSH_POLLERS.keys(),
...PULL_POLLERS.keys(),
] );

// Cancel main process operations
for ( const stateId of allStateIds ) {
getIpcApi().cancelSyncOperation( stateId );
}

// Stop renderer-side pollers
for ( const controller of PUSH_POLLERS.values() ) {
controller.abort();
}
PUSH_POLLERS.clear();
for ( const controller of PULL_POLLERS.values() ) {
controller.abort();
}
PULL_POLLERS.clear();
Comment thread
gcsecsey marked this conversation as resolved.
Outdated

// Reset authenticated RTK Query caches
listenerApi.dispatch( connectedSitesApi.util.resetApiState() );
listenerApi.dispatch( wpcomSitesApi.util.resetApiState() );
listenerApi.dispatch( wpcomApi.util.resetApiState() );
Comment thread
gcsecsey marked this conversation as resolved.
Outdated
},
} );

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
2 changes: 2 additions & 0 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
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