Merge branch 'feat/cloud-sync-saika' into feat/datacenter-dev

This commit is contained in:
DiamondThree
2023-01-12 17:15:52 +08:00
9 changed files with 220 additions and 64 deletions

View File

@@ -23,7 +23,7 @@ export class DataCenter {
private readonly _workspaceUnitCollection = new WorkspaceUnitCollection(); private readonly _workspaceUnitCollection = new WorkspaceUnitCollection();
private readonly _logger = getLogger('dc'); private readonly _logger = getLogger('dc');
private _workspaceInstances: Map<string, BlocksuiteWorkspace> = new Map(); private _workspaceInstances: Map<string, BlocksuiteWorkspace> = new Map();
private _messageCenter = new MessageCenter(); private _messageCenter = MessageCenter.getInstance();
/** /**
* A mainProvider must exist as the only data trustworthy source. * A mainProvider must exist as the only data trustworthy source.
*/ */

View File

@@ -1,3 +1,61 @@
export enum MessageCode { export enum MessageCode {
loginError, loginError,
noPermission,
loadListFailed,
getDetailFailed,
createWorkspaceFailed,
getMembersFailed,
updateWorkspaceFailed,
deleteWorkspaceFailed,
inviteMemberFailed,
removeMemberFailed,
acceptInvitingFailed,
getBlobFailed,
leaveWorkspaceFailed,
downloadWorkspaceFailed,
} }
export const messages = {
[MessageCode.loginError]: {
message: 'Login failed',
},
[MessageCode.noPermission]: {
message: 'No permission',
},
[MessageCode.loadListFailed]: {
message: 'Load list failed',
},
[MessageCode.getDetailFailed]: {
message: 'Get detail failed',
},
[MessageCode.createWorkspaceFailed]: {
message: 'Create workspace failed',
},
[MessageCode.getMembersFailed]: {
message: 'Get members failed',
},
[MessageCode.updateWorkspaceFailed]: {
message: 'Update workspace failed',
},
[MessageCode.deleteWorkspaceFailed]: {
message: 'Delete workspace failed',
},
[MessageCode.inviteMemberFailed]: {
message: 'Invite member failed',
},
[MessageCode.removeMemberFailed]: {
message: 'Remove member failed',
},
[MessageCode.acceptInvitingFailed]: {
message: 'Accept inviting failed',
},
[MessageCode.getBlobFailed]: {
message: 'Get blob failed',
},
[MessageCode.leaveWorkspaceFailed]: {
message: 'Leave workspace failed',
},
[MessageCode.downloadWorkspaceFailed]: {
message: 'Download workspace failed',
},
};

View File

@@ -1,24 +1,36 @@
import { Observable } from 'lib0/observable'; import { Observable } from 'lib0/observable';
import { Message } from '../types'; import { Message } from '../types';
import { MessageCode } from './code.js'; import { MessageCode, messages } from './code.js';
export class MessageCenter extends Observable<string> { export class MessageCenter extends Observable<string> {
private _messages: Record<number, Omit<Message, 'provider' | 'code'>> =
messages;
constructor() { constructor() {
super(); super();
} }
public send(message: MessageCode) { static instance: MessageCenter;
this.emit('message', [message]);
static getInstance() {
if (!MessageCenter.instance) {
MessageCenter.instance = new MessageCenter();
}
return MessageCenter.instance;
}
static messageCode = MessageCode;
public getMessageSender(provider: string) {
return this._send.bind(this, provider);
}
private _send(provider: string, messageCode: MessageCode) {
this.emit('message', [
{ ...this._messages[messageCode], provider, code: messageCode },
]);
} }
public onMessage(callback: (message: Message) => void) { public onMessage(callback: (message: Message) => void) {
this.on('message', callback); this.on('message', callback);
} }
private messages: Record<number, Message> = {
[MessageCode.loginError]: {
code: MessageCode.loginError,
message: 'Login failed',
},
};
} }

View File

@@ -2,7 +2,6 @@ import { BaseProvider } from '../base.js';
import type { import type {
ProviderConstructorParams, ProviderConstructorParams,
CreateWorkspaceInfoParams, CreateWorkspaceInfoParams,
WorkspaceMeta0,
} from '../base'; } from '../base';
import type { User } from '../../types'; import type { User } from '../../types';
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store'; import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
@@ -12,8 +11,6 @@ import { WebsocketProvider } from './sync.js';
// import { IndexedDBProvider } from '../local/indexeddb'; // import { IndexedDBProvider } from '../local/indexeddb';
import { getApis, Workspace } from './apis/index.js'; import { getApis, Workspace } from './apis/index.js';
import type { Apis, WorkspaceDetail, Callback } from './apis'; import type { Apis, WorkspaceDetail, Callback } from './apis';
import { setDefaultAvatar } from '../utils.js';
import { MessageCode } from '../../message/index.js';
import { token } from './apis/token.js'; import { token } from './apis/token.js';
import { WebsocketClient } from './channel'; import { WebsocketClient } from './channel';
import { import {
@@ -24,6 +21,7 @@ import {
import { WorkspaceUnit } from '../../workspace-unit.js'; import { WorkspaceUnit } from '../../workspace-unit.js';
import { createBlocksuiteWorkspace, applyUpdate } from '../../utils/index.js'; import { createBlocksuiteWorkspace, applyUpdate } from '../../utils/index.js';
import type { SyncMode } from '../../workspace-unit'; import type { SyncMode } from '../../workspace-unit';
import { MessageCenter } from 'src/message/message.js';
type ChannelMessage = { type ChannelMessage = {
ws_list: Workspace[]; ws_list: Workspace[];
@@ -42,7 +40,6 @@ const {
export class AffineProvider extends BaseProvider { export class AffineProvider extends BaseProvider {
public id = 'affine'; public id = 'affine';
private _workspacesCache: Map<string, BlocksuiteWorkspace> = new Map();
private _onTokenRefresh?: Callback = undefined; private _onTokenRefresh?: Callback = undefined;
private _wsMap: Map<string, WebsocketProvider> = new Map(); private _wsMap: Map<string, WebsocketProvider> = new Map();
private _apis: Apis; private _apis: Apis;
@@ -121,7 +118,7 @@ export class AffineProvider extends BaseProvider {
}, },
published: detail.public, published: detail.public,
memberCount: detail.member_count, memberCount: detail.member_count,
provider: 'affine', provider: this.id,
syncMode: 'core' as SyncMode, syncMode: 'core' as SyncMode,
}; };
if (this._workspaces.get(id)) { if (this._workspaces.get(id)) {
@@ -207,7 +204,7 @@ export class AffineProvider extends BaseProvider {
owner: undefined, owner: undefined,
published: w.public, published: w.public,
memberCount: 1, memberCount: 1,
provider: 'affine', provider: this.id,
syncMode: 'core', syncMode: 'core',
}, },
this._apis this._apis
@@ -232,7 +229,7 @@ export class AffineProvider extends BaseProvider {
this._connectChannel(); this._connectChannel();
} }
if (!user) { if (!user) {
this._messageCenter.send(MessageCode.loginError); this._sendMessage(MessageCenter.messageCode.loginError);
} }
} }
@@ -258,17 +255,17 @@ export class AffineProvider extends BaseProvider {
} }
public override async clear(): Promise<void> { public override async clear(): Promise<void> {
for (const w of this._workspacesCache.values()) { for (const w of this._workspaces.list()) {
if (w.room) { if (w.id) {
try { try {
await this.deleteWorkspace(w.room); await this.deleteWorkspace(w.id);
this._workspaces.remove(w.room); this._workspaces.remove(w.id);
} catch (e) { } catch (e) {
this._logger('has a problem of delete workspace ', e); this._logger('has a problem of delete workspace ', e);
} }
} }
} }
this._workspacesCache.clear(); this._workspaces.clear();
} }
public override async closeWorkspace(id: string) { public override async closeWorkspace(id: string) {
@@ -314,7 +311,7 @@ export class AffineProvider extends BaseProvider {
owner: await this.getUserInfo(), owner: await this.getUserInfo(),
published: false, published: false,
memberCount: 1, memberCount: 1,
provider: 'affine', provider: this.id,
syncMode: 'core', syncMode: 'core',
}); });
@@ -359,7 +356,7 @@ export class AffineProvider extends BaseProvider {
owner: await this.getUserInfo(), owner: await this.getUserInfo(),
published: false, published: false,
memberCount: 1, memberCount: 1,
provider: 'affine', provider: this.id,
syncMode: 'core', syncMode: 'core',
}); });

