refactor(data-center): remove side effect in affine provider (#1106)

This commit is contained in:
Himself65 2023-02-17 16:25:08 -06:00 committed by GitHub
parent 7849254785
commit 4647d44972
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 343 additions and 296 deletions

View File

@ -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}</>;
}

View File

@ -78,19 +78,19 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
<ConfirmProvider key="ConfirmProvider" />,
]}
>
<MessageCenterHandler>
{NoNeedAppStatePageList.includes(router.route) ? (
getLayout(<Component {...pageProps} />)
) : (
<Suspense fallback={<PageLoading />}>
<DataCenterPreloader>
{NoNeedAppStatePageList.includes(router.route) ? (
getLayout(<Component {...pageProps} />)
) : (
<Suspense fallback={<PageLoading />}>
<DataCenterPreloader>
<MessageCenterHandler>
<AppDefender>
{getLayout(<Component {...pageProps} />)}
</AppDefender>
</DataCenterPreloader>
</Suspense>
)}
</MessageCenterHandler>
</MessageCenterHandler>
</DataCenterPreloader>
</Suspense>
)}
</ProviderComposer>
</GlobalAppProvider>
</>

View File

@ -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';

View File

@ -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<BlocksuiteWorkspace, WebsocketProvider> = 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<WorkspaceUnit | undefined> {
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,

View File

@ -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);
});
});

View File

@ -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<LoginResponse> =>
bareClient.post('api/user/token', { json: params }).json();
const createDoLogin =
(bareClient: KyInstance) =>
(params: LoginParams): Promise<LoginResponse> =>
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<LoginResponse>;
private readonly _doLogin: ReturnType<typeof createDoLogin>;
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;

View File

@ -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<T> = {
} & {};
export type Apis = Prettify<
typeof user &
Omit<typeof workspace, 'WorkspaceType' | 'PermissionType'> & {
ReturnType<typeof createUserApis> &
ReturnType<typeof createWorkspaceApis> & {
signInWithGoogle: ReturnType<typeof getAuthorizer>[0];
onAuthStateChanged: ReturnType<typeof getAuthorizer>[1];
signOutFirebase: ReturnType<typeof getAuthorizer>[2];
} & { auth: typeof auth }
} & { auth: ReturnType<typeof createGoogleAuth> }
>;
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';

View File

@ -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);
},
],
},
});

View File

@ -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<User[] | null> {
const searchParams = new URLSearchParams({ ...params });
return client.get('api/user', { searchParams }).json<User[] | null>();
export function createUserApis(bareClient: KyInstance, authClient: KyInstance) {
return {
getUserByEmail: async (
params: GetUserByEmailParams
): Promise<User[] | null> => {
const searchParams = new URLSearchParams({ ...params });
return authClient.get('api/user', { searchParams }).json<User[] | null>();
},
} as const;
}

View File

@ -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<Workspace[]> {
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<WorkspaceDetail | null> {
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<Member[]> {
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<void> {
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<void> {
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<void> {
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<Permission> {
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<string> {
return client.put('api/blob', { body: params.blob }).text();
}
export async function getBlob(params: {
blobId: string;
}): Promise<ArrayBuffer> {
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<Workspace[]> => {
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<WorkspaceDetail | null> => {
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<Member[]> => {
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<void> => {
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<ArrayBuffer> {
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<void> => {
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<void> => {
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<Permission> => {
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<string> => {
return authClient.put('api/blob', { body: params.blob }).text();
},
getBlob: async (params: { blobId: string }): Promise<ArrayBuffer> => {
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<ArrayBuffer> => {
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;
}

View File

@ -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<typeof websocket.WebsocketClient>[1] & {
params: Record<string, string>;
}
@ -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) {

View File

@ -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
);