fix: sigin in different window may not refresh workspace list (#4270)

Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
Peng Xiao
2023-09-08 01:30:21 +08:00
committed by GitHub
parent 719917d445
commit d54f027f50
12 changed files with 72 additions and 154 deletions
+7 -7
View File
@@ -7,11 +7,11 @@ import { WorkspaceFallback } from '@affine/component/workspace';
import { CacheProvider } from '@emotion/react'; import { CacheProvider } from '@emotion/react';
import { getCurrentStore } from '@toeverything/infra/atom'; import { getCurrentStore } from '@toeverything/infra/atom';
import { use } from 'foxact/use'; import { use } from 'foxact/use';
import { SessionProvider } from 'next-auth/react';
import type { PropsWithChildren, ReactElement } from 'react'; import type { PropsWithChildren, ReactElement } from 'react';
import { lazy, memo, Suspense } from 'react'; import { lazy, memo, Suspense } from 'react';
import { RouterProvider } from 'react-router-dom'; import { RouterProvider } from 'react-router-dom';
import { CloudSessionProvider } from './providers/session-provider';
import { router } from './router'; import { router } from './router';
import createEmotionCache from './utils/create-emotion-cache'; import createEmotionCache from './utils/create-emotion-cache';
@@ -48,9 +48,9 @@ const languageLoadingPromise = loadLanguage().catch(console.error);
export const App = memo(function App() { export const App = memo(function App() {
use(languageLoadingPromise); use(languageLoadingPromise);
return ( return (
<SessionProvider refetchOnWindowFocus> <CacheProvider value={cache}>
<CacheProvider value={cache}> <AffineContext store={getCurrentStore()}>
<AffineContext store={getCurrentStore()}> <CloudSessionProvider>
<DebugProvider> <DebugProvider>
<RouterProvider <RouterProvider
fallbackElement={<WorkspaceFallback key="RouterFallback" />} fallbackElement={<WorkspaceFallback key="RouterFallback" />}
@@ -58,8 +58,8 @@ export const App = memo(function App() {
future={future} future={future}
/> />
</DebugProvider> </DebugProvider>
</AffineContext> </CloudSessionProvider>
</CacheProvider> </AffineContext>
</SessionProvider> </CacheProvider>
); );
}); });
@@ -2,9 +2,7 @@ import {
AuthModal as AuthModalBase, AuthModal as AuthModalBase,
type AuthModalProps as AuthModalBaseProps, type AuthModalProps as AuthModalBaseProps,
} from '@affine/component/auth-components'; } from '@affine/component/auth-components';
import { refreshRootMetadataAtom } from '@affine/workspace/atom'; import { type FC, useCallback, useMemo } from 'react';
import { useSetAtom } from 'jotai';
import { type FC, startTransition, useCallback, useMemo } from 'react';
import { AfterSignInSendEmail } from './after-sign-in-send-email'; import { AfterSignInSendEmail } from './after-sign-in-send-email';
import { AfterSignUpSendEmail } from './after-sign-up-send-email'; import { AfterSignUpSendEmail } from './after-sign-up-send-email';
@@ -60,14 +58,9 @@ export const AuthModal: FC<AuthModalBaseProps & AuthProps> = ({
setEmailType, setEmailType,
emailType, emailType,
}) => { }) => {
const refreshMetadata = useSetAtom(refreshRootMetadataAtom);
const onSignedIn = useCallback(() => { const onSignedIn = useCallback(() => {
setOpen(false); setOpen(false);
startTransition(() => { }, [setOpen]);
refreshMetadata();
});
}, [refreshMetadata, setOpen]);
return ( return (
<AuthModalBase open={open} setOpen={setOpen}> <AuthModalBase open={open} setOpen={setOpen}>
@@ -4,10 +4,8 @@ import {
BackButton, BackButton,
ModalHeader, ModalHeader,
} from '@affine/component/auth-components'; } from '@affine/component/auth-components';
import { pushNotificationAtom } from '@affine/component/notification-center';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Button } from '@toeverything/components/button'; import { Button } from '@toeverything/components/button';
import { useSetAtom } from 'jotai';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports // eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import type { FC } from 'react'; import type { FC } from 'react';
@@ -25,8 +23,6 @@ export const SignInWithPassword: FC<AuthPanelProps> = ({
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const { update } = useSession(); const { update } = useSession();
const pushNotification = useSetAtom(pushNotificationAtom);
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [passwordError, setPasswordError] = useState(false); const [passwordError, setPasswordError] = useState(false);
@@ -43,12 +39,7 @@ export const SignInWithPassword: FC<AuthPanelProps> = ({
await update(); await update();
onSignedIn?.(); onSignedIn?.();
pushNotification({ }, [email, password, onSignedIn, update]);
title: t['com.affine.auth.has.signed'](),
message: t['com.affine.auth.has.signed.message'](),
type: 'success',
});
}, [email, password, pushNotification, onSignedIn, t, update]);
return ( return (
<> <>
@@ -1,26 +0,0 @@
import { Modal, ModalWrapper } from '@affine/component';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import * as styles from './styles.css';
export const DesktopLoginModal = ({
signingEmail,
}: {
signingEmail?: string;
}) => {
const t = useAFFiNEI18N();
return (
<Modal open={!!signingEmail}>
<ModalWrapper className={styles.root}>
<div className={styles.title}>
{t['com.affine.auth.desktop.signing.in']()}
</div>
<Trans i18nKey="com.affine.auth.desktop.signing.in.message">
Signing in with account {signingEmail}
</Trans>
</ModalWrapper>
</Modal>
);
};
@@ -1,11 +0,0 @@
import { style } from '@vanilla-extract/css';
export const root = style({
padding: '20px 24px',
});
export const title = style({
fontSize: 'var(--affine-font-h-6)',
fontWeight: 600,
marginBottom: 12,
});
+4 -7
View File
@@ -4,7 +4,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { fetcher } from '@affine/workspace/affine/gql'; import { fetcher } from '@affine/workspace/affine/gql';
import { Logo1Icon } from '@blocksuite/icons'; import { Logo1Icon } from '@blocksuite/icons';
import { Button } from '@toeverything/components/button'; import { Button } from '@toeverything/components/button';
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { import {
type LoaderFunction, type LoaderFunction,
useLoaderData, useLoaderData,
@@ -72,15 +72,12 @@ const OpenAppImpl = ({ urlToOpen, channel }: OpenAppProps) => {
const [params] = useSearchParams(); const [params] = useSearchParams();
const autoOpen = useMemo(() => params.get('open') !== 'false', [params]); const autoOpen = useMemo(() => params.get('open') !== 'false', [params]);
useEffect(() => { if (urlToOpen && lastOpened !== urlToOpen && autoOpen) {
if (!urlToOpen || lastOpened === urlToOpen || !autoOpen) { lastOpened = urlToOpen;
return;
}
setTimeout(() => { setTimeout(() => {
lastOpened = urlToOpen;
open(urlToOpen, '_blank'); open(urlToOpen, '_blank');
}, 1000); }, 1000);
}, [urlToOpen, autoOpen]); }
if (!urlToOpen) { if (!urlToOpen) {
return null; return null;
@@ -1,7 +1,4 @@
import { pushNotificationAtom } from '@affine/component/notification-center';
import { isDesktop } from '@affine/env/constant';
import { WorkspaceSubPath } from '@affine/env/workspace'; import { WorkspaceSubPath } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom'; import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import { assertExists } from '@blocksuite/global/utils'; import { assertExists } from '@blocksuite/global/utils';
import { arrayMove } from '@dnd-kit/sortable'; import { arrayMove } from '@dnd-kit/sortable';
@@ -16,8 +13,6 @@ import {
startTransition, startTransition,
Suspense, Suspense,
useCallback, useCallback,
useEffect,
useState,
useTransition, useTransition,
} from 'react'; } from 'react';
@@ -43,12 +38,6 @@ const Auth = lazy(() =>
})) }))
); );
const DesktopLogin = lazy(() =>
import('../components/affine/desktop-login-modal').then(module => ({
default: module.DesktopLoginModal,
}))
);
const WorkspaceListModal = lazy(() => const WorkspaceListModal = lazy(() =>
import('../components/pure/workspace-list-modal').then(module => ({ import('../components/pure/workspace-list-modal').then(module => ({
default: module.WorkspaceListModal, default: module.WorkspaceListModal,
@@ -146,49 +135,6 @@ export const AuthModal = (): ReactElement => {
); );
}; };
export const DesktopLoginModal = (): ReactElement => {
const [signingEmail, setSigningEmail] = useState<string>();
const setAuthAtom = useSetAtom(authAtom);
const pushNotification = useSetAtom(pushNotificationAtom);
const t = useAFFiNEI18N();
// hack for closing the potentially opened auth modal
const closeAuthModal = useCallback(() => {
setAuthAtom(prev => ({ ...prev, openModal: false }));
}, [setAuthAtom]);
useEffect(() => {
return window.events?.ui.onStartLogin(opts => {
setSigningEmail(opts.email);
});
}, []);
useEffect(() => {
return window.events?.ui.onFinishLogin(({ success, email }) => {
if (email && email !== signingEmail) {
return;
}
setSigningEmail(undefined);
closeAuthModal();
if (success) {
pushNotification({
title: t['com.affine.auth.toast.title.signed-in'](),
message: t['com.affine.auth.toast.message.signed-in'](),
type: 'success',
});
} else {
pushNotification({
title: t['com.affine.auth.toast.title.failed'](),
message: t['com.affine.auth.toast.message.failed'](),
type: 'error',
});
}
});
}, [closeAuthModal, pushNotification, signingEmail, t]);
return <DesktopLogin signingEmail={signingEmail} />;
};
export function CurrentWorkspaceModals() { export function CurrentWorkspaceModals() {
const [currentWorkspace] = useCurrentWorkspace(); const [currentWorkspace] = useCurrentWorkspace();
const [openDisableCloudAlertModal, setOpenDisableCloudAlertModal] = useAtom( const [openDisableCloudAlertModal, setOpenDisableCloudAlertModal] = useAtom(
@@ -323,7 +269,6 @@ export const AllWorkspaceModals = (): ReactElement => {
<Suspense> <Suspense>
<AuthModal /> <AuthModal />
</Suspense> </Suspense>
{isDesktop && <DesktopLoginModal />}
</> </>
); );
}; };
@@ -0,0 +1,50 @@
import '@toeverything/hooks/use-affine-ipc-renderer';
import { pushNotificationAtom } from '@affine/component/notification-center';
import { isDesktop } from '@affine/env/constant';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { refreshRootMetadataAtom } from '@affine/workspace/atom';
import { useSetAtom } from 'jotai';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { SessionProvider, useSession } from 'next-auth/react';
import { type PropsWithChildren, startTransition, useRef } from 'react';
const SessionReporter = () => {
const session = useSession();
const prevSession = useRef<ReturnType<typeof useSession>>();
const pushNotification = useSetAtom(pushNotificationAtom);
const refreshMetadata = useSetAtom(refreshRootMetadataAtom);
const t = useAFFiNEI18N();
if (prevSession.current !== session && session.status !== 'loading') {
// unauthenticated -> authenticated
if (
prevSession.current?.status === 'unauthenticated' &&
session.status === 'authenticated'
) {
startTransition(() => {
refreshMetadata();
});
pushNotification({
title: t['com.affine.auth.has.signed'](),
message: t['com.affine.auth.has.signed.message'](),
type: 'success',
});
if (isDesktop) {
window.affine.ipcRenderer.send('affine:login');
}
}
prevSession.current = session;
}
return null;
};
export const CloudSessionProvider = ({ children }: PropsWithChildren) => {
return (
<SessionProvider refetchOnWindowFocus>
<SessionReporter />
{children}
</SessionProvider>
);
};
+8 -9
View File
@@ -1,6 +1,6 @@
import path from 'node:path'; import path from 'node:path';
import type { App } from 'electron'; import { type App, type BrowserWindow, ipcMain } from 'electron';
import { buildType, CLOUD_BASE_URL, isDev } from './config'; import { buildType, CLOUD_BASE_URL, isDev } from './config';
import { logger } from './logger'; import { logger } from './logger';
@@ -11,7 +11,6 @@ import {
restoreOrCreateWindow, restoreOrCreateWindow,
setCookie, setCookie,
} from './main-window'; } from './main-window';
import { uiSubjects } from './ui';
let protocol = buildType === 'stable' ? 'affine' : `affine-${buildType}`; let protocol = buildType === 'stable' ? 'affine' : `affine-${buildType}`;
if (isDev) { if (isDev) {
@@ -100,16 +99,16 @@ async function handleOauthJwt(url: string) {
isSecure ? '__Secure-next-auth.callback-url' : 'next-auth.callback-url' isSecure ? '__Secure-next-auth.callback-url' : 'next-auth.callback-url'
); );
let hiddenWindow: BrowserWindow | null = null;
ipcMain.once('affine:login', () => {
hiddenWindow?.destroy();
});
// hacks to refresh auth state in the main window // hacks to refresh auth state in the main window
const window = await handleOpenUrlInHiddenWindow( hiddenWindow = await handleOpenUrlInHiddenWindow(
mainWindowOrigin + '/auth/signIn' mainWindowOrigin + '/auth/signIn'
); );
uiSubjects.onFinishLogin.next({
success: true,
});
setTimeout(() => {
window.destroy();
}, 3000);
} catch (e) { } catch (e) {
logger.error('failed to open url in popup', e); logger.error('failed to open url in popup', e);
} }
-14
View File
@@ -5,20 +5,6 @@ import { uiSubjects } from './subject';
* Events triggered by application menu * Events triggered by application menu
*/ */
export const uiEvents = { export const uiEvents = {
onFinishLogin: (
fn: (result: { success: boolean; email?: string }) => void
) => {
const sub = uiSubjects.onFinishLogin.subscribe(fn);
return () => {
sub.unsubscribe();
};
},
onStartLogin: (fn: (opts: { email?: string }) => void) => {
const sub = uiSubjects.onStartLogin.subscribe(fn);
return () => {
sub.unsubscribe();
};
},
onMaximized: (fn: (maximized: boolean) => void) => { onMaximized: (fn: (maximized: boolean) => void) => {
const sub = uiSubjects.onMaximized.subscribe(fn); const sub = uiSubjects.onMaximized.subscribe(fn);
return () => { return () => {
-2
View File
@@ -1,7 +1,5 @@
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
export const uiSubjects = { export const uiSubjects = {
onStartLogin: new Subject<{ email?: string }>(),
onFinishLogin: new Subject<{ success: boolean; email?: string }>(),
onMaximized: new Subject<boolean>(), onMaximized: new Subject<boolean>(),
}; };
-4
View File
@@ -266,10 +266,6 @@ export interface WorkspaceEvents {
} }
export interface UIEvents { export interface UIEvents {
onStartLogin: (fn: (options: { email?: string }) => void) => () => void;
onFinishLogin: (
fn: (result: { success: boolean; email?: string }) => void
) => () => void;
onMaximized: (fn: (maximized: boolean) => void) => () => void; onMaximized: (fn: (maximized: boolean) => void) => () => void;
} }