mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-21 20:16:26 +08:00
feat: cleanup fetcher for prepare refactor
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { signInWithGoogle, login, setToken } from '@pathfinder/data-services';
|
import { signInWithGoogle } from '@pathfinder/data-services';
|
||||||
import { styled } from '@/styles';
|
import { styled } from '@/styles';
|
||||||
import { Button } from '@/ui/button';
|
import { Button } from '@/ui/button';
|
||||||
import { useModal } from '@/providers/global-modal-provider';
|
import { useModal } from '@/providers/global-modal-provider';
|
||||||
@@ -10,13 +10,7 @@ export const GoogleLoginButton = () => {
|
|||||||
<StyledGoogleButton
|
<StyledGoogleButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
signInWithGoogle()
|
signInWithGoogle()
|
||||||
.then(async user => {
|
.then(async => {
|
||||||
const idToken = await user.user.getIdToken();
|
|
||||||
const token = await login({ token: idToken, type: 'Google' });
|
|
||||||
setToken({
|
|
||||||
accessToken: token.token,
|
|
||||||
refreshToken: token.refresh,
|
|
||||||
});
|
|
||||||
triggerLoginModal();
|
triggerLoginModal();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
|||||||
const editor = createEditorHandler?.(currentPage) || null;
|
const editor = createEditorHandler?.(currentPage) || null;
|
||||||
|
|
||||||
if (editor) {
|
if (editor) {
|
||||||
const pageMeta = currentWorkspace?.meta.pageMetas.find(
|
const pageMeta = currentWorkspace.meta.pageMetas.find(
|
||||||
p => p.id === currentPage.pageId
|
p => p.id === currentPage.pageId
|
||||||
);
|
);
|
||||||
if (pageMeta?.mode) {
|
if (pageMeta?.mode) {
|
||||||
@@ -133,13 +133,13 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
|
|||||||
const callback = async (user: AccessTokenMessage | null) => {
|
const callback = async (user: AccessTokenMessage | null) => {
|
||||||
const workspacesMeta = user ? await getWorkspaces() : [];
|
const workspacesMeta = user ? await getWorkspaces() : [];
|
||||||
const workspacesList = await Promise.all(
|
const workspacesList = await Promise.all(
|
||||||
workspacesMeta?.map(async ({ id }) => {
|
workspacesMeta.map(async ({ id }) => {
|
||||||
const workspace = (await loadWorkspaceHandler?.(id)) || null;
|
const workspace = (await loadWorkspaceHandler?.(id)) || null;
|
||||||
return { id, workspace };
|
return { id, workspace };
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
let workspaces: Record<string, Workspace | null> = {};
|
const workspaces: Record<string, Workspace | null> = {};
|
||||||
|
|
||||||
workspacesList.forEach(({ id, workspace }) => {
|
workspacesList.forEach(({ id, workspace }) => {
|
||||||
workspaces[id] = workspace;
|
workspaces[id] = workspace;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"axios": "^1.2.0",
|
"axios": "^1.2.0",
|
||||||
"encoding": "^0.1.13",
|
"encoding": "^0.1.13",
|
||||||
"firebase": "^9.13.0",
|
"firebase": "^9.13.0",
|
||||||
|
"ky": "^0.33.0",
|
||||||
"swr": "^2.0.0"
|
"swr": "^2.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import {
|
|||||||
signInWithPopup,
|
signInWithPopup,
|
||||||
} from 'firebase/auth';
|
} from 'firebase/auth';
|
||||||
import type { User } from 'firebase/auth';
|
import type { User } from 'firebase/auth';
|
||||||
|
import { login } from './sdks';
|
||||||
|
import { setToken } from './request';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* firebaseConfig reference: https://firebase.google.com/docs/web/setup#add_firebase_to_your_app
|
* firebaseConfig reference: https://firebase.google.com/docs/web/setup#add_firebase_to_your_app
|
||||||
@@ -32,8 +34,14 @@ const signIn = (email: string, password: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const googleAuthProvider = new GoogleAuthProvider();
|
const googleAuthProvider = new GoogleAuthProvider();
|
||||||
export const signInWithGoogle = () => {
|
export const signInWithGoogle = async () => {
|
||||||
return signInWithPopup(firebaseAuth, googleAuthProvider);
|
const user = await signInWithPopup(firebaseAuth, googleAuthProvider);
|
||||||
|
const idToken = await user.user.getIdToken();
|
||||||
|
const token = await login({ token: idToken, type: 'Google' });
|
||||||
|
setToken({
|
||||||
|
accessToken: token.token,
|
||||||
|
refreshToken: token.refresh,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onAuthStateChanged = (callback: (user: User | null) => void) => {
|
export const onAuthStateChanged = (callback: (user: User | null) => void) => {
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
export class ServiceError extends Error {
|
|
||||||
public message: string;
|
|
||||||
public code: string;
|
|
||||||
constructor(code: string, message: string) {
|
|
||||||
super(message);
|
|
||||||
this.message = message;
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,8 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { setAuthorization } from './request';
|
import { getToken, isAccessTokenExpired, refreshToken } from './token';
|
||||||
import { handleResponseError, handleResponse } from './response';
|
|
||||||
|
|
||||||
declare module 'axios' {
|
declare module 'axios' {
|
||||||
interface AxiosRequestConfig {
|
interface AxiosRequestConfig {
|
||||||
/**
|
|
||||||
* If true, request will send with Authorization header.
|
|
||||||
*/
|
|
||||||
withAuthorization?: boolean;
|
|
||||||
/**
|
/**
|
||||||
* If true, indicate this request is refreshing token.
|
* If true, indicate this request is refreshing token.
|
||||||
*/
|
*/
|
||||||
@@ -15,9 +10,52 @@ declare module 'axios' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const request = axios.create({
|
export const request = axios.create();
|
||||||
withAuthorization: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
request.interceptors.request.use(setAuthorization);
|
request.interceptors.request.use(async config => {
|
||||||
request.interceptors.response.use(handleResponse, handleResponseError);
|
if (!config.headers) {
|
||||||
|
config.headers = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getToken();
|
||||||
|
if (token) {
|
||||||
|
if (isAccessTokenExpired(token.accessToken)) {
|
||||||
|
await refreshToken();
|
||||||
|
}
|
||||||
|
if (!config.headers) {
|
||||||
|
config.headers = {};
|
||||||
|
}
|
||||||
|
config.headers['Authorization'] = token.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
request.interceptors.response.use(
|
||||||
|
response => {
|
||||||
|
const { status, data } = response;
|
||||||
|
if (status === 200) {
|
||||||
|
if (data.error) {
|
||||||
|
// TODO - common error handling
|
||||||
|
throw new Error(data.error.message, { cause: data.error.code });
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
async error => {
|
||||||
|
const { response, config, message } = error;
|
||||||
|
const status = response?.status;
|
||||||
|
|
||||||
|
if (status === 401) {
|
||||||
|
if (!config) {
|
||||||
|
throw new Error(message, { cause: status.toString() });
|
||||||
|
}
|
||||||
|
if (config.refreshTokenRequest) {
|
||||||
|
throw new Error(message, { cause: status.toString() });
|
||||||
|
}
|
||||||
|
await refreshToken();
|
||||||
|
return request({ ...config, refreshTokenRequest: true });
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import type { AccessTokenMessage } from './types';
|
import { AccessTokenMessage } from './token';
|
||||||
|
|
||||||
type Callback = (user: AccessTokenMessage | null) => void;
|
type Callback = (user: AccessTokenMessage | null) => void;
|
||||||
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
export { request } from './axios';
|
export { request } from './axios';
|
||||||
export { ServiceError } from './ServiceError';
|
export type { AccessTokenMessage } from './token';
|
||||||
export { setToken, getToken, authorizationEvent } from './request';
|
export { authorizationEvent, getToken, setToken } from './token';
|
||||||
export type { AccessTokenMessage } from './request';
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './authorization';
|
|
||||||
export type { AccessTokenMessage } from './types';
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export interface AccessTokenMessage {
|
|
||||||
create_at: number;
|
|
||||||
exp: number;
|
|
||||||
email: string;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
avatar_url: string;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export {
|
|
||||||
setAuthorization,
|
|
||||||
setToken,
|
|
||||||
getToken,
|
|
||||||
authorizationEvent,
|
|
||||||
} from './authorization';
|
|
||||||
export type { AccessTokenMessage } from './authorization';
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import type { AxiosResponse, AxiosError } from 'axios';
|
|
||||||
import { ServiceError } from '../ServiceError';
|
|
||||||
import { refreshToken } from '../request/authorization';
|
|
||||||
import { request } from '../axios';
|
|
||||||
|
|
||||||
export async function handleResponseError(error: AxiosError<any, any>) {
|
|
||||||
const { response, config, message } = error;
|
|
||||||
const status = response?.status;
|
|
||||||
|
|
||||||
if (status === 401) {
|
|
||||||
if (!config) {
|
|
||||||
throw new ServiceError(message, status.toString());
|
|
||||||
}
|
|
||||||
if (config.refreshTokenRequest) {
|
|
||||||
throw new ServiceError(message, status.toString());
|
|
||||||
}
|
|
||||||
await refreshToken();
|
|
||||||
return request({ ...config, refreshTokenRequest: true });
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleResponse(response: AxiosResponse<any, any>) {
|
|
||||||
const { status, data } = response;
|
|
||||||
if (status === 200) {
|
|
||||||
if (data.error) {
|
|
||||||
// TODO - common error handling
|
|
||||||
const error = new ServiceError(data.error.message, data.error.code);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { handleResponseError, handleResponse } from './handle-error';
|
|
||||||
+39
-68
@@ -1,44 +1,17 @@
|
|||||||
import type { AxiosRequestConfig } from 'axios';
|
|
||||||
import { login } from '../../../sdks';
|
|
||||||
import { AuthorizationEvent } from './events';
|
import { AuthorizationEvent } from './events';
|
||||||
import type { AccessTokenMessage } from './types';
|
import { login } from '../sdks';
|
||||||
|
|
||||||
|
export interface AccessTokenMessage {
|
||||||
|
create_at: number;
|
||||||
|
exp: number;
|
||||||
|
email: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
avatar_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
const TOKEN_KEY = 'affine_token';
|
const TOKEN_KEY = 'affine_token';
|
||||||
|
|
||||||
export const authorizationEvent = new AuthorizationEvent();
|
|
||||||
authorizationEvent.triggerChange(
|
|
||||||
parseAccessToken(getToken()?.accessToken || '')
|
|
||||||
);
|
|
||||||
|
|
||||||
interface Token {
|
|
||||||
accessToken: string;
|
|
||||||
refreshToken: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set null to clear token
|
|
||||||
*/
|
|
||||||
export function setToken(token: Token | null): void {
|
|
||||||
if (token === null) {
|
|
||||||
window.localStorage.removeItem(TOKEN_KEY);
|
|
||||||
authorizationEvent.triggerChange(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.localStorage.setItem(TOKEN_KEY, JSON.stringify(token));
|
|
||||||
authorizationEvent.triggerChange(parseAccessToken(token.accessToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getToken(): {
|
|
||||||
accessToken: string;
|
|
||||||
refreshToken: string;
|
|
||||||
} | null {
|
|
||||||
try {
|
|
||||||
return JSON.parse(window.localStorage.getItem(TOKEN_KEY) || '');
|
|
||||||
} catch (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function b64DecodeUnicode(str: string) {
|
function b64DecodeUnicode(str: string) {
|
||||||
// Going backwards: from byte stream, to percent-encoding, to original string.
|
// Going backwards: from byte stream, to percent-encoding, to original string.
|
||||||
return decodeURIComponent(
|
return decodeURIComponent(
|
||||||
@@ -64,7 +37,7 @@ function parseAccessToken(token: string): AccessTokenMessage | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAccessTokenExpired(token: string) {
|
export function isAccessTokenExpired(token: string) {
|
||||||
const message = parseAccessToken(token);
|
const message = parseAccessToken(token);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return true;
|
return true;
|
||||||
@@ -72,41 +45,39 @@ function isAccessTokenExpired(token: string) {
|
|||||||
return Date.now() - message.create_at > message.exp;
|
return Date.now() - message.create_at > message.exp;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isLoggedIn(): Promise<boolean> {
|
export function getToken(): {
|
||||||
const token = getToken();
|
accessToken: string;
|
||||||
if (!token) {
|
refreshToken: string;
|
||||||
return false;
|
} | null {
|
||||||
|
try {
|
||||||
|
return JSON.parse(window.localStorage.getItem(TOKEN_KEY) || '');
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return isAccessTokenExpired(token.accessToken);
|
}
|
||||||
|
|
||||||
|
export const authorizationEvent = new AuthorizationEvent();
|
||||||
|
authorizationEvent.triggerChange(
|
||||||
|
parseAccessToken(getToken()?.accessToken || '')
|
||||||
|
);
|
||||||
|
|
||||||
|
interface Token {
|
||||||
|
accessToken: string;
|
||||||
|
refreshToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setToken(token: Token | null): void {
|
||||||
|
if (token === null) {
|
||||||
|
window.localStorage.removeItem(TOKEN_KEY);
|
||||||
|
authorizationEvent.triggerChange(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.localStorage.setItem(TOKEN_KEY, JSON.stringify(token));
|
||||||
|
authorizationEvent.triggerChange(parseAccessToken(token.accessToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
let refreshingToken: ReturnType<typeof login> | undefined;
|
let refreshingToken: ReturnType<typeof login> | undefined;
|
||||||
|
|
||||||
export async function setAuthorization(config: AxiosRequestConfig<unknown>) {
|
|
||||||
if (!config.headers) {
|
|
||||||
config.headers = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.withAuthorization) {
|
|
||||||
let token = getToken();
|
|
||||||
if (!token) {
|
|
||||||
throw new Error('No authorization token.');
|
|
||||||
}
|
|
||||||
if (isAccessTokenExpired(token.accessToken)) {
|
|
||||||
await refreshToken();
|
|
||||||
token = getToken();
|
|
||||||
}
|
|
||||||
if (!token) {
|
|
||||||
throw new Error('No authorization token.');
|
|
||||||
}
|
|
||||||
if (!config.headers) {
|
|
||||||
config.headers = {};
|
|
||||||
}
|
|
||||||
config.headers['Authorization'] = token.accessToken;
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const refreshToken = async () => {
|
export const refreshToken = async () => {
|
||||||
let token = getToken();
|
let token = getToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@@ -32,7 +32,7 @@ export async function login(params: LoginParams): Promise<LoginResponse> {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: params,
|
data: params,
|
||||||
withAuthorization: false,
|
withAuthorization: false,
|
||||||
});
|
} as any);
|
||||||
return data.data;
|
return data.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Generated
+13
-3
@@ -117,12 +117,14 @@ importers:
|
|||||||
axios: ^1.2.0
|
axios: ^1.2.0
|
||||||
encoding: ^0.1.13
|
encoding: ^0.1.13
|
||||||
firebase: ^9.13.0
|
firebase: ^9.13.0
|
||||||
|
ky: ^0.33.0
|
||||||
swr: ^2.0.0
|
swr: ^2.0.0
|
||||||
typescript: ^4.8.4
|
typescript: ^4.8.4
|
||||||
dependencies:
|
dependencies:
|
||||||
axios: 1.2.0
|
axios: 1.2.1
|
||||||
encoding: 0.1.13
|
encoding: 0.1.13
|
||||||
firebase: 9.13.0_encoding@0.1.13
|
firebase: 9.13.0_encoding@0.1.13
|
||||||
|
ky: 0.33.0
|
||||||
swr: 2.0.0
|
swr: 2.0.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
typescript: 4.9.3
|
typescript: 4.9.3
|
||||||
@@ -3586,10 +3588,10 @@ packages:
|
|||||||
engines: { node: '>=4' }
|
engines: { node: '>=4' }
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/axios/1.2.0:
|
/axios/1.2.1:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
integrity: sha512-zT7wZyNYu3N5Bu0wuZ6QccIf93Qk1eV8LOewxgjOZFd2DenOs98cJ7+Y6703d0wkaXGY6/nZd4EweJaHz9uzQw==,
|
integrity: sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==,
|
||||||
}
|
}
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.2
|
follow-redirects: 1.15.2
|
||||||
@@ -6675,6 +6677,14 @@ packages:
|
|||||||
engines: { node: '>=14.16' }
|
engines: { node: '>=14.16' }
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/ky/0.33.0:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-peKzuOlN/q3Q3jOgi4t0cp6DOgif5rVnmiSIsjsmkiOcdnSjkrKSUqQmRWYCTqjUtR9b3xQQr8aj7KwSW1r49A==,
|
||||||
|
}
|
||||||
|
engines: { node: '>=14.16' }
|
||||||
|
dev: false
|
||||||
|
|
||||||
/language-subtag-registry/0.3.22:
|
/language-subtag-registry/0.3.22:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user