mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-11-22 08:24:44 +03:00
fix: reload the page when login token expired (#1839)
This commit is contained in:
parent
5ac36b6f0a
commit
efe5444816
@ -21,14 +21,22 @@ const revalidate = async () => {
|
||||
const response = await affineAuth.refreshToken(storage);
|
||||
if (response) {
|
||||
setLoginStorage(response);
|
||||
|
||||
// todo: need to notify the app that the token has been refreshed
|
||||
// this is a hack to force a reload
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export function useAffineRefreshAuthToken() {
|
||||
export function useAffineRefreshAuthToken(
|
||||
// every 30 seconds, check if the token is expired
|
||||
refreshInterval = 30 * 1000
|
||||
) {
|
||||
useSWR('autoRefreshToken', {
|
||||
fetcher: revalidate,
|
||||
refreshInterval,
|
||||
});
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import {
|
||||
import { HelpIsland } from '../components/pure/help-island';
|
||||
import { PageLoading } from '../components/pure/loading';
|
||||
import WorkSpaceSliderBar from '../components/pure/workspace-slider-bar';
|
||||
import { useAffineRefreshAuthToken } from '../hooks/affine/use-affine-refresh-auth-token';
|
||||
import {
|
||||
useSidebarFloating,
|
||||
useSidebarResizing,
|
||||
@ -187,11 +186,6 @@ export const WorkspaceLayout: FC<PropsWithChildren> =
|
||||
);
|
||||
};
|
||||
|
||||
function AffineWorkspaceEffect() {
|
||||
useAffineRefreshAuthToken();
|
||||
return null;
|
||||
}
|
||||
|
||||
export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const [currentPageId] = useCurrentPageId();
|
||||
@ -348,7 +342,6 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
|
||||
</StyledSpacer>
|
||||
<MainContainerWrapper resizing={resizing} style={{ width: mainWidth }}>
|
||||
<MainContainer className="main-container">
|
||||
<AffineWorkspaceEffect />
|
||||
{children}
|
||||
<StyledToolWrapper>
|
||||
{/* fixme(himself65): remove this */}
|
||||
|
49
apps/web/src/plugins/affine/__tests__/index.spec.tsx
Normal file
49
apps/web/src/plugins/affine/__tests__/index.spec.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import {
|
||||
getLoginStorage,
|
||||
isExpired,
|
||||
loginResponseSchema,
|
||||
parseIdToken,
|
||||
setLoginStorage,
|
||||
} from '@affine/workspace/affine/login';
|
||||
import user1 from '@affine-test/fixtures/built-in-user1.json';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { afterEach, describe, expect, test } from 'vitest';
|
||||
|
||||
import { useAffineRefreshAuthToken } from '../../../hooks/affine/use-affine-refresh-auth-token';
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
describe('AFFiNE workspace', () => {
|
||||
test('Provider', async () => {
|
||||
expect(getLoginStorage()).toBeNull();
|
||||
const data = await fetch('http://127.0.0.1:3000/api/user/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: 'DebugLoginUser',
|
||||
email: user1.email,
|
||||
password: user1.password,
|
||||
}),
|
||||
}).then(r => r.json());
|
||||
loginResponseSchema.parse(data);
|
||||
setLoginStorage({
|
||||
// expired token that already expired
|
||||
token:
|
||||
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2ODA4MjE0OTQsImlkIjoiaFd0dkFoM1E3SGhiWVlNeGxyX1I0IiwibmFtZSI6ImRlYnVnMSIsImVtYWlsIjoiZGVidWcxQHRvZXZlcnl0aGluZy5pbmZvIiwiYXZhdGFyX3VybCI6bnVsbCwiY3JlYXRlZF9hdCI6MTY4MDgxNTcxMTAwMH0.fDSkbM-ovmGD21sKYSTuiqC1dTiceOfcgIUfI2dLsBk',
|
||||
// but refresh is still valid
|
||||
refresh: data.refresh,
|
||||
});
|
||||
renderHook(() => useAffineRefreshAuthToken(1));
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
const userData = parseIdToken(getLoginStorage()?.token as string);
|
||||
expect(userData).not.toBeNull();
|
||||
expect(isExpired(userData)).toBe(false);
|
||||
});
|
||||
});
|
@ -21,9 +21,10 @@ import { PageNotFoundError } from '../../components/affine/affine-error-eoundary
|
||||
import { WorkspaceSettingDetail } from '../../components/affine/workspace-setting-detail';
|
||||
import { BlockSuitePageList } from '../../components/blocksuite/block-suite-page-list';
|
||||
import { PageDetailEditor } from '../../components/page-detail-editor';
|
||||
import { useAffineRefreshAuthToken } from '../../hooks/affine/use-affine-refresh-auth-token';
|
||||
import { AffineSWRConfigProvider } from '../../providers/AffineSWRConfigProvider';
|
||||
import { BlockSuiteWorkspace } from '../../shared';
|
||||
import { affineApis } from '../../shared/apis';
|
||||
import { affineApis, prefixUrl } from '../../shared/apis';
|
||||
import { initPage, toast } from '../../utils';
|
||||
import type { WorkspacePlugin } from '..';
|
||||
import { QueryKey } from './fetcher';
|
||||
@ -65,7 +66,7 @@ const getPersistenceAllWorkspace = () => {
|
||||
return allWorkspaces;
|
||||
};
|
||||
|
||||
export const affineAuth = createAffineAuth();
|
||||
export const affineAuth = createAffineAuth(prefixUrl);
|
||||
|
||||
export const AffinePlugin: WorkspacePlugin<WorkspaceFlavour.AFFINE> = {
|
||||
flavour: WorkspaceFlavour.AFFINE,
|
||||
@ -231,6 +232,7 @@ export const AffinePlugin: WorkspacePlugin<WorkspaceFlavour.AFFINE> = {
|
||||
},
|
||||
UI: {
|
||||
Provider: ({ children }) => {
|
||||
useAffineRefreshAuthToken();
|
||||
return <AffineSWRConfigProvider>{children}</AffineSWRConfigProvider>;
|
||||
},
|
||||
PageDetail: ({ currentWorkspace, currentPageId }) => {
|
||||
|
@ -26,6 +26,8 @@ if (typeof window === 'undefined') {
|
||||
params.get('prefixUrl') && (prefixUrl = params.get('prefixUrl') as string);
|
||||
}
|
||||
|
||||
export { prefixUrl };
|
||||
|
||||
const affineApis = {} as ReturnType<typeof createUserApis> &
|
||||
ReturnType<typeof createWorkspaceApis>;
|
||||
Object.assign(affineApis, createUserApis(prefixUrl));
|
||||
|
@ -29,14 +29,16 @@ describe('storage', () => {
|
||||
describe('utils', () => {
|
||||
test('isExpired', async () => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
expect(isExpired({ exp: now + 1 } as AccessTokenMessage)).toBeFalsy();
|
||||
expect(isExpired({ exp: now + 1 } as AccessTokenMessage, 0)).toBeFalsy();
|
||||
const promise = new Promise<void>(resolve => {
|
||||
setTimeout(() => {
|
||||
expect(isExpired({ exp: now + 1 } as AccessTokenMessage)).toBeTruthy();
|
||||
expect(
|
||||
isExpired({ exp: now + 1 } as AccessTokenMessage, 0)
|
||||
).toBeTruthy();
|
||||
resolve();
|
||||
}, 2000);
|
||||
});
|
||||
expect(isExpired({ exp: now - 1 } as AccessTokenMessage)).toBeTruthy();
|
||||
expect(isExpired({ exp: now - 1 } as AccessTokenMessage, 0)).toBeTruthy();
|
||||
await promise;
|
||||
});
|
||||
});
|
||||
|
@ -43,9 +43,13 @@ export function parseIdToken(token: string): AccessTokenMessage {
|
||||
return JSON.parse(decode(token.split('.')[1]));
|
||||
}
|
||||
|
||||
export const isExpired = (token: AccessTokenMessage): boolean => {
|
||||
export const isExpired = (
|
||||
token: AccessTokenMessage,
|
||||
// earlier than `before`, consider it expired
|
||||
before = 60 // 1 minute
|
||||
): boolean => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
return token.exp < now;
|
||||
return token.exp < now - before;
|
||||
};
|
||||
|
||||
export const setLoginStorage = (login: LoginResponse) => {
|
||||
|
3
scripts/setup/search.ts
Normal file
3
scripts/setup/search.ts
Normal file
@ -0,0 +1,3 @@
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.search = '?prefixUrl=http://127.0.0.1:3000/';
|
||||
}
|
@ -6,7 +6,7 @@ export default function getConfig() {
|
||||
gitVersion: 'UNKNOWN',
|
||||
hash: 'UNKNOWN',
|
||||
editorVersion: 'UNKNOWN',
|
||||
serverAPI: 'http://localhost:3000/',
|
||||
serverAPI: 'http://127.0.0.1:3000/',
|
||||
enableBroadCastChannelProvider: true,
|
||||
enableIndexedDBProvider: true,
|
||||
enableDebugPage: true,
|
||||
|
@ -14,6 +14,7 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
test: {
|
||||
setupFiles: ['./scripts/setup/search.ts'],
|
||||
include: [
|
||||
'packages/**/*.spec.ts',
|
||||
'packages/**/*.spec.tsx',
|
||||
|
Loading…
Reference in New Issue
Block a user