diff --git a/packages/data-center/src/provider/affine/__tests__/mock-apis.ts b/packages/data-center/src/provider/affine/__tests__/mock-apis.ts index b05b9cd66..21a36c769 100644 --- a/packages/data-center/src/provider/affine/__tests__/mock-apis.ts +++ b/packages/data-center/src/provider/affine/__tests__/mock-apis.ts @@ -1,7 +1,7 @@ import type { Apis, AccessTokenMessage } from '../apis'; const user: AccessTokenMessage = { - create_at: Date.now(), + created_at: Date.now(), exp: 100000000, email: 'demo@demo.demo', id: '123', diff --git a/packages/data-center/src/provider/affine/affine.ts b/packages/data-center/src/provider/affine/affine.ts index e8e564e09..0b28813ee 100644 --- a/packages/data-center/src/provider/affine/affine.ts +++ b/packages/data-center/src/provider/affine/affine.ts @@ -5,13 +5,11 @@ import type { } from '../base'; import type { User } from '../../types'; import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store'; -import { storage } from './storage.js'; import assert from 'assert'; import { WebsocketProvider } from './sync.js'; // import { IndexedDBProvider } from '../local/indexeddb'; import { getApis, Workspace } from './apis/index.js'; -import type { Apis, WorkspaceDetail, Callback } from './apis'; -import { token } from './apis/token.js'; +import type { Apis, WorkspaceDetail } from './apis'; import { WebsocketClient } from './channel'; import { loadWorkspaceUnit, @@ -40,10 +38,10 @@ const { export class AffineProvider extends BaseProvider { public id = 'affine'; - private _onTokenRefresh?: Callback = undefined; private _wsMap: Map = new Map(); private _apis: Apis; private _channel?: WebsocketClient; + private _refreshToken?: string; // private _idbMap: Map = new Map(); private _workspaceLoadingQueue: Set = new Set(); @@ -53,40 +51,25 @@ export class AffineProvider extends BaseProvider { } override async init() { - this._onTokenRefresh = () => { - if (this._apis.token.refresh) { - storage.setItem('token', this._apis.token.refresh); + this._apis.auth.onChange(() => { + if (this._apis.auth.isLogin) { + this._reconnectChannel(); + } else { + this._destroyChannel(); } - }; + }); - this._apis.token.onChange(this._onTokenRefresh); - - // initial login token - if (this._apis.token.isExpired) { - try { - const refreshToken = storage.getItem('token'); - if (!refreshToken) return; - await this._apis.token.refreshToken(refreshToken); - - if (this._apis.token.refresh) { - storage.set('token', this._apis.token.refresh); - } - - assert(this._apis.token.isLogin); - } catch (_) { - // this._logger('Authorization failed, fallback to local mode'); - } - } else { - storage.setItem('token', this._apis.token.refresh); - } - - if (token.isLogin) { - this._connectChannel(); + if (this._apis.auth.isExpired && this._apis.auth.refresh) { + // do we need to await the following? + this._apis.auth.refreshToken(); } } - private _connectChannel() { - if (!this._channel) { + private _reconnectChannel() { + if (this._refreshToken !== this._apis.auth.refresh) { + // need to reconnect + this._destroyChannel(); + this._channel = new WebsocketClient( `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${ window.location.host @@ -94,14 +77,25 @@ export class AffineProvider extends BaseProvider { this._logger, { params: { - token: this._apis.token.refresh, + token: this._apis.auth.refresh, }, } ); + + this._channel.on('message', (msg: ChannelMessage) => { + this._handlerAffineListMessage(msg); + }); + + this._refreshToken = this._apis.auth.refresh; + } + } + + private _destroyChannel() { + if (this._channel) { + this._channel.disconnect(); + this._channel.destroy(); + this._channel = undefined; } - this._channel.on('message', (msg: ChannelMessage) => { - this._handlerAffineListMessage(msg); - }); } private async _handlerAffineListMessage({ @@ -111,7 +105,7 @@ export class AffineProvider extends BaseProvider { this._logger('receive server message'); const newlyCreatedWorkspaces: WorkspaceUnit[] = []; const currentWorkspaceIds = this._workspaces.list().map(w => w.id); - const newlyRemovedWorkspacecIds = currentWorkspaceIds; + const newlyRemovedWorkspaceIds = currentWorkspaceIds; for (const [id, detail] of Object.entries(ws_details)) { const { name, avatar } = metadata[id]; @@ -121,7 +115,7 @@ export class AffineProvider extends BaseProvider { const workspaceIndex = currentWorkspaceIds.indexOf(id); const ifWorkspaceExist = workspaceIndex !== -1; if (ifWorkspaceExist) { - newlyRemovedWorkspacecIds.splice(workspaceIndex, 1); + newlyRemovedWorkspaceIds.splice(workspaceIndex, 1); } /** @@ -163,7 +157,7 @@ export class AffineProvider extends BaseProvider { this._workspaces.add(newlyCreatedWorkspaces); // sync newlyRemoveWorkspaces to context - this._workspaces.remove(newlyRemovedWorkspacecIds); + this._workspaces.remove(newlyRemovedWorkspaceIds); } private _getWebsocketProvider(workspace: BlocksuiteWorkspace) { @@ -176,13 +170,13 @@ export class AffineProvider extends BaseProvider { window.location.protocol === 'https:' ? 'wss' : 'ws' }://${window.location.host}/api/sync/`; ws = new WebsocketProvider(wsUrl, room, doc, { - params: { token: this._apis.token.refresh }, + params: { token: this._apis.auth.refresh }, // @ts-expect-error ignore the type awareness: workspace.awarenessStore.awareness, }); workspace.awarenessStore.awareness.setLocalStateField('user', { - name: token.user?.name ?? 'other', - id: Number(token.user?.id ?? -1), + name: this._apis.auth.user?.name ?? 'other', + id: Number(this._apis.auth.user?.id ?? -1), color: '#ffa500', }); @@ -232,7 +226,7 @@ export class AffineProvider extends BaseProvider { } override async loadWorkspaces() { - if (!this._apis.token.isLogin) { + if (!this._apis.auth.isLogin) { return []; } const workspacesList = await this._apis.getWorkspaces(); @@ -261,25 +255,24 @@ export class AffineProvider extends BaseProvider { } override async auth() { - const refreshToken = await storage.getItem('token'); - if (refreshToken) { - await this._apis.token.refreshToken(refreshToken); - if (this._apis.token.isLogin && !this._apis.token.isExpired) { + if (this._apis.auth.isLogin) { + await this._apis.auth.refreshToken(); + if (this._apis.auth.isLogin && !this._apis.auth.isExpired) { // login success return; } } + const user = await this._apis.signInWithGoogle?.(); - if (!this._channel?.connected) { - this._connectChannel(); - } + if (!user) { this._sendMessage(MessageCenter.messageCode.loginError); } } + // TODO: may need to update related workspace attributes on user info change? public override async getUserInfo(): Promise { - const user = this._apis.token.user; + const user = this._apis.auth.user; return user ? { id: user.id, @@ -389,7 +382,7 @@ export class AffineProvider extends BaseProvider { } public override getToken(): string { - return this._apis.token.token; + return this._apis.auth.token; } public override async getUserByEmail( @@ -441,11 +434,11 @@ export class AffineProvider extends BaseProvider { } public override async logout(): Promise { - token.clear(); - this._channel?.disconnect(); + this._apis.auth.clear(); + this._destroyChannel(); this._wsMap.forEach(ws => ws.disconnect()); this._workspaces.clear(false); - storage.removeItem('token'); + await this._apis.signOutFirebase(); } public override async getWorkspaceMembers(id: string) { diff --git a/packages/data-center/src/provider/affine/apis/__tests__/token.spec.ts b/packages/data-center/src/provider/affine/apis/__tests__/auth.spec.ts similarity index 79% rename from packages/data-center/src/provider/affine/apis/__tests__/token.spec.ts rename to packages/data-center/src/provider/affine/apis/__tests__/auth.spec.ts index 739bae9e9..72a271c12 100644 --- a/packages/data-center/src/provider/affine/apis/__tests__/token.spec.ts +++ b/packages/data-center/src/provider/affine/apis/__tests__/auth.spec.ts @@ -1,10 +1,10 @@ import { test, expect } from '@playwright/test'; -import { Token } from '../token.js'; +import { Auth } from '../auth.js'; -test.describe('class Token', () => { +test.describe('class Auth', () => { test('parse tokens', () => { const tokenString = `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzU2Nzk1MjAsImlkIjo2LCJuYW1lIjoidGVzdCIsImVtYWlsIjoidGVzdEBnbWFpbC5jb20iLCJhdmF0YXJfdXJsIjoiaHR0cHM6Ly90ZXN0LmNvbS9hdmF0YXIiLCJjcmVhdGVkX2F0IjoxNjc1Njc4OTIwMzU4fQ.R8GxrNhn3gNumtapthrP6_J5eQjXLV7i-LanSPqe7hw`; - expect(Token.parse(tokenString)).toEqual({ + expect(Auth.parseIdToken(tokenString)).toEqual({ avatar_url: 'https://test.com/avatar', created_at: 1675678920358, email: 'test@gmail.com', @@ -16,6 +16,6 @@ test.describe('class Token', () => { test('parse invalid tokens', () => { const tokenString = `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.aaa.R8GxrNhn3gNumtapthrP6_J5eQjXLV7i-LanSPqe7hw`; - expect(Token.parse(tokenString)).toEqual(null); + expect(Auth.parseIdToken(tokenString)).toEqual(null); }); }); diff --git a/packages/data-center/src/provider/affine/apis/auth.ts b/packages/data-center/src/provider/affine/apis/auth.ts new file mode 100644 index 000000000..cd79513f2 --- /dev/null +++ b/packages/data-center/src/provider/affine/apis/auth.ts @@ -0,0 +1,254 @@ +import { initializeApp } from 'firebase/app'; +import { + type Auth as FirebaseAuth, + getAuth as getFirebaseAuth, + GoogleAuthProvider, + signInWithPopup, + signOut, +} from 'firebase/auth'; +import type { User } from 'firebase/auth'; +import { decode } from 'js-base64'; + +import { getLogger } from '../../../logger.js'; +import { bareClient } from './request.js'; +import { storage } from '../storage.js'; + +export interface AccessTokenMessage { + created_at: number; + exp: number; + email: string; + id: string; + name: string; + avatar_url: string; +} + +export type Callback = (user: AccessTokenMessage | null) => void; + +type LoginParams = { + type: 'Google' | 'Refresh'; + token: string; +}; + +type LoginResponse = { + // access token, expires in a very short time + token: string; + // Refresh token + refresh: string; +}; + +// TODO: organize storage keys in a better way +const AFFINE_LOGIN_STORAGE_KEY = 'affine:login'; + +/** + * Use refresh token to get a new access token (JWT) + * The returned token also contains the user info payload. + */ +const doLogin = (params: LoginParams): Promise => + bareClient.post('api/user/token', { json: params }).json(); + +export class Auth { + private readonly _logger; + private _accessToken = ''; // idtoken (JWT) + private _refreshToken = ''; + + private _user: AccessTokenMessage | null = null; + private _padding?: Promise; + + constructor() { + this._logger = getLogger('token'); + this._logger.enabled = true; + + this.restoreLogin(); + } + + setLogin(login: LoginResponse) { + this._accessToken = login.token; + this._refreshToken = login.refresh; + this._user = Auth.parseIdToken(this._accessToken); + + this.triggerChange(this._user); + this.storeLogin(); + } + + private storeLogin() { + if (this.refresh) { + const { token, refresh } = this; + storage.setItem( + AFFINE_LOGIN_STORAGE_KEY, + JSON.stringify({ token, refresh }) + ); + } + } + + private restoreLogin() { + const loginStr = storage.getItem(AFFINE_LOGIN_STORAGE_KEY); + if (!loginStr) { + return; + } + try { + const login: LoginResponse = JSON.parse(loginStr); + this.setLogin(login); + } catch (err) { + this._logger('Failed to parse login info', err); + } + } + + async initToken(token: string) { + const res = await doLogin({ token, type: 'Google' }); + this.setLogin(res); + return this._user; + } + + async refreshToken(refreshToken?: string) { + if (!this._padding) { + this._padding = doLogin({ + type: 'Refresh', + token: refreshToken || this._refreshToken, + }); + this._padding.finally(() => { + // clear on settled + this._padding = undefined; + }); + this._refreshToken = refreshToken || this._refreshToken; + } + const res = await this._padding; + if (!refreshToken || refreshToken !== this._refreshToken) { + this.setLogin(res); + } + return true; + } + + get user() { + // computed through access token + return this._user; + } + + get token() { + return this._accessToken; + } + + get refresh() { + return this._refreshToken; + } + + get isLogin() { + return !!this._refreshToken; + } + + get isExpired() { + if (!this._user) return true; + // exp is in seconds + return Date.now() > this._user.exp * 1000; + } + + static parseIdToken(token: string): AccessTokenMessage | null { + try { + return JSON.parse(decode(token.split('.')[1])); + } catch (error) { + // todo: log errors? + return null; + } + } + + private callbacks: Callback[] = []; + private lastState: AccessTokenMessage | null = null; + + triggerChange(user: AccessTokenMessage | null) { + this.lastState = user; + this.callbacks.forEach(callback => callback(user)); + } + + onChange(callback: Callback) { + this.callbacks.push(callback); + callback(this.lastState); + } + + offChange(callback: Callback) { + const index = this.callbacks.indexOf(callback); + if (index > -1) { + this.callbacks.splice(index, 1); + } + } + + clear() { + this._accessToken = ''; + this._refreshToken = ''; + storage.removeItem(AFFINE_LOGIN_STORAGE_KEY); + } +} + +export const auth = new Auth(); + +export const getAuthorizer = () => { + let _firebaseAuth: FirebaseAuth | null = null; + + // getAuth will send requests on calling thus we can lazy init it + const getAuth = () => { + try { + if (!_firebaseAuth) { + const app = initializeApp({ + apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, + authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, + projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, + storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, + messagingSenderId: + process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, + measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, + }); + _firebaseAuth = getFirebaseAuth(app); + } + return _firebaseAuth; + } catch (error) { + getLogger('getAuthorizer')(error); + console.error('getAuthorizer', error); + return null; + } + }; + + const getToken = async () => { + const currentUser = getAuth()?.currentUser; + if (currentUser) { + await currentUser.getIdTokenResult(true); + if (!currentUser.isAnonymous) { + return currentUser.getIdToken(); + } + } + return; + }; + + const signInWithGoogle = async () => { + const idToken = await getToken(); + let loginUser: AccessTokenMessage | null = null; + if (idToken) { + loginUser = await auth.initToken(idToken); + } else { + const firebaseAuth = getAuth(); + if (firebaseAuth) { + const googleAuthProvider = new GoogleAuthProvider(); + // make sure the user has a chance to select an account + // https://developers.google.com/identity/openid-connect/openid-connect#prompt + googleAuthProvider.setCustomParameters({ + prompt: 'select_account', + }); + const user = await signInWithPopup(firebaseAuth, googleAuthProvider); + const idToken = await user.user.getIdToken(); + loginUser = await auth.initToken(idToken); + } + } + return loginUser; + }; + + const onAuthStateChanged = (callback: (user: User | null) => void) => { + getAuth()?.onAuthStateChanged(callback); + }; + + const signOutFirebase = async () => { + const firebaseAuth = getAuth(); + if (firebaseAuth?.currentUser) { + await signOut(firebaseAuth); + } + }; + + return [signInWithGoogle, onAuthStateChanged, signOutFirebase] as const; +}; diff --git a/packages/data-center/src/provider/affine/apis/index.ts b/packages/data-center/src/provider/affine/apis/index.ts index d3ac78088..d96fc07c5 100644 --- a/packages/data-center/src/provider/affine/apis/index.ts +++ b/packages/data-center/src/provider/affine/apis/index.ts @@ -1,28 +1,40 @@ // export { token } from './token.js'; -export type { Callback } from './token.js'; +export type { Callback } from './auth.js'; -import { getAuthorizer } from './token.js'; +import { getAuthorizer } from './auth.js'; import * as user from './user.js'; import * as workspace from './workspace.js'; -import { token } from './token.js'; +import { auth } from './auth.js'; -export type Apis = typeof user & - Omit & { - signInWithGoogle: ReturnType[0]; - onAuthStateChanged: ReturnType[1]; - } & { token: typeof token }; +// See https://twitter.com/mattpocockuk/status/1622730173446557697 +// TODO: move to ts utils? +type Prettify = { + [K in keyof T]: T[K]; + // eslint-disable-next-line @typescript-eslint/ban-types +} & {}; + +export type Apis = Prettify< + typeof user & + Omit & { + signInWithGoogle: ReturnType[0]; + onAuthStateChanged: ReturnType[1]; + signOutFirebase: ReturnType[2]; + } & { auth: typeof auth } +>; export const getApis = (): Apis => { - const [signInWithGoogle, onAuthStateChanged] = getAuthorizer(); + const [signInWithGoogle, onAuthStateChanged, signOutFirebase] = + getAuthorizer(); return { ...user, ...workspace, signInWithGoogle, + signOutFirebase, onAuthStateChanged, - token, + auth, }; }; -export type { AccessTokenMessage } from './token'; +export type { AccessTokenMessage } from './auth'; export type { Member, Workspace, WorkspaceDetail } from './workspace'; export { WorkspaceType } from './workspace.js'; diff --git a/packages/data-center/src/provider/affine/apis/request.ts b/packages/data-center/src/provider/affine/apis/request.ts index 306ac5b9e..b0a758468 100644 --- a/packages/data-center/src/provider/affine/apis/request.ts +++ b/packages/data-center/src/provider/affine/apis/request.ts @@ -1,6 +1,6 @@ import ky from 'ky-universal'; import { MessageCenter } from '../../../message/index.js'; -import { token } from './token.js'; +import { auth } from './auth.js'; type KyInstance = typeof ky; @@ -28,13 +28,23 @@ export const bareClient: KyInstance = ky.extend({ }, }); +const refreshTokenIfExpired = async () => { + if (auth.isLogin && auth.isExpired) { + try { + await auth.refreshToken(); + } catch (err) { + return new Response('Unauthorized', { status: 401 }); + } + } +}; + export const client: KyInstance = bareClient.extend({ hooks: { beforeRequest: [ async request => { - if (token.isLogin) { - if (token.isExpired) await token.refreshToken(); - request.headers.set('Authorization', token.token); + if (auth.isLogin) { + await refreshTokenIfExpired(); + request.headers.set('Authorization', auth.token); } else { return new Response('Unauthorized', { status: 401 }); } @@ -43,8 +53,8 @@ export const client: KyInstance = bareClient.extend({ beforeRetry: [ async ({ request }) => { - await token.refreshToken(); - request.headers.set('Authorization', token.token); + await refreshTokenIfExpired(); + request.headers.set('Authorization', auth.token); }, ], diff --git a/packages/data-center/src/provider/affine/apis/token.ts b/packages/data-center/src/provider/affine/apis/token.ts deleted file mode 100644 index 5b8e16592..000000000 --- a/packages/data-center/src/provider/affine/apis/token.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { initializeApp } from 'firebase/app'; -import { getAuth, GoogleAuthProvider, signInWithPopup } from 'firebase/auth'; -import type { User } from 'firebase/auth'; -import { decode } from 'js-base64'; - -import { getLogger } from '../../../logger.js'; -import { bareClient } from './request.js'; - -export interface AccessTokenMessage { - create_at: number; - exp: number; - email: string; - id: string; - name: string; - avatar_url: string; -} - -export type Callback = (user: AccessTokenMessage | null) => void; - -type LoginParams = { - type: 'Google' | 'Refresh'; - token: string; -}; - -type LoginResponse = { - // access token, expires in a very short time - token: string; - // Refresh token - refresh: string; -}; - -const login = (params: LoginParams): Promise => - bareClient.post('api/user/token', { json: params }).json(); - -export class Token { - private readonly _logger; - private _accessToken!: string; - private _refreshToken!: string; - - private _user!: AccessTokenMessage | null; - private _padding?: Promise; - - constructor() { - this._logger = getLogger('token'); - this._logger.enabled = true; - - this._setToken(); // fill with default value - } - - get user() { - return this._user; - } - - private _setToken(login?: LoginResponse) { - this._accessToken = login?.token || ''; - this._refreshToken = login?.refresh || ''; - - this._user = Token.parse(this._accessToken); - if (login) { - this._logger('set login', login); - this.triggerChange(this._user); - } else { - this._logger('empty login'); - } - } - - async initToken(token: string) { - const tokens = await login({ token, type: 'Google' }); - this._setToken(tokens); - return this._user; - } - - async refreshToken(token?: string) { - if (!this._padding) { - this._padding = login({ - type: 'Refresh', - token: this._refreshToken || token!, - }); - } - this._setToken(await this._padding); - this._padding = undefined; - } - - get token() { - return this._accessToken; - } - - get refresh() { - return this._refreshToken; - } - - get isLogin() { - return !!this._refreshToken; - } - - get isExpired() { - if (!this._user) return true; - return Date.now() - this._user.create_at > this._user.exp; - } - - static parse(token: string): AccessTokenMessage | null { - try { - return JSON.parse(decode(token.split('.')[1])); - } catch (error) { - // todo: log errors? - return null; - } - } - - private callbacks: Callback[] = []; - private lastState: AccessTokenMessage | null = null; - - triggerChange(user: AccessTokenMessage | null) { - this.lastState = user; - this.callbacks.forEach(callback => callback(user)); - } - - onChange(callback: Callback) { - this.callbacks.push(callback); - callback(this.lastState); - } - - offChange(callback: Callback) { - const index = this.callbacks.indexOf(callback); - if (index > -1) { - this.callbacks.splice(index, 1); - } - } - - clear() { - this._setToken(); - } -} - -export const token = new Token(); - -export const getAuthorizer = () => { - const app = initializeApp({ - apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, - authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, - projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, - storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, - messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, - appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, - measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, - }); - try { - const firebaseAuth = getAuth(app); - - const googleAuthProvider = new GoogleAuthProvider(); - - const getToken = async () => { - const currentUser = firebaseAuth.currentUser; - if (currentUser) { - await currentUser.getIdTokenResult(true); - if (!currentUser.isAnonymous) { - return currentUser.getIdToken(); - } - } - return; - }; - - const signInWithGoogle = async () => { - const idToken = await getToken(); - let loginUser: AccessTokenMessage | null = null; - if (idToken) { - loginUser = await token.initToken(idToken); - } else { - const user = await signInWithPopup(firebaseAuth, googleAuthProvider); - const idToken = await user.user.getIdToken(); - loginUser = await token.initToken(idToken); - } - return loginUser; - }; - - const onAuthStateChanged = (callback: (user: User | null) => void) => { - firebaseAuth.onAuthStateChanged(callback); - }; - - return [signInWithGoogle, onAuthStateChanged] as const; - } catch (e) { - getLogger('getAuthorizer')(e); - console.error('getAuthorizer', e); - return [] as const; - } -}; diff --git a/packages/data-center/src/provider/affine/apis/workspace.ts b/packages/data-center/src/provider/affine/apis/workspace.ts index 584db159f..c12fed6e6 100644 --- a/packages/data-center/src/provider/affine/apis/workspace.ts +++ b/packages/data-center/src/provider/affine/apis/workspace.ts @@ -41,7 +41,7 @@ export interface Workspace { export async function getWorkspaces(): Promise { try { - return client + return await client .get('api/workspace', { headers: { 'Cache-Control': 'no-cache', @@ -63,8 +63,7 @@ export async function getWorkspaceDetail( params: GetWorkspaceDetailParams ): Promise { try { - const response = client.get(`api/workspace/${params.id}`); - return response.json(); + return await client.get(`api/workspace/${params.id}`).json(); } catch (error) { sendMessage(messageCode.getDetailFailed); throw new RequestError('get detail failed', error); @@ -100,7 +99,7 @@ export async function getWorkspaceMembers( params: GetWorkspaceDetailParams ): Promise { try { - return client.get(`api/workspace/${params.id}/permission`).json(); + return await client.get(`api/workspace/${params.id}/permission`).json(); } catch (error) { sendMessage(messageCode.getMembersFailed); throw new RequestError('get members failed', error); @@ -115,7 +114,7 @@ export async function createWorkspace( encodedYDoc: Blob ): Promise<{ id: string }> { try { - return client.post('api/workspace', { body: encodedYDoc }).json(); + return await client.post('api/workspace', { body: encodedYDoc }).json(); } catch (error) { sendMessage(messageCode.createWorkspaceFailed); throw new RequestError('create workspace failed', error); @@ -131,7 +130,7 @@ export async function updateWorkspace( params: UpdateWorkspaceParams ): Promise<{ public: boolean | null }> { try { - return client + return await client .post(`api/workspace/${params.id}`, { json: { public: params.public, @@ -151,10 +150,12 @@ export interface DeleteWorkspaceParams { export async function deleteWorkspace( params: DeleteWorkspaceParams ): Promise { - await client.delete(`api/workspace/${params.id}`).catch(error => { + try { + await client.delete(`api/workspace/${params.id}`); + } catch (error) { sendMessage(messageCode.deleteWorkspaceFailed); throw new RequestError('delete workspace failed', error); - }); + } } export interface InviteMemberParams { @@ -167,13 +168,11 @@ export interface InviteMemberParams { */ export async function inviteMember(params: InviteMemberParams): Promise { try { - return client - .post(`api/workspace/${params.id}/permission`, { - json: { - email: params.email, - }, - }) - .json(); + await client.post(`api/workspace/${params.id}/permission`, { + json: { + email: params.email, + }, + }); } catch (error) { sendMessage(messageCode.inviteMemberFailed); throw new RequestError('invite member failed', error); @@ -185,10 +184,12 @@ export interface RemoveMemberParams { } export async function removeMember(params: RemoveMemberParams): Promise { - await client.delete(`api/permission/${params.permissionId}`).catch(error => { + try { + await client.delete(`api/permission/${params.permissionId}`); + } catch (error) { sendMessage(messageCode.removeMemberFailed); throw new RequestError('remove member failed', error); - }); + } } export interface AcceptInvitingParams { @@ -199,7 +200,9 @@ export async function acceptInviting( params: AcceptInvitingParams ): Promise { try { - return bareClient.post(`api/invitation/${params.invitingCode}`).json(); + return await bareClient + .post(`api/invitation/${params.invitingCode}`) + .json(); } catch (error) { sendMessage(messageCode.acceptInvitingFailed); throw new RequestError('accept inviting failed', error); @@ -214,7 +217,7 @@ export async function getBlob(params: { blobId: string; }): Promise { try { - return client.get(`api/blob/${params.blobId}`).arrayBuffer(); + return await client.get(`api/blob/${params.blobId}`).arrayBuffer(); } catch (error) { sendMessage(messageCode.getBlobFailed); throw new RequestError('get blob failed', error); @@ -226,13 +229,12 @@ export interface LeaveWorkspaceParams { } export async function leaveWorkspace({ id }: LeaveWorkspaceParams) { - await client - .delete(`api/workspace/${id}/permission`) - .json() - .catch(error => { - sendMessage(messageCode.leaveWorkspaceFailed); - throw new RequestError('leave workspace failed', error); - }); + try { + await client.delete(`api/workspace/${id}/permission`); + } catch (error) { + sendMessage(messageCode.leaveWorkspaceFailed); + throw new RequestError('leave workspace failed', error); + } } export async function downloadWorkspace( @@ -241,9 +243,11 @@ export async function downloadWorkspace( ): Promise { try { if (published) { - return bareClient.get(`api/public/doc/${workspaceId}`).arrayBuffer(); + return await bareClient + .get(`api/public/doc/${workspaceId}`) + .arrayBuffer(); } - return client.get(`api/workspace/${workspaceId}/doc`).arrayBuffer(); + return await client.get(`api/workspace/${workspaceId}/doc`).arrayBuffer(); } catch (error) { sendMessage(messageCode.downloadWorkspaceFailed); throw new RequestError('download workspace failed', error); diff --git a/packages/data-center/src/provider/affine/channel.ts b/packages/data-center/src/provider/affine/channel.ts index da9e81c35..ce19a35ac 100644 --- a/packages/data-center/src/provider/affine/channel.ts +++ b/packages/data-center/src/provider/affine/channel.ts @@ -1,6 +1,6 @@ import * as websocket from 'lib0/websocket'; import { Logger } from 'src/types'; -import { token } from './apis/token'; +import { auth } from './apis/auth'; import * as url from 'lib0/url'; const RECONNECT_INTERVAL_TIME = 500; @@ -40,7 +40,7 @@ export class WebsocketClient extends websocket.WebsocketClient { this.on('disconnect', ({ error }: { error: Error }) => { if (error) { // Try reconnect if connect error has occurred - if (this.shouldReconnect && token.isLogin && !this.connected) { + if (this.shouldReconnect && auth.isLogin && !this.connected) { try { setTimeout(() => { if (this._retryTimes <= MAX_RECONNECT_TIMES) { diff --git a/packages/data-center/src/provider/affine/storage.ts b/packages/data-center/src/provider/affine/storage.ts index 4ac1b7469..95031de65 100644 --- a/packages/data-center/src/provider/affine/storage.ts +++ b/packages/data-center/src/provider/affine/storage.ts @@ -1 +1,3 @@ -export { varStorage as storage } from 'lib0/storage'; +import { varStorage } from 'lib0/storage'; + +export const storage = varStorage as Storage;