View File

@@ -1,7 +1,11 @@
import kyOrigin from 'ky';
import ky from 'ky-universal'; import ky from 'ky-universal';
import { MessageCenter } from '../../../message/index.js';
import { token } from './token.js'; import { token } from './token.js';
const messageCenter = MessageCenter.getInstance();
const _sendMessage = messageCenter.getMessageSender('affine');
export const bareClient = ky.extend({ export const bareClient = ky.extend({
prefixUrl: 'http://localhost:8080', prefixUrl: 'http://localhost:8080',
retry: 1, retry: 1,
@@ -41,5 +45,15 @@ export const client = bareClient.extend({
request.headers.set('Authorization', token.token); request.headers.set('Authorization', token.token);
}, },
], ],
beforeError: [
error => {
const { response } = error;
if (response.status === 401) {
_sendMessage(MessageCenter.messageCode.noPermission);
}
return error;
},
],
}, },
}); });

View File

@@ -1,6 +1,20 @@
import { MessageCenter } from '../../../message/index.js';
import { bareClient, client } from './request.js'; import { bareClient, client } from './request.js';
import type { User } from './user'; import type { User } from './user';
const messageCenter = MessageCenter.getInstance();
const sendMessage = messageCenter.getMessageSender('affine');
const { messageCode } = MessageCenter;
class RequestError extends Error {
constructor(message: string, cause: unknown | null = null) {
super(message);
this.name = 'RequestError';
this.cause = cause;
}
}
export interface GetWorkspaceDetailParams { export interface GetWorkspaceDetailParams {
id: string; id: string;
} }
@@ -26,13 +40,18 @@ export interface Workspace {
} }
export async function getWorkspaces(): Promise<Workspace[]> { export async function getWorkspaces(): Promise<Workspace[]> {
return client try {
.get('api/workspace', { return client
headers: { .get('api/workspace', {
'Cache-Control': 'no-cache', headers: {
}, 'Cache-Control': 'no-cache',
}) },
.json(); })
.json();
} catch (error) {
sendMessage(messageCode.loadListFailed);
throw new RequestError('load list failed', error);
}
} }
export interface WorkspaceDetail extends Workspace { export interface WorkspaceDetail extends Workspace {
@@ -43,7 +62,13 @@ export interface WorkspaceDetail extends Workspace {
export async function getWorkspaceDetail( export async function getWorkspaceDetail(
params: GetWorkspaceDetailParams params: GetWorkspaceDetailParams
): Promise<WorkspaceDetail | null> { ): Promise<WorkspaceDetail | null> {
return client.get(`api/workspace/${params.id}`).json(); try {
const response = client.get(`api/workspace/${params.id}`);
return response.json();
} catch (error) {
sendMessage(messageCode.getDetailFailed);
throw new RequestError('get detail failed', error);
}
} }
export interface Permission { export interface Permission {
@@ -74,7 +99,12 @@ export interface GetWorkspaceMembersParams {
export async function getWorkspaceMembers( export async function getWorkspaceMembers(
params: GetWorkspaceDetailParams params: GetWorkspaceDetailParams
): Promise<Member[]> { ): Promise<Member[]> {
return client.get(`api/workspace/${params.id}/permission`).json(); try {
return client.get(`api/workspace/${params.id}/permission`).json();
} catch (error) {
sendMessage(messageCode.getMembersFailed);
throw new RequestError('get members failed', error);
}
} }
export interface CreateWorkspaceParams { export interface CreateWorkspaceParams {
@@ -84,10 +114,12 @@ export interface CreateWorkspaceParams {
export async function createWorkspace( export async function createWorkspace(
params: CreateWorkspaceParams params: CreateWorkspaceParams
): Promise<{ id: string }> { ): Promise<{ id: string }> {
// FIXME: avatar should be optional try {
return client return client.post('api/workspace', { json: params }).json();
.post('api/workspace', { json: { ...params, avatar: '123' } }) } catch (error) {
.json(); sendMessage(messageCode.createWorkspaceFailed);
throw new RequestError('create workspace failed', error);
}
} }
export interface UpdateWorkspaceParams { export interface UpdateWorkspaceParams {
@@ -98,13 +130,18 @@ export interface UpdateWorkspaceParams {
export async function updateWorkspace( export async function updateWorkspace(
params: UpdateWorkspaceParams params: UpdateWorkspaceParams
): Promise<{ public: boolean | null }> { ): Promise<{ public: boolean | null }> {
return client try {
.post(`api/workspace/${params.id}`, { return client
json: { .post(`api/workspace/${params.id}`, {
public: params.public, json: {
}, public: params.public,
}) },
.json(); })
.json();
} catch (error) {
sendMessage(messageCode.updateWorkspaceFailed);
throw new RequestError('update workspace failed', error);
}
} }
export interface DeleteWorkspaceParams { export interface DeleteWorkspaceParams {
@@ -114,7 +151,10 @@ export interface DeleteWorkspaceParams {
export async function deleteWorkspace( export async function deleteWorkspace(
params: DeleteWorkspaceParams params: DeleteWorkspaceParams
): Promise<void> { ): Promise<void> {
await client.delete(`api/workspace/${params.id}`); await client.delete(`api/workspace/${params.id}`).catch(error => {
sendMessage(messageCode.deleteWorkspaceFailed);
throw new RequestError('delete workspace failed', error);
});
} }
export interface InviteMemberParams { export interface InviteMemberParams {
@@ -126,13 +166,18 @@ export interface InviteMemberParams {
* Notice: Only support normal(contrast to private) workspace. * Notice: Only support normal(contrast to private) workspace.
*/ */
export async function inviteMember(params: InviteMemberParams): Promise<void> { export async function inviteMember(params: InviteMemberParams): Promise<void> {
return client try {
.post(`api/workspace/${params.id}/permission`, { return client
json: { .post(`api/workspace/${params.id}/permission`, {
email: params.email, json: {
}, email: params.email,
}) },
.json(); })
.json();
} catch (error) {
sendMessage(messageCode.inviteMemberFailed);
throw new RequestError('invite member failed', error);
}
} }
export interface RemoveMemberParams { export interface RemoveMemberParams {
@@ -140,7 +185,10 @@ export interface RemoveMemberParams {
} }
export async function removeMember(params: RemoveMemberParams): Promise<void> { export async function removeMember(params: RemoveMemberParams): Promise<void> {
await client.delete(`api/permission/${params.permissionId}`); await client.delete(`api/permission/${params.permissionId}`).catch(error => {
sendMessage(messageCode.removeMemberFailed);
throw new RequestError('remove member failed', error);
});
} }
export interface AcceptInvitingParams { export interface AcceptInvitingParams {
@@ -150,7 +198,12 @@ export interface AcceptInvitingParams {
export async function acceptInviting( export async function acceptInviting(
params: AcceptInvitingParams params: AcceptInvitingParams
): Promise<void> { ): Promise<void> {
await bareClient.post(`api/invitation/${params.invitingCode}`); await bareClient
.post(`api/invitation/${params.invitingCode}`)
.catch(error => {
sendMessage(messageCode.acceptInvitingFailed);
throw new RequestError('accept inviting failed', error);
});
} }
export async function uploadBlob(params: { blob: Blob }): Promise<string> { export async function uploadBlob(params: { blob: Blob }): Promise<string> {
@@ -160,7 +213,12 @@ export async function uploadBlob(params: { blob: Blob }): Promise<string> {
export async function getBlob(params: { export async function getBlob(params: {
blobId: string; blobId: string;
}): Promise<ArrayBuffer> { }): Promise<ArrayBuffer> {
return client.get(`api/blob/${params.blobId}`).arrayBuffer(); try {
return client.get(`api/blob/${params.blobId}`).arrayBuffer();
} catch (error) {
sendMessage(messageCode.getBlobFailed);
throw new RequestError('get blob failed', error);
}
} }
export interface LeaveWorkspaceParams { export interface LeaveWorkspaceParams {
@@ -168,15 +226,26 @@ export interface LeaveWorkspaceParams {
} }
export async function leaveWorkspace({ id }: LeaveWorkspaceParams) { export async function leaveWorkspace({ id }: LeaveWorkspaceParams) {
await client.delete(`api/workspace/${id}/permission`).json(); await client
.delete(`api/workspace/${id}/permission`)
.json()
.catch(error => {
sendMessage(messageCode.leaveWorkspaceFailed);
throw new RequestError('leave workspace failed', error);
});
} }
export async function downloadWorkspace( export async function downloadWorkspace(
workspaceId: string, workspaceId: string,
published = false published = false
): Promise<ArrayBuffer> { ): Promise<ArrayBuffer> {
if (published) { try {
return bareClient.get(`api/public/doc/${workspaceId}`).arrayBuffer(); if (published) {
return bareClient.get(`api/public/doc/${workspaceId}`).arrayBuffer();
}
return client.get(`api/workspace/${workspaceId}/doc`).arrayBuffer();
} catch (error) {
sendMessage(messageCode.downloadWorkspaceFailed);
throw new RequestError('download workspace failed', error);
} }
return client.get(`api/workspace/${workspaceId}/doc`).arrayBuffer();
} }

View File

@@ -22,10 +22,15 @@ export type UpdateWorkspaceMetaParams = Partial<
>; >;
export class BaseProvider { export class BaseProvider {
/** provider id */
public readonly id: string = 'base'; public readonly id: string = 'base';
/** workspace unit collection */
protected _workspaces!: WorkspaceUnitCollectionScope; protected _workspaces!: WorkspaceUnitCollectionScope;
protected _logger!: Logger; protected _logger!: Logger;
protected _messageCenter!: MessageCenter; /** send message with message center */
protected _sendMessage!: ReturnType<
InstanceType<typeof MessageCenter>['getMessageSender']
>;
public constructor({ public constructor({
logger, logger,
@@ -34,7 +39,7 @@ export class BaseProvider {
}: ProviderConstructorParams) { }: ProviderConstructorParams) {
this._logger = (logger || defaultLogger) as Logger; this._logger = (logger || defaultLogger) as Logger;
this._workspaces = workspaces; this._workspaces = workspaces;
this._messageCenter = messageCenter; this._sendMessage = messageCenter.getMessageSender(this.id);
} }
/** /**

View File

@@ -101,7 +101,7 @@ export class LocalProvider extends BaseProvider {
owner: undefined, owner: undefined,
syncMode: 'core', syncMode: 'core',
memberCount: 1, memberCount: 1,
provider: 'local', provider: this.id,
}); });
this._workspaces.add(workspaceUnit); this._workspaces.add(workspaceUnit);
this._storeWorkspaces(this._workspaces.list()); this._storeWorkspaces(this._workspaces.list());

View File

@@ -25,4 +25,5 @@ export type Logger = ReturnType<typeof getLogger>;
export type Message = { export type Message = {
code: number; code: number;
message: string; message: string;
provider: string;
}; };