From b666fd8d0a785ba80a44165acd081f2950cd7d67 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 22 Dec 2022 15:09:49 +0800 Subject: [PATCH] feat: cleanup fetcher for prepare refactor --- .../login-modal/LoginOptionButton.tsx | 10 +- .../providers/app-state-provider/provider.tsx | 6 +- packages/data-services/package.json | 1 + packages/data-services/src/auth.ts | 12 +- .../data-services/src/request/ServiceError.ts | 9 -- packages/data-services/src/request/axios.ts | 60 ++++++++-- .../{request/authorization => }/events.ts | 2 +- packages/data-services/src/request/index.ts | 5 +- .../request/request/authorization/index.ts | 2 - .../request/request/authorization/types.ts | 8 -- .../src/request/request/index.ts | 7 -- .../src/request/response/handle-error.ts | 34 ------ .../src/request/response/index.ts | 1 - .../authorization.ts => token.ts} | 107 +++++++----------- packages/data-services/src/sdks/user.ts | 2 +- pnpm-lock.yaml | 16 ++- 16 files changed, 121 insertions(+), 161 deletions(-) delete mode 100644 packages/data-services/src/request/ServiceError.ts rename packages/data-services/src/request/{request/authorization => }/events.ts (92%) delete mode 100644 packages/data-services/src/request/request/authorization/index.ts delete mode 100644 packages/data-services/src/request/request/authorization/types.ts delete mode 100644 packages/data-services/src/request/request/index.ts delete mode 100644 packages/data-services/src/request/response/handle-error.ts delete mode 100644 packages/data-services/src/request/response/index.ts rename packages/data-services/src/request/{request/authorization/authorization.ts => token.ts} (68%) diff --git a/packages/app/src/components/login-modal/LoginOptionButton.tsx b/packages/app/src/components/login-modal/LoginOptionButton.tsx index 8070e51254..b10432214a 100644 --- a/packages/app/src/components/login-modal/LoginOptionButton.tsx +++ b/packages/app/src/components/login-modal/LoginOptionButton.tsx @@ -1,4 +1,4 @@ -import { signInWithGoogle, login, setToken } from '@pathfinder/data-services'; +import { signInWithGoogle } from '@pathfinder/data-services'; import { styled } from '@/styles'; import { Button } from '@/ui/button'; import { useModal } from '@/providers/global-modal-provider'; @@ -10,13 +10,7 @@ export const GoogleLoginButton = () => { { signInWithGoogle() - .then(async user => { - const idToken = await user.user.getIdToken(); - const token = await login({ token: idToken, type: 'Google' }); - setToken({ - accessToken: token.token, - refreshToken: token.refresh, - }); + .then(async => { triggerLoginModal(); }) .catch(error => { diff --git a/packages/app/src/providers/app-state-provider/provider.tsx b/packages/app/src/providers/app-state-provider/provider.tsx index 981fec9222..2e38922e06 100644 --- a/packages/app/src/providers/app-state-provider/provider.tsx +++ b/packages/app/src/providers/app-state-provider/provider.tsx @@ -94,7 +94,7 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => { const editor = createEditorHandler?.(currentPage) || null; if (editor) { - const pageMeta = currentWorkspace?.meta.pageMetas.find( + const pageMeta = currentWorkspace.meta.pageMetas.find( p => p.id === currentPage.pageId ); if (pageMeta?.mode) { @@ -133,13 +133,13 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => { const callback = async (user: AccessTokenMessage | null) => { const workspacesMeta = user ? await getWorkspaces() : []; const workspacesList = await Promise.all( - workspacesMeta?.map(async ({ id }) => { + workspacesMeta.map(async ({ id }) => { const workspace = (await loadWorkspaceHandler?.(id)) || null; return { id, workspace }; }) ); - let workspaces: Record = {}; + const workspaces: Record = {}; workspacesList.forEach(({ id, workspace }) => { workspaces[id] = workspace; diff --git a/packages/data-services/package.json b/packages/data-services/package.json index 6dc645fdd4..2af370726e 100644 --- a/packages/data-services/package.json +++ b/packages/data-services/package.json @@ -25,6 +25,7 @@ "axios": "^1.2.0", "encoding": "^0.1.13", "firebase": "^9.13.0", + "ky": "^0.33.0", "swr": "^2.0.0" } } diff --git a/packages/data-services/src/auth.ts b/packages/data-services/src/auth.ts index 9896fc7757..c8d9e0a301 100644 --- a/packages/data-services/src/auth.ts +++ b/packages/data-services/src/auth.ts @@ -7,6 +7,8 @@ import { signInWithPopup, } 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 @@ -32,8 +34,14 @@ const signIn = (email: string, password: string) => { }; const googleAuthProvider = new GoogleAuthProvider(); -export const signInWithGoogle = () => { - return signInWithPopup(firebaseAuth, googleAuthProvider); +export const signInWithGoogle = async () => { + 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) => { diff --git a/packages/data-services/src/request/ServiceError.ts b/packages/data-services/src/request/ServiceError.ts deleted file mode 100644 index 07ba217007..0000000000 --- a/packages/data-services/src/request/ServiceError.ts +++ /dev/null @@ -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; - } -} diff --git a/packages/data-services/src/request/axios.ts b/packages/data-services/src/request/axios.ts index 1b4bb6d73b..8693325837 100644 --- a/packages/data-services/src/request/axios.ts +++ b/packages/data-services/src/request/axios.ts @@ -1,13 +1,8 @@ import axios from 'axios'; -import { setAuthorization } from './request'; -import { handleResponseError, handleResponse } from './response'; +import { getToken, isAccessTokenExpired, refreshToken } from './token'; declare module 'axios' { interface AxiosRequestConfig { - /** - * If true, request will send with Authorization header. - */ - withAuthorization?: boolean; /** * If true, indicate this request is refreshing token. */ @@ -15,9 +10,52 @@ declare module 'axios' { } } -export const request = axios.create({ - withAuthorization: true, -}); +export const request = axios.create(); -request.interceptors.request.use(setAuthorization); -request.interceptors.response.use(handleResponse, handleResponseError); +request.interceptors.request.use(async config => { + 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; + } +); diff --git a/packages/data-services/src/request/request/authorization/events.ts b/packages/data-services/src/request/events.ts similarity index 92% rename from packages/data-services/src/request/request/authorization/events.ts rename to packages/data-services/src/request/events.ts index e8d65b8259..d4444bcb63 100644 --- a/packages/data-services/src/request/request/authorization/events.ts +++ b/packages/data-services/src/request/events.ts @@ -1,4 +1,4 @@ -import type { AccessTokenMessage } from './types'; +import { AccessTokenMessage } from './token'; type Callback = (user: AccessTokenMessage | null) => void; diff --git a/packages/data-services/src/request/index.ts b/packages/data-services/src/request/index.ts index ad1a78ad8f..5a70e5256f 100644 --- a/packages/data-services/src/request/index.ts +++ b/packages/data-services/src/request/index.ts @@ -1,4 +1,3 @@ export { request } from './axios'; -export { ServiceError } from './ServiceError'; -export { setToken, getToken, authorizationEvent } from './request'; -export type { AccessTokenMessage } from './request'; +export type { AccessTokenMessage } from './token'; +export { authorizationEvent, getToken, setToken } from './token'; diff --git a/packages/data-services/src/request/request/authorization/index.ts b/packages/data-services/src/request/request/authorization/index.ts deleted file mode 100644 index fbac5e9287..0000000000 --- a/packages/data-services/src/request/request/authorization/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './authorization'; -export type { AccessTokenMessage } from './types'; diff --git a/packages/data-services/src/request/request/authorization/types.ts b/packages/data-services/src/request/request/authorization/types.ts deleted file mode 100644 index fa7069395b..0000000000 --- a/packages/data-services/src/request/request/authorization/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface AccessTokenMessage { - create_at: number; - exp: number; - email: string; - id: string; - name: string; - avatar_url: string; -} diff --git a/packages/data-services/src/request/request/index.ts b/packages/data-services/src/request/request/index.ts deleted file mode 100644 index a31c82791d..0000000000 --- a/packages/data-services/src/request/request/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { - setAuthorization, - setToken, - getToken, - authorizationEvent, -} from './authorization'; -export type { AccessTokenMessage } from './authorization'; diff --git a/packages/data-services/src/request/response/handle-error.ts b/packages/data-services/src/request/response/handle-error.ts deleted file mode 100644 index e4168691f9..0000000000 --- a/packages/data-services/src/request/response/handle-error.ts +++ /dev/null @@ -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) { - 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) { - 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; -} diff --git a/packages/data-services/src/request/response/index.ts b/packages/data-services/src/request/response/index.ts deleted file mode 100644 index 94547c0213..0000000000 --- a/packages/data-services/src/request/response/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { handleResponseError, handleResponse } from './handle-error'; diff --git a/packages/data-services/src/request/request/authorization/authorization.ts b/packages/data-services/src/request/token.ts similarity index 68% rename from packages/data-services/src/request/request/authorization/authorization.ts rename to packages/data-services/src/request/token.ts index 1009971cd5..92d22e98e3 100644 --- a/packages/data-services/src/request/request/authorization/authorization.ts +++ b/packages/data-services/src/request/token.ts @@ -1,44 +1,17 @@ -import type { AxiosRequestConfig } from 'axios'; -import { login } from '../../../sdks'; 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'; -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) { // Going backwards: from byte stream, to percent-encoding, to original string. 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); if (!message) { return true; @@ -72,41 +45,39 @@ function isAccessTokenExpired(token: string) { return Date.now() - message.create_at > message.exp; } -async function isLoggedIn(): Promise { - const token = getToken(); - if (!token) { - return false; +export function getToken(): { + accessToken: string; + refreshToken: string; +} | 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 | undefined; -export async function setAuthorization(config: AxiosRequestConfig) { - 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 () => { let token = getToken(); if (!token) { diff --git a/packages/data-services/src/sdks/user.ts b/packages/data-services/src/sdks/user.ts index 8f3c699c3a..0ba0e64107 100644 --- a/packages/data-services/src/sdks/user.ts +++ b/packages/data-services/src/sdks/user.ts @@ -32,7 +32,7 @@ export async function login(params: LoginParams): Promise { method: 'POST', data: params, withAuthorization: false, - }); + } as any); return data.data; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dbee243f73..a67f90ed52 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,12 +117,14 @@ importers: axios: ^1.2.0 encoding: ^0.1.13 firebase: ^9.13.0 + ky: ^0.33.0 swr: ^2.0.0 typescript: ^4.8.4 dependencies: - axios: 1.2.0 + axios: 1.2.1 encoding: 0.1.13 firebase: 9.13.0_encoding@0.1.13 + ky: 0.33.0 swr: 2.0.0 devDependencies: typescript: 4.9.3 @@ -3586,10 +3588,10 @@ packages: engines: { node: '>=4' } dev: true - /axios/1.2.0: + /axios/1.2.1: resolution: { - integrity: sha512-zT7wZyNYu3N5Bu0wuZ6QccIf93Qk1eV8LOewxgjOZFd2DenOs98cJ7+Y6703d0wkaXGY6/nZd4EweJaHz9uzQw==, + integrity: sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==, } dependencies: follow-redirects: 1.15.2 @@ -6675,6 +6677,14 @@ packages: engines: { node: '>=14.16' } dev: false + /ky/0.33.0: + resolution: + { + integrity: sha512-peKzuOlN/q3Q3jOgi4t0cp6DOgif5rVnmiSIsjsmkiOcdnSjkrKSUqQmRWYCTqjUtR9b3xQQr8aj7KwSW1r49A==, + } + engines: { node: '>=14.16' } + dev: false + /language-subtag-registry/0.3.22: resolution: {