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

This commit is contained in:
DiamondThree
2023-01-11 20:45:58 +08:00
9 changed files with 208 additions and 20 deletions

View File

@@ -36,7 +36,6 @@
"ky-universal": "^0.11.0",
"lib0": "^0.2.58",
"swr": "^2.0.0",
"yjs": "^13.5.44",
"y-protocols": "^1.0.5"
},
"peerDependencies": {

View File

@@ -13,7 +13,7 @@ import assert from 'assert';
import { getLogger } from './logger';
import { createBlocksuiteWorkspace } from './utils/index.js';
import { MessageCenter } from './message';
import type { WorkspaceUnit } from './workspace-unit';
import { WorkspaceUnit } from './workspace-unit';
/**
* @class DataCenter
@@ -153,7 +153,7 @@ export class DataCenter {
/**
* load workspace instance by id
* @param {string} workspaceId workspace id
* @returns {Promise<BlocksuiteWorkspace>}
* @returns {Promise<WorkspaceUnit>}
*/
public async loadWorkspace(workspaceId: string) {
const workspaceUnit = this._workspaceUnitCollection.find(workspaceId);
@@ -175,6 +175,29 @@ export class DataCenter {
return workspaceUnit;
}
public async loadPublicWorkspace(workspaceId: string) {
const workspaceUnit = this._workspaceUnitCollection.find(workspaceId);
assert(workspaceUnit, 'Workspace not found');
const provider = this.providerMap.get(workspaceUnit.provider);
assert(provider);
const blocksuiteWorkspace = this._getBlocksuiteWorkspace(workspaceId);
await provider.loadPublicWorkspace(blocksuiteWorkspace);
const workspaceUnitForPublic = new WorkspaceUnit({
id: workspaceUnit.id,
name: workspaceUnit.name,
avatar: workspaceUnit.avatar,
owner: workspaceUnit.owner,
published: workspaceUnit.published,
provider: workspaceUnit.provider,
memberCount: workspaceUnit.memberCount,
syncMode: workspaceUnit.syncMode,
});
workspaceUnitForPublic.setBlocksuiteWorkspace(blocksuiteWorkspace);
return workspaceUnitForPublic;
}
/**
* get user info by provider id
* @param {string} providerId the provider name of workspace
@@ -206,7 +229,7 @@ export class DataCenter {
/**
* change workspaces meta
* @param {WorkspaceMeta} workspaceMeta workspace meta
* @param {BlocksuiteWorkspace} workspace workspace instance
* @param {WorkspaceUnit} workspace workspace instance
*/
public async updateWorkspaceMeta(
{ name, avatar }: UpdateWorkspaceMetaParams,
@@ -255,12 +278,17 @@ export class DataCenter {
}
}
public async inviteMember(id: string, email: string) {
const workspaceInfo = this._workspaceUnitCollection.find(id);
/**
* invite the new member to the workspace
* @param {string} workspaceId workspace id
* @param {string} email
*/
public async inviteMember(workspaceId: string, email: string) {
const workspaceInfo = this._workspaceUnitCollection.find(workspaceId);
assert(workspaceInfo, 'Workspace not found');
const provider = this.providerMap.get(workspaceInfo.provider);
if (provider) {
await provider.invite(id, email);
await provider.invite(workspaceId, email);
}
}
@@ -380,10 +408,10 @@ export class DataCenter {
* @returns {Promise<string | null>} blob url
*/
async getBlob(
workspace: BlocksuiteWorkspace,
workspaceUnit: WorkspaceUnit,
id: string
): Promise<string | null> {
const blob = await workspace.blobs;
const blob = await workspaceUnit.blocksuiteWorkspace?.blobs;
return (await blob?.get(id)) || '';
}
@@ -392,8 +420,8 @@ export class DataCenter {
* @param id
* @returns {Promise<string | null>} blob url
*/
async setBlob(workspace: BlocksuiteWorkspace, blob: Blob): Promise<string> {
const blobStorage = await workspace.blobs;
async setBlob(workspace: WorkspaceUnit, blob: Blob): Promise<string> {
const blobStorage = await workspace.blocksuiteWorkspace?.blobs;
return (await blobStorage?.set(blob)) || '';
}

View File

@@ -7,33 +7,47 @@ import type {
import type { User } from '../../types';
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
import { BlockSchema } from '@blocksuite/blocks/models';
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { storage } from './storage.js';
import assert from 'assert';
import { WebsocketProvider } from './sync.js';
// import { IndexedDBProvider } from '../local/indexeddb';
import { getApis, Member } from './apis/index.js';
import { getApis } from './apis/index.js';
import type { Apis, WorkspaceDetail, Callback } from './apis';
import { setDefaultAvatar } from '../utils.js';
import { MessageCode } from '../../message';
import { token } from './apis/token.js';
import { WebsocketClient } from './channel';
export interface AffineProviderConstructorParams
extends ProviderConstructorParams {
apis?: Apis;
}
const {
Y: { applyUpdate, encodeStateAsUpdate },
} = BlocksuiteWorkspace;
export class AffineProvider extends BaseProvider {
public id = 'affine';
private _workspacesCache: Map<string, BlocksuiteWorkspace> = new Map();
private _onTokenRefresh?: Callback = undefined;
private _wsMap: Map<string, WebsocketProvider> = new Map();
private _apis: Apis;
private _channel: WebsocketClient;
// private _idbMap: Map<string, IndexedDBProvider> = new Map();
constructor({ apis, ...params }: AffineProviderConstructorParams) {
super(params);
this._apis = apis || getApis();
this._channel = new WebsocketClient(
`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${
window.location.host
}/global/sync/`,
this._logger
);
if (token.isLogin) {
this._connectChannel();
}
}
override async init() {
@@ -64,6 +78,15 @@ export class AffineProvider extends BaseProvider {
}
}
private _connectChannel() {
this._channel.connect();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this._channel.on('message', (message: any) => {
console.log('message', message);
});
}
private _getWebsocketProvider(workspace: BlocksuiteWorkspace) {
const { doc, room } = workspace;
assert(room);
@@ -93,6 +116,11 @@ export class AffineProvider extends BaseProvider {
}
}
override async loadPublicWorkspace(blocksuiteWorkspace: BlocksuiteWorkspace) {
await this._applyCloudUpdates(blocksuiteWorkspace);
return blocksuiteWorkspace;
}
override async warpWorkspace(workspace: BlocksuiteWorkspace) {
await this._applyCloudUpdates(workspace);
const { room } = workspace;
@@ -207,6 +235,9 @@ export class AffineProvider extends BaseProvider {
}
}
const user = await this._apis.signInWithGoogle?.();
if (!this._channel.connected) {
this._connectChannel();
}
if (!user) {
this._messageCenter.send(MessageCode.loginError);
}
@@ -365,6 +396,8 @@ export class AffineProvider extends BaseProvider {
public override async logout(): Promise<void> {
token.clear();
this._channel.disconnect();
this._wsMap.forEach(ws => ws.disconnect());
storage.removeItem('token');
}

View File

@@ -0,0 +1,53 @@
import * as websocket from 'lib0/websocket';
import { Logger } from 'src/types';
import { token } from './apis/token';
const RECONNECT_INTERVAL_TIME = 5000;
const MAX_RECONNECT_TIMES = 50;
export class WebsocketClient extends websocket.WebsocketClient {
public shouldReconnect = false;
private _reconnectInterval: number | null = null;
private _logger: Logger;
constructor(
url: string,
logger: Logger,
options?: { binaryType: 'arraybuffer' | 'blob' | null }
) {
super(url, options);
this._logger = logger;
this._setupChannel();
}
private _setupChannel() {
this.on('connect', () => {
this._logger('Affine channel connected');
this.shouldReconnect = true;
if (this._reconnectInterval) {
window.clearInterval(this._reconnectInterval);
}
});
this.on('disconnect', ({ error }: { error: Error }) => {
if (error) {
let times = 0;
// Try reconnect if connect error has occurred
this._reconnectInterval = window.setInterval(() => {
if (this.shouldReconnect && token.isLogin && !this.connected) {
try {
this.connect();
this._logger(`try reconnect channel ${++times} times`);
if (times > MAX_RECONNECT_TIMES) {
this._logger('reconnect failed, max reconnect times reached');
this._reconnectInterval &&
window.clearInterval(this._reconnectInterval);
}
} catch (e) {
this._logger('reconnect failed', e);
}
}
}, RECONNECT_INTERVAL_TIME);
}
});
}
}

View File

@@ -75,6 +75,15 @@ export class BaseProvider {
return workspace;
}
/**
* @deprecated Temporary for public workspace
* @param blocksuiteWorkspace
* @returns
*/
public async loadPublicWorkspace(blocksuiteWorkspace: BlocksuiteWorkspace) {
return blocksuiteWorkspace;
}
/**
* load workspaces
**/

View File

@@ -1,14 +1,19 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as idb from 'lib0/indexeddb.js';
import { Observable } from 'lib0/observable.js';
import type { Doc } from 'yjs';
import { applyUpdate, encodeStateAsUpdate, transact } from 'yjs';
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
const customStoreName = 'custom';
const updatesStoreName = 'updates';
const PREFERRED_TRIM_SIZE = 500;
const {
Y: { applyUpdate, transact, encodeStateAsUpdate },
} = BlocksuiteWorkspace;
type Doc = Parameters<typeof transact>[0];
const fetchUpdates = async (provider: IndexedDBProvider) => {
const [updatesStore] = idb.transact(provider.db as IDBDatabase, [
updatesStoreName,