diff --git a/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx b/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx index 1abc639a26..a700708457 100644 --- a/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx +++ b/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx @@ -20,7 +20,7 @@ const DynamicBlocksuite = ({ new Promise(async resolve => { if (workspaceId) { const workspace = await getDataCenter().then(dc => - dc.initWorkspace(workspaceId, 'affine') + dc.getWorkspace(workspaceId, 'affine') ); resolve(workspace); diff --git a/packages/data-center/src/datacenter/datacenter.ts b/packages/data-center/src/datacenter/datacenter.ts index 41634b39fa..45e7336743 100644 --- a/packages/data-center/src/datacenter/datacenter.ts +++ b/packages/data-center/src/datacenter/datacenter.ts @@ -13,18 +13,18 @@ export class DataCenter { private readonly _config; private readonly _logger; - static async init(): Promise { - const dc = new DataCenter(); + static async init(debug: boolean): Promise { + const dc = new DataCenter(debug); dc.addProvider(AffineProvider); dc.addProvider(LocalProvider); return dc; } - private constructor() { + private constructor(debug: boolean) { this._config = getKVConfigure('sys'); this._logger = getLogger('dc'); - this._logger.enabled = true; + this._logger.enabled = debug; } private addProvider(provider: typeof BaseProvider) { @@ -43,7 +43,7 @@ export class DataCenter { throw Error(`Provider ${providerId} not found`); } - private async _initWorkspace(id: string, pid: string): Promise { + private async _getWorkspace(id: string, pid: string): Promise { this._logger(`Init workspace ${id} with ${pid}`); const providerId = await this._getProvider(id, pid); @@ -57,6 +57,7 @@ export class DataCenter { await provider.init({ config: getKVConfigure(id), + debug: this._logger.enabled, logger: this._logger.extend(`${Provider.id}:${id}`), workspace, }); @@ -66,13 +67,13 @@ export class DataCenter { return provider; } - async initWorkspace( + async getWorkspace( id: string, provider = 'local' ): Promise { if (id) { if (!this._workspaces.has(id)) { - this._workspaces.set(id, this._initWorkspace(id, provider)); + this._workspaces.set(id, this._getWorkspace(id, provider)); } const workspace = this._workspaces.get(id); assert(workspace); @@ -86,10 +87,24 @@ export class DataCenter { return config.set(key, value); } - async getWorkspaceList() { + async listWorkspace() { const keys = await this._config.keys(); return keys .filter(k => k.startsWith('workspace:')) .map(k => k.split(':')[1]); } + + async removeWorkspace(id: string) { + await this._config.delete(`workspace:${id}:provider`); + const provider = await this._workspaces.get(id); + if (provider) { + this._workspaces.delete(id); + await provider.clear(); + } + } + + async clearWorkspaces() { + const workspaces = await this.listWorkspace(); + await Promise.all(workspaces.map(id => this.removeWorkspace(id))); + } } diff --git a/packages/data-center/src/datacenter/index.ts b/packages/data-center/src/datacenter/index.ts index a95a63a365..b14a9f50aa 100644 --- a/packages/data-center/src/datacenter/index.ts +++ b/packages/data-center/src/datacenter/index.ts @@ -4,9 +4,9 @@ import { DataCenter } from './datacenter.js'; const _initializeDataCenter = () => { let _dataCenterInstance: Promise; - return () => { + return (debug = true) => { if (!_dataCenterInstance) { - _dataCenterInstance = DataCenter.init(); + _dataCenterInstance = DataCenter.init(debug); } return _dataCenterInstance; diff --git a/packages/data-center/src/datacenter/provider/affine/index.ts b/packages/data-center/src/datacenter/provider/affine/index.ts index 302183d390..c9c1b0e096 100644 --- a/packages/data-center/src/datacenter/provider/affine/index.ts +++ b/packages/data-center/src/datacenter/provider/affine/index.ts @@ -5,11 +5,13 @@ import type { InitialParams } from '../index.js'; import { LocalProvider } from '../local/index.js'; import { downloadWorkspace } from './apis.js'; +import { WebsocketProvider } from './sync.js'; import { token, Callback } from './token.js'; export class AffineProvider extends LocalProvider { static id = 'affine'; private _onTokenRefresh?: Callback = undefined; + private _ws?: WebsocketProvider; constructor() { super(); @@ -64,7 +66,21 @@ export class AffineProvider extends LocalProvider { try { const updates = await downloadWorkspace(workspace.room); if (updates) { - applyUpdate(doc, new Uint8Array(updates)); + await new Promise(resolve => { + doc.once('update', resolve); + applyUpdate(doc, new Uint8Array(updates)); + }); + // TODO: wait util data loaded + this._ws = new WebsocketProvider('/', workspace.room, doc); + // Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later + await new Promise((resolve, reject) => { + // TODO: synced will also be triggered on reconnection after losing sync + // There needs to be an event mechanism to emit the synchronization state to the upper layer + assert(this._ws); + this._ws.once('synced', () => resolve()); + this._ws.once('lost-connection', () => resolve()); + this._ws.once('connection-error', () => reject()); + }); } } catch (e) { this._logger('Failed to init cloud workspace', e); diff --git a/packages/data-center/src/websocket/y-websocket.js b/packages/data-center/src/datacenter/provider/affine/sync.js similarity index 100% rename from packages/data-center/src/websocket/y-websocket.js rename to packages/data-center/src/datacenter/provider/affine/sync.js diff --git a/packages/data-center/src/datacenter/provider/base.ts b/packages/data-center/src/datacenter/provider/base.ts index 202ff859c3..45f22b8a3e 100644 --- a/packages/data-center/src/datacenter/provider/base.ts +++ b/packages/data-center/src/datacenter/provider/base.ts @@ -17,7 +17,12 @@ export class BaseProvider { this._config = params.config; this._logger = params.logger; this._workspace = params.workspace; - this._logger.enabled = true; + this._logger.enabled = params.debug; + } + + async clear() { + await this.destroy(); + await this._config.clear(); } async destroy() { diff --git a/packages/data-center/src/datacenter/provider/index.ts b/packages/data-center/src/datacenter/provider/index.ts index aa9a4cd871..9c2c798137 100644 --- a/packages/data-center/src/datacenter/provider/index.ts +++ b/packages/data-center/src/datacenter/provider/index.ts @@ -7,6 +7,7 @@ export type Logger = ReturnType; export type InitialParams = { config: ConfigStore; + debug: boolean; logger: Logger; workspace: Workspace; }; diff --git a/packages/data-center/src/datacenter/provider/local/index.ts b/packages/data-center/src/datacenter/provider/local/index.ts index 31c7677631..5c3a89f23a 100644 --- a/packages/data-center/src/datacenter/provider/local/index.ts +++ b/packages/data-center/src/datacenter/provider/local/index.ts @@ -33,6 +33,12 @@ export class LocalProvider extends BaseProvider { this._logger('Local data loaded'); } + async clear() { + await super.clear(); + await this._blobs.clear(); + this._idb?.clearData(); + } + async destroy(): Promise { super.destroy(); if (this._idb) { diff --git a/packages/data-center/src/datacenter/store.ts b/packages/data-center/src/datacenter/store.ts index 2b8e5473e9..108b1ff28b 100644 --- a/packages/data-center/src/datacenter/store.ts +++ b/packages/data-center/src/datacenter/store.ts @@ -1,10 +1,11 @@ -import { createStore, del, get, keys, set } from 'idb-keyval'; +import { createStore, del, get, keys, set, clear } from 'idb-keyval'; export type ConfigStore = { get: (key: string) => Promise; set: (key: string, value: T) => Promise; keys: () => Promise; delete: (key: string) => Promise; + clear: () => Promise; }; const initialIndexedDB = (database: string): ConfigStore => { @@ -14,6 +15,7 @@ const initialIndexedDB = (database: string): ConfigStore => { set: (key: string, value: T) => set(key, value, store), keys: () => keys(store), delete: (key: string) => del(key, store), + clear: () => clear(store), }; }; @@ -34,7 +36,16 @@ const scopedIndexedDB = () => { .filter(k => k.startsWith(`${scope}:`)) .map(k => k.replace(`${scope}:`, '')) ), - delete: (key: string) => del(`${scope}:${key}`), + delete: (key: string) => idb.delete(`${scope}:${key}`), + clear: async () => { + await idb + .keys() + .then(keys => + Promise.all( + keys.filter(k => k.startsWith(`${scope}:`)).map(k => del(k)) + ) + ); + }, }; storeCache.set(scope, store); diff --git a/packages/data-center/src/index.ts b/packages/data-center/src/index.ts index ddabd863de..1f5590097c 100644 --- a/packages/data-center/src/index.ts +++ b/packages/data-center/src/index.ts @@ -1,6 +1,5 @@ export { signInWithGoogle, onAuthStateChanged } from './auth'; export * from './sdks'; -export * from './websocket'; export { getDataCenter } from './datacenter'; diff --git a/packages/data-center/src/websocket/index.ts b/packages/data-center/src/websocket/index.ts deleted file mode 100644 index d6651c22a2..0000000000 --- a/packages/data-center/src/websocket/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { WebsocketProvider } from './y-websocket'; diff --git a/packages/data-center/tests/datacenter.spec.ts b/packages/data-center/tests/datacenter.spec.ts index 7592c1790e..80c88402a6 100644 --- a/packages/data-center/tests/datacenter.spec.ts +++ b/packages/data-center/tests/datacenter.spec.ts @@ -7,43 +7,61 @@ import 'fake-indexeddb/auto'; test('init data center', async () => { const dataCenter = await getDataCenter(); expect(dataCenter).toBeTruthy(); + await dataCenter.clearWorkspaces(); - const workspace = await dataCenter.initWorkspace('test'); + const workspace = await dataCenter.getWorkspace('test1'); expect(workspace).toBeTruthy(); }); test('should init error with unknown provider', async () => { const dataCenter = await getDataCenter(); + await dataCenter.clearWorkspaces(); test.fail(); - await dataCenter.initWorkspace('test', 'not exist provider'); + await dataCenter.getWorkspace('test2', 'not exist provider'); }); test.skip('init affine provider', async () => { const dataCenter = await getDataCenter(); + await dataCenter.clearWorkspaces(); // TODO: set constant token for testing await dataCenter.setWorkspaceConfig('6', 'token', 'YOUR_TOKEN'); - const workspace = await dataCenter.initWorkspace('6', 'affine'); + const workspace = await dataCenter.getWorkspace('6', 'affine'); expect(workspace).toBeTruthy(); }); test('list workspaces', async () => { const dataCenter = await getDataCenter(); + await dataCenter.clearWorkspaces(); await Promise.all([ - dataCenter.initWorkspace('test1'), - dataCenter.initWorkspace('test2'), - dataCenter.initWorkspace('test3'), - dataCenter.initWorkspace('test4'), + dataCenter.getWorkspace('test3'), + dataCenter.getWorkspace('test4'), + dataCenter.getWorkspace('test5'), + dataCenter.getWorkspace('test6'), ]); - expect(await dataCenter.getWorkspaceList()).toStrictEqual([ - 'test1', - 'test2', + expect(await dataCenter.listWorkspace()).toStrictEqual([ 'test3', 'test4', + 'test5', + 'test6', ]); }); + +test('remove workspaces', async () => { + const dataCenter = await getDataCenter(); + await dataCenter.clearWorkspaces(); + + await Promise.all([ + dataCenter.getWorkspace('test7'), + dataCenter.getWorkspace('test8'), + ]); + + await dataCenter.removeWorkspace('test7'); + + expect(await dataCenter.listWorkspace()).toStrictEqual(['test8']); +}); diff --git a/packages/data-center/tests/utils.ts b/packages/data-center/tests/utils.ts index 7b06d4c6d6..8705aa5a1e 100644 --- a/packages/data-center/tests/utils.ts +++ b/packages/data-center/tests/utils.ts @@ -1,4 +1,5 @@ -export const getDataCenter = () => - import('../src/datacenter/index.js').then(async dataCenter => - dataCenter.getDataCenter() +export const getDataCenter = () => { + return import('../src/datacenter/index.js').then(async dataCenter => + dataCenter.getDataCenter(false) ); +};