diff --git a/apps/web/src/components/message-center-handler/index.tsx b/apps/web/src/components/message-center-handler/index.tsx index f53ed3756b..0339e2d300 100644 --- a/apps/web/src/components/message-center-handler/index.tsx +++ b/apps/web/src/components/message-center-handler/index.tsx @@ -1,14 +1,14 @@ import { toast } from '@affine/component'; -import { getApis, MessageCenter } from '@affine/datacenter'; +import { MessageCenter } from '@affine/datacenter'; +import { AffineProvider } from '@affine/datacenter'; import { useRouter } from 'next/router'; -import { useEffect } from 'react'; +import { ReactNode, useCallback, useEffect } from 'react'; -export function MessageCenterHandler({ - children, -}: { - children?: React.ReactNode; -}) { +import { useGlobalState } from '@/store/app'; + +export function MessageCenterHandler({ children }: { children?: ReactNode }) { const router = useRouter(); + const dataCenter = useGlobalState(useCallback(store => store.dataCenter, [])); useEffect(() => { const instance = MessageCenter.getInstance(); if (instance) { @@ -18,7 +18,15 @@ export function MessageCenterHandler({ // todo: more specific message for accessing different resources // todo: error toast style toast('You have no permission to access this workspace'); - getApis().auth.clear(); + // todo(himself65): remove dynamic lookup + const affineProvider = dataCenter.providers.find( + p => p.id === 'affine' + ); + if (affineProvider && affineProvider instanceof AffineProvider) { + affineProvider.apis.auth.clear(); + } else { + console.error('cannot find affine provider, please fix this ASAP'); + } // the status of the app right now is unknown, and it won't help if we let // the app continue and let the user auth the app. // that's why so we need to reload the page for now. @@ -30,7 +38,7 @@ export function MessageCenterHandler({ } }); } - }, [router]); + }, [dataCenter?.providers, router]); return <>{children}; } diff --git a/apps/web/src/pages/_app.tsx b/apps/web/src/pages/_app.tsx index b9814150a8..0b09b8495e 100644 --- a/apps/web/src/pages/_app.tsx +++ b/apps/web/src/pages/_app.tsx @@ -78,19 +78,19 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => { , ]} > - - {NoNeedAppStatePageList.includes(router.route) ? ( - getLayout() - ) : ( - }> - + {NoNeedAppStatePageList.includes(router.route) ? ( + getLayout() + ) : ( + }> + + {getLayout()} - - - )} - + + + + )} diff --git a/packages/data-center/src/index.ts b/packages/data-center/src/index.ts index de2a3b9d74..0d39aa2a3a 100644 --- a/packages/data-center/src/index.ts +++ b/packages/data-center/src/index.ts @@ -28,6 +28,7 @@ export const getDataCenter = _initializeDataCenter(); export type { DataCenter }; export { getLogger } from './logger'; export * from './message'; +export { AffineProvider } from './provider/affine'; export * from './provider/affine/apis'; export * from './types'; export { WorkspaceUnit } from './workspace-unit'; diff --git a/packages/data-center/src/provider/affine/affine.ts b/packages/data-center/src/provider/affine/affine.ts index 1a7ac7066a..2eaa8e1516 100644 --- a/packages/data-center/src/provider/affine/affine.ts +++ b/packages/data-center/src/provider/affine/affine.ts @@ -1,5 +1,6 @@ import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store'; import assert from 'assert'; +import { KyInstance } from 'ky/distribution/types/ky'; import { MessageCenter } from '../../message'; import type { User } from '../../types'; @@ -12,16 +13,12 @@ import type { } from '../base'; import { BaseProvider } from '../base'; import type { Apis, WorkspaceDetail } from './apis'; -// import { IndexedDBProvider } from '../local/indexeddb'; import { getApis, Workspace } from './apis'; +import { createGoogleAuth } from './apis/google'; +import { createAuthClient, createBareClient } from './apis/request'; import { WebsocketClient } from './channel'; import { WebsocketProvider } from './sync'; -import { - createBlocksuiteWorkspaceWithAuth, - createWorkspaceUnit, - loadWorkspaceUnit, - migrateBlobDB, -} from './utils'; +import { createWorkspaceUnit, loadWorkspaceUnit, migrateBlobDB } from './utils'; type ChannelMessage = { ws_list: Workspace[]; @@ -39,6 +36,9 @@ const { } = BlocksuiteWorkspace; export class AffineProvider extends BaseProvider { + private readonly bareClient: KyInstance; + private readonly authClient: KyInstance; + public id = 'affine'; private _wsMap: Map = new Map(); private _apis: Apis; @@ -52,7 +52,14 @@ export class AffineProvider extends BaseProvider { constructor({ apis, ...params }: AffineProviderConstructorParams) { super(params); - this._apis = apis || getApis(); + this.bareClient = createBareClient('/'); + const googleAuth = createGoogleAuth(this.bareClient); + this.authClient = createAuthClient(this.bareClient, googleAuth); + this._apis = apis || getApis(this.bareClient, this.authClient, googleAuth); + } + + public get apis() { + return Object.freeze(this._apis); } override async init() { @@ -80,6 +87,7 @@ export class AffineProvider extends BaseProvider { window.location.host }/api/global/sync/`, this._logger, + this._apis.auth, { params: { token: this._apis.auth.refresh, @@ -370,16 +378,19 @@ export class AffineProvider extends BaseProvider { public override async createWorkspace( meta: CreateWorkspaceInfoParams ): Promise { - const workspaceUnitForUpload = await createWorkspaceUnit({ - id: '', - name: meta.name, - avatar: undefined, - owner: await this.getUserInfo(), - published: false, - memberCount: 1, - provider: this.id, - syncMode: 'core', - }); + const workspaceUnitForUpload = await createWorkspaceUnit( + { + id: '', + name: meta.name, + avatar: undefined, + owner: await this.getUserInfo(), + published: false, + memberCount: 1, + provider: this.id, + syncMode: 'core', + }, + this._apis + ); const { id } = await this._apis.createWorkspace( new Blob([ encodeStateAsUpdate(workspaceUnitForUpload.blocksuiteWorkspace!.doc) @@ -387,16 +398,19 @@ export class AffineProvider extends BaseProvider { ]) ); - const workspaceUnit = await createWorkspaceUnit({ - id, - name: meta.name, - avatar: undefined, - owner: await this.getUserInfo(), - published: false, - memberCount: 1, - provider: this.id, - syncMode: 'core', - }); + const workspaceUnit = await createWorkspaceUnit( + { + id, + name: meta.name, + avatar: undefined, + owner: await this.getUserInfo(), + published: false, + memberCount: 1, + provider: this.id, + syncMode: 'core', + }, + this._apis + ); this._workspaces.add(workspaceUnit); @@ -446,7 +460,8 @@ export class AffineProvider extends BaseProvider { }); await migrateBlobDB(workspaceUnit.id, id); - const blocksuiteWorkspace = await createBlocksuiteWorkspaceWithAuth(id); + const blocksuiteWorkspace = + await this._apis.createBlockSuiteWorkspaceWithAuth(id); assert(workspaceUnit.blocksuiteWorkspace); await applyUpdate( blocksuiteWorkspace, diff --git a/packages/data-center/src/provider/affine/apis/__tests__/auth.spec.ts b/packages/data-center/src/provider/affine/apis/__tests__/auth.spec.ts index 8ef6129a35..2b26c02ac7 100644 --- a/packages/data-center/src/provider/affine/apis/__tests__/auth.spec.ts +++ b/packages/data-center/src/provider/affine/apis/__tests__/auth.spec.ts @@ -1,11 +1,11 @@ import { describe, expect, test } from 'vitest'; -import { Auth } from '../auth'; +import { GoogleAuth } from '../google'; describe('class Auth', () => { test('parse tokens', () => { const tokenString = `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzU2Nzk1MjAsImlkIjo2LCJuYW1lIjoidGVzdCIsImVtYWlsIjoidGVzdEBnbWFpbC5jb20iLCJhdmF0YXJfdXJsIjoiaHR0cHM6Ly90ZXN0LmNvbS9hdmF0YXIiLCJjcmVhdGVkX2F0IjoxNjc1Njc4OTIwMzU4fQ.R8GxrNhn3gNumtapthrP6_J5eQjXLV7i-LanSPqe7hw`; - expect(Auth.parseIdToken(tokenString)).toEqual({ + expect(GoogleAuth.parseIdToken(tokenString)).toEqual({ avatar_url: 'https://test.com/avatar', created_at: 1675678920358, email: 'test@gmail.com', @@ -17,6 +17,6 @@ describe('class Auth', () => { test('parse invalid tokens', () => { const tokenString = `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.aaa.R8GxrNhn3gNumtapthrP6_J5eQjXLV7i-LanSPqe7hw`; - expect(Auth.parseIdToken(tokenString)).toEqual(null); + expect(GoogleAuth.parseIdToken(tokenString)).toEqual(null); }); }); diff --git a/packages/data-center/src/provider/affine/apis/auth.ts b/packages/data-center/src/provider/affine/apis/google.ts similarity index 87% rename from packages/data-center/src/provider/affine/apis/auth.ts rename to packages/data-center/src/provider/affine/apis/google.ts index 1bbeb9413e..72bf49d93f 100644 --- a/packages/data-center/src/provider/affine/apis/auth.ts +++ b/packages/data-center/src/provider/affine/apis/google.ts @@ -8,10 +8,10 @@ import { signOut, } from 'firebase/auth'; import { decode } from 'js-base64'; +import { KyInstance } from 'ky/distribution/types/ky'; import { getLogger } from '../../../logger'; import { storage } from '../storage'; -import { bareClient } from './request'; export interface AccessTokenMessage { created_at: number; @@ -43,20 +43,24 @@ 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(); +const createDoLogin = + (bareClient: KyInstance) => + (params: LoginParams): Promise => + bareClient.post('api/user/token', { json: params }).json(); -export class Auth { +export class GoogleAuth { private readonly _logger; private _accessToken = ''; // idtoken (JWT) private _refreshToken = ''; private _user: AccessTokenMessage | null = null; private _padding?: Promise; + private readonly _doLogin: ReturnType; - constructor() { + constructor(bareClient: KyInstance) { this._logger = getLogger('token'); this._logger.enabled = true; + this._doLogin = createDoLogin(bareClient); this.restoreLogin(); } @@ -64,7 +68,7 @@ export class Auth { setLogin(login: LoginResponse) { this._accessToken = login.token; this._refreshToken = login.refresh; - this._user = Auth.parseIdToken(this._accessToken); + this._user = GoogleAuth.parseIdToken(this._accessToken); this.triggerChange(this._user); this.storeLogin(); @@ -94,14 +98,14 @@ export class Auth { } async initToken(token: string) { - const res = await doLogin({ token, type: 'Google' }); + const res = await this._doLogin({ token, type: 'Google' }); this.setLogin(res); return this._user; } async refreshToken(refreshToken?: string) { if (!this._padding) { - this._padding = doLogin({ + this._padding = this._doLogin({ type: 'Refresh', token: refreshToken || this._refreshToken, }); @@ -181,9 +185,11 @@ export class Auth { } } -export const auth = new Auth(); +export function createGoogleAuth(bareAuth: KyInstance): GoogleAuth { + return new GoogleAuth(bareAuth); +} -export const getAuthorizer = () => { +export const getAuthorizer = (googleAuth: GoogleAuth) => { let _firebaseAuth: FirebaseAuth | null = null; const logger = getLogger('authorizer'); @@ -225,7 +231,7 @@ export const getAuthorizer = () => { const idToken = await getToken(); let loginUser: AccessTokenMessage | null = null; if (idToken) { - loginUser = await auth.initToken(idToken); + loginUser = await googleAuth.initToken(idToken); } else { const firebaseAuth = getAuth(); if (firebaseAuth) { @@ -237,7 +243,7 @@ export const getAuthorizer = () => { }); const user = await signInWithPopup(firebaseAuth, googleAuthProvider); const idToken = await user.user.getIdToken(); - loginUser = await auth.initToken(idToken); + loginUser = await googleAuth.initToken(idToken); } } return loginUser; diff --git a/packages/data-center/src/provider/affine/apis/index.ts b/packages/data-center/src/provider/affine/apis/index.ts index cfdea1c7bf..4b4aa04206 100644 --- a/packages/data-center/src/provider/affine/apis/index.ts +++ b/packages/data-center/src/provider/affine/apis/index.ts @@ -1,10 +1,10 @@ -// export { token } from './token.js'; -export type { Callback } from './auth'; +export type { Callback } from './google'; -import { getAuthorizer } from './auth'; -import { auth } from './auth'; -import * as user from './user'; -import * as workspace from './workspace'; +import { KyInstance } from 'ky/distribution/types/ky'; + +import { createGoogleAuth, getAuthorizer, GoogleAuth } from './google'; +import { createUserApis } from './user'; +import { createWorkspaceApis } from './workspace'; // See https://twitter.com/mattpocockuk/status/1622730173446557697 // TODO: move to ts utils? @@ -14,27 +14,31 @@ type Prettify = { } & {}; export type Apis = Prettify< - typeof user & - Omit & { + ReturnType & + ReturnType & { signInWithGoogle: ReturnType[0]; onAuthStateChanged: ReturnType[1]; signOutFirebase: ReturnType[2]; - } & { auth: typeof auth } + } & { auth: ReturnType } >; -export const getApis = (): Apis => { +export const getApis = ( + bareClient: KyInstance, + authClient: KyInstance, + googleAuth: GoogleAuth +): Apis => { const [signInWithGoogle, onAuthStateChanged, signOutFirebase] = - getAuthorizer(); + getAuthorizer(googleAuth); return { - ...user, - ...workspace, + ...createUserApis(bareClient, authClient), + ...createWorkspaceApis(bareClient, authClient, googleAuth), signInWithGoogle, signOutFirebase, onAuthStateChanged, - auth, + auth: googleAuth, }; }; -export type { AccessTokenMessage } from './auth'; +export type { AccessTokenMessage } from './google'; export type { Member, Workspace, WorkspaceDetail } from './workspace'; export * from './workspace'; diff --git a/packages/data-center/src/provider/affine/apis/request.ts b/packages/data-center/src/provider/affine/apis/request.ts index 7783c96d7e..521bb3a072 100644 --- a/packages/data-center/src/provider/affine/apis/request.ts +++ b/packages/data-center/src/provider/affine/apis/request.ts @@ -1,7 +1,7 @@ import ky from 'ky-universal'; import { MessageCenter } from '../../../message'; -import { auth } from './auth'; +import { GoogleAuth } from './google'; type KyInstance = typeof ky; @@ -9,52 +9,57 @@ const messageCenter = MessageCenter.getInstance(); const _sendMessage = messageCenter.getMessageSender('affine'); -export const bareClient: KyInstance = ky.extend({ - prefixUrl: '/', - retry: 1, - // todo: report timeout error - timeout: 60000, - hooks: { - beforeError: [ - error => { - const { response } = error; - if (response.status === 401) { - _sendMessage(MessageCenter.messageCode.noPermission); - } - return error; - }, - ], - }, -}); +export const createBareClient = (prefixUrl: string): KyInstance => + ky.extend({ + prefixUrl: prefixUrl, + retry: 1, + // todo: report timeout error + timeout: 60000, + hooks: { + beforeError: [ + error => { + const { response } = error; + if (response.status === 401) { + _sendMessage(MessageCenter.messageCode.noPermission); + } + return error; + }, + ], + }, + }); -const refreshTokenIfExpired = async () => { - if (auth.isLogin && auth.isExpired) { +const refreshTokenIfExpired = async (googleAuth: GoogleAuth) => { + if (googleAuth.isLogin && googleAuth.isExpired) { try { - await auth.refreshToken(); + await googleAuth.refreshToken(); } catch (err) { return new Response('Unauthorized', { status: 401 }); } } }; -export const client: KyInstance = bareClient.extend({ - hooks: { - beforeRequest: [ - async request => { - if (auth.isLogin) { - await refreshTokenIfExpired(); - request.headers.set('Authorization', auth.token); - } else { - return new Response('Unauthorized', { status: 401 }); - } - }, - ], +export const createAuthClient = ( + bareClient: KyInstance, + googleAuth: GoogleAuth +): KyInstance => + bareClient.extend({ + hooks: { + beforeRequest: [ + async request => { + if (googleAuth.isLogin) { + await refreshTokenIfExpired(googleAuth); + request.headers.set('Authorization', googleAuth.token); + } else { + return new Response('Unauthorized', { status: 401 }); + } + }, + ], - beforeRetry: [ - async ({ request }) => { - await refreshTokenIfExpired(); - request.headers.set('Authorization', auth.token); - }, - ], - }, -}); + beforeRetry: [ + async ({ request }) => { + await refreshTokenIfExpired(googleAuth); + request.headers.set('Authorization', googleAuth.token); + }, + ], + }, + }); diff --git a/packages/data-center/src/provider/affine/apis/user.ts b/packages/data-center/src/provider/affine/apis/user.ts index 4a8827c6e8..15d3045a77 100644 --- a/packages/data-center/src/provider/affine/apis/user.ts +++ b/packages/data-center/src/provider/affine/apis/user.ts @@ -1,4 +1,4 @@ -import { client } from './request'; +import { KyInstance } from 'ky/distribution/types/ky'; export interface GetUserByEmailParams { email: string; @@ -13,9 +13,13 @@ export interface User { create_at: string; } -export async function getUserByEmail( - params: GetUserByEmailParams -): Promise { - const searchParams = new URLSearchParams({ ...params }); - return client.get('api/user', { searchParams }).json(); +export function createUserApis(bareClient: KyInstance, authClient: KyInstance) { + return { + getUserByEmail: async ( + params: GetUserByEmailParams + ): Promise => { + const searchParams = new URLSearchParams({ ...params }); + return authClient.get('api/user', { searchParams }).json(); + }, + } 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 1a74bcfa44..f156e1263b 100644 --- a/packages/data-center/src/provider/affine/apis/workspace.ts +++ b/packages/data-center/src/provider/affine/apis/workspace.ts @@ -1,5 +1,8 @@ +import { KyInstance } from 'ky/distribution/types/ky'; + import { MessageCenter } from '../../../message'; -import { bareClient, client } from './request'; +import { createBlocksuiteWorkspace as _createBlocksuiteWorkspace } from '../../../utils'; +import { GoogleAuth } from './google'; import type { User } from './user'; const messageCenter = MessageCenter.getInstance(); @@ -15,6 +18,7 @@ class RequestError extends Error { this.cause = cause; } } + export interface GetWorkspaceDetailParams { id: string; } @@ -39,37 +43,11 @@ export interface Workspace { create_at: number; } -export async function getWorkspaces(): Promise { - try { - return await client - .get('api/workspace', { - headers: { - 'Cache-Control': 'no-cache', - }, - }) - .json(); - } catch (error) { - sendMessage(messageCode.loadListFailed); - throw new RequestError('load list failed', error); - } -} - export interface WorkspaceDetail extends Workspace { owner: User; member_count: number; } -export async function getWorkspaceDetail( - params: GetWorkspaceDetailParams -): Promise { - try { - return await client.get(`api/workspace/${params.id}`).json(); - } catch (error) { - sendMessage(messageCode.getDetailFailed); - throw new RequestError('get detail failed', error); - } -} - export interface Permission { id: string; type: PermissionType; @@ -97,161 +75,194 @@ export interface GetWorkspaceMembersParams { id: string; } -export async function getWorkspaceMembers( - params: GetWorkspaceDetailParams -): Promise { - try { - return await client.get(`api/workspace/${params.id}/permission`).json(); - } catch (error) { - sendMessage(messageCode.getMembersFailed); - throw new RequestError('get members failed', error); - } -} - export interface CreateWorkspaceParams { name: string; } -export async function createWorkspace( - encodedYDoc: Blob -): Promise<{ id: string }> { - try { - return await client.post('api/workspace', { body: encodedYDoc }).json(); - } catch (error) { - sendMessage(messageCode.createWorkspaceFailed); - throw new RequestError('create workspace failed', error); - } -} - export interface UpdateWorkspaceParams { id: string; public: boolean; } -export async function updateWorkspace( - params: UpdateWorkspaceParams -): Promise<{ public: boolean | null }> { - try { - return await client - .post(`api/workspace/${params.id}`, { - json: { - public: params.public, - }, - }) - .json(); - } catch (error) { - sendMessage(messageCode.updateWorkspaceFailed); - throw new RequestError('update workspace failed', error); - } -} - export interface DeleteWorkspaceParams { id: string; } -export async function deleteWorkspace( - params: DeleteWorkspaceParams -): Promise { - try { - await client.delete(`api/workspace/${params.id}`); - } catch (error) { - sendMessage(messageCode.deleteWorkspaceFailed); - throw new RequestError('delete workspace failed', error); - } -} - export interface InviteMemberParams { id: string; email: string; } -/** - * Notice: Only support normal(contrast to private) workspace. - */ -export async function inviteMember(params: InviteMemberParams): Promise { - try { - await client.post(`api/workspace/${params.id}/permission`, { - json: { - email: params.email, - }, - }); - } catch (error) { - sendMessage(messageCode.inviteMemberFailed); - throw new RequestError('invite member failed', error); - } -} - export interface RemoveMemberParams { permissionId: number; } -export async function removeMember(params: RemoveMemberParams): Promise { - try { - await client.delete(`api/permission/${params.permissionId}`); - } catch (error) { - sendMessage(messageCode.removeMemberFailed); - throw new RequestError('remove member failed', error); - } -} - export interface AcceptInvitingParams { invitingCode: string; } -export async function acceptInviting( - params: AcceptInvitingParams -): Promise { - try { - return await bareClient - .post(`api/invitation/${params.invitingCode}`) - .json(); - } catch (error) { - sendMessage(messageCode.acceptInvitingFailed); - throw new RequestError('accept inviting failed', error); - } -} - -export async function uploadBlob(params: { blob: Blob }): Promise { - return client.put('api/blob', { body: params.blob }).text(); -} - -export async function getBlob(params: { - blobId: string; -}): Promise { - try { - return await client.get(`api/blob/${params.blobId}`).arrayBuffer(); - } catch (error) { - sendMessage(messageCode.getBlobFailed); - throw new RequestError('get blob failed', error); - } -} - export interface LeaveWorkspaceParams { id: number | string; } -export async function leaveWorkspace({ id }: LeaveWorkspaceParams) { - try { - await client.delete(`api/workspace/${id}/permission`); - } catch (error) { - sendMessage(messageCode.leaveWorkspaceFailed); - throw new RequestError('leave workspace failed', error); - } -} +export function createWorkspaceApis( + bareClient: KyInstance, + authClient: KyInstance, + googleAuth: GoogleAuth +) { + return { + getWorkspaces: async (): Promise => { + try { + return await authClient + .get('api/workspace', { + headers: { + 'Cache-Control': 'no-cache', + }, + }) + .json(); + } catch (error) { + sendMessage(messageCode.loadListFailed); + throw new RequestError('load list failed', error); + } + }, + getWorkspaceDetail: async ( + params: GetWorkspaceDetailParams + ): Promise => { + try { + return await authClient.get(`api/workspace/${params.id}`).json(); + } catch (error) { + sendMessage(messageCode.getDetailFailed); + throw new RequestError('get detail failed', error); + } + }, + getWorkspaceMembers: async ( + params: GetWorkspaceDetailParams + ): Promise => { + try { + return await authClient + .get(`api/workspace/${params.id}/permission`) + .json(); + } catch (error) { + sendMessage(messageCode.getMembersFailed); + throw new RequestError('get members failed', error); + } + }, + createWorkspace: async (encodedYDoc: Blob): Promise<{ id: string }> => { + try { + return await authClient + .post('api/workspace', { body: encodedYDoc }) + .json(); + } catch (error) { + sendMessage(messageCode.createWorkspaceFailed); + throw new RequestError('create workspace failed', error); + } + }, + updateWorkspace: async ( + params: UpdateWorkspaceParams + ): Promise<{ public: boolean | null }> => { + try { + return await authClient + .post(`api/workspace/${params.id}`, { + json: { + public: params.public, + }, + }) + .json(); + } catch (error) { + sendMessage(messageCode.updateWorkspaceFailed); + throw new RequestError('update workspace failed', error); + } + }, + deleteWorkspace: async (params: DeleteWorkspaceParams): Promise => { + try { + await authClient.delete(`api/workspace/${params.id}`); + } catch (error) { + sendMessage(messageCode.deleteWorkspaceFailed); + throw new RequestError('delete workspace failed', error); + } + }, -export async function downloadWorkspace( - workspaceId: string, - published = false -): Promise { - try { - if (published) { - return await bareClient - .get(`api/public/doc/${workspaceId}`) - .arrayBuffer(); - } - return await client.get(`api/workspace/${workspaceId}/doc`).arrayBuffer(); - } catch (error) { - sendMessage(messageCode.downloadWorkspaceFailed); - throw new RequestError('download workspace failed', error); - } + /** + * Notice: Only support normal(contrast to private) workspace. + */ + inviteMember: async (params: InviteMemberParams): Promise => { + try { + await authClient.post(`api/workspace/${params.id}/permission`, { + json: { + email: params.email, + }, + }); + } catch (error) { + sendMessage(messageCode.inviteMemberFailed); + throw new RequestError('invite member failed', error); + } + }, + removeMember: async (params: RemoveMemberParams): Promise => { + try { + await authClient.delete(`api/permission/${params.permissionId}`); + } catch (error) { + sendMessage(messageCode.removeMemberFailed); + throw new RequestError('remove member failed', error); + } + }, + acceptInviting: async ( + params: AcceptInvitingParams + ): Promise => { + try { + return await bareClient + .post(`api/invitation/${params.invitingCode}`) + .json(); + } catch (error) { + sendMessage(messageCode.acceptInvitingFailed); + throw new RequestError('accept inviting failed', error); + } + }, + uploadBlob: async (params: { blob: Blob }): Promise => { + return authClient.put('api/blob', { body: params.blob }).text(); + }, + getBlob: async (params: { blobId: string }): Promise => { + try { + return await authClient.get(`api/blob/${params.blobId}`).arrayBuffer(); + } catch (error) { + sendMessage(messageCode.getBlobFailed); + throw new RequestError('get blob failed', error); + } + }, + leaveWorkspace: async ({ id }: LeaveWorkspaceParams) => { + try { + await authClient.delete(`api/workspace/${id}/permission`); + } catch (error) { + sendMessage(messageCode.leaveWorkspaceFailed); + throw new RequestError('leave workspace failed', error); + } + }, + downloadWorkspace: async ( + workspaceId: string, + published = false + ): Promise => { + try { + if (published) { + return await bareClient + .get(`api/public/doc/${workspaceId}`) + .arrayBuffer(); + } + return await authClient + .get(`api/workspace/${workspaceId}/doc`) + .arrayBuffer(); + } catch (error) { + sendMessage(messageCode.downloadWorkspaceFailed); + throw new RequestError('download workspace failed', error); + } + }, + createBlockSuiteWorkspaceWithAuth: async (newWorkspaceId: string) => { + if (googleAuth.isExpired && googleAuth.isLogin) { + await googleAuth.refreshToken(); + } + return _createBlocksuiteWorkspace(newWorkspaceId, { + blobOptionsGetter: (k: string) => + // token could be expired + ({ api: '/api/workspace', token: googleAuth.token }[k]), + }); + }, + } as const; } diff --git a/packages/data-center/src/provider/affine/channel.ts b/packages/data-center/src/provider/affine/channel.ts index ac779f6680..3f301e3736 100644 --- a/packages/data-center/src/provider/affine/channel.ts +++ b/packages/data-center/src/provider/affine/channel.ts @@ -2,7 +2,7 @@ import * as url from 'lib0/url'; import * as websocket from 'lib0/websocket'; import { Logger } from '../../types'; -import { auth } from './apis/auth'; +import { GoogleAuth } from './apis/google'; const RECONNECT_INTERVAL_TIME = 500; const MAX_RECONNECT_TIMES = 50; @@ -11,9 +11,11 @@ export class WebsocketClient extends websocket.WebsocketClient { public shouldReconnect = false; private _logger: Logger; private _retryTimes = 0; + private _auth: GoogleAuth; constructor( serverUrl: string, logger: Logger, + auth: GoogleAuth, options?: ConstructorParameters[1] & { params: Record; } @@ -27,6 +29,7 @@ export class WebsocketClient extends websocket.WebsocketClient { const newUrl = serverUrl + '/' + (encodedParams.length === 0 ? '' : '?' + encodedParams); super(newUrl, options); + this._auth = auth; this._logger = logger; this._setupChannel(); } @@ -41,7 +44,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 && auth.isLogin && !this.connected) { + if (this.shouldReconnect && this._auth.isLogin && !this.connected) { try { setTimeout(() => { if (this._retryTimes <= MAX_RECONNECT_TIMES) { diff --git a/packages/data-center/src/provider/affine/utils.ts b/packages/data-center/src/provider/affine/utils.ts index 2c5f96f279..5a7c695cbf 100644 --- a/packages/data-center/src/provider/affine/utils.ts +++ b/packages/data-center/src/provider/affine/utils.ts @@ -1,29 +1,16 @@ -import { createBlocksuiteWorkspace as _createBlocksuiteWorkspace } from '../../utils'; import { applyUpdate } from '../../utils'; import type { WorkspaceUnitCtorParams } from '../../workspace-unit'; import { WorkspaceUnit } from '../../workspace-unit'; import { setDefaultAvatar } from '../utils'; import type { Apis } from './apis'; -import { auth } from './apis/auth'; import { getDatabase } from './idb-kv'; -export const createBlocksuiteWorkspaceWithAuth = async (id: string) => { - if (auth.isExpired && auth.isLogin) { - await auth.refreshToken(); - } - return _createBlocksuiteWorkspace(id, { - blobOptionsGetter: (k: string) => - // token could be expired - ({ api: '/api/workspace', token: auth.token }[k]), - }); -}; - export const loadWorkspaceUnit = async ( params: WorkspaceUnitCtorParams, apis: Apis ) => { const workspaceUnit = new WorkspaceUnit(params); - const blocksuiteWorkspace = await createBlocksuiteWorkspaceWithAuth( + const blocksuiteWorkspace = await apis.createBlockSuiteWorkspaceWithAuth( workspaceUnit.id ); @@ -54,10 +41,13 @@ export const loadWorkspaceUnit = async ( return workspaceUnit; }; -export const createWorkspaceUnit = async (params: WorkspaceUnitCtorParams) => { +export const createWorkspaceUnit = async ( + params: WorkspaceUnitCtorParams, + apis: Apis +) => { const workspaceUnit = new WorkspaceUnit(params); - const blocksuiteWorkspace = await createBlocksuiteWorkspaceWithAuth( + const blocksuiteWorkspace = await apis.createBlockSuiteWorkspaceWithAuth( workspaceUnit.id );