import { WorkspaceUnitCollection } from './workspace-unit-collection.js'; import type { WorkspaceUnitCollectionChangeEvent } from './workspace-unit-collection'; import { StoreOptions, Workspace as BlocksuiteWorkspace, } from '@blocksuite/store'; import type { BaseProvider, CreateWorkspaceInfoParams, UpdateWorkspaceMetaParams, } from './provider/base'; import { LocalProvider } from './provider/local/local'; import { AffineProvider } from './provider'; import type { Message } from './types'; import assert from 'assert'; import { getLogger } from './logger'; import { createBlocksuiteWorkspace } from './utils/index.js'; import { MessageCenter } from './message'; import { WorkspaceUnit } from './workspace-unit'; /** * @class DataCenter * @classdesc Data center is made for managing different providers for business */ export class DataCenter { private readonly _workspaceUnitCollection = new WorkspaceUnitCollection(); private readonly _logger = getLogger('dc'); private _workspaceInstances: Map = new Map(); private _messageCenter = MessageCenter.getInstance(); /** * A mainProvider must exist as the only data trustworthy source. */ private _mainProvider?: BaseProvider; providerMap: Map = new Map(); constructor(debug: boolean) { this._logger.enabled = debug; } static async init(debug: boolean): Promise { const dc = new DataCenter(debug); const getInitParams = () => { return { logger: dc._logger, workspaces: dc._workspaceUnitCollection.createScope(), messageCenter: dc._messageCenter, }; }; // TODO: switch different provider await dc.registerProvider(new LocalProvider(getInitParams())); await dc.registerProvider(new AffineProvider(getInitParams())); for (const provider of dc.providerMap.values()) { await provider.loadWorkspaces(); } return dc; } /** * Register provider. * We will automatically set the first provider to default provider. */ async registerProvider(provider: BaseProvider) { if (!this._mainProvider) { this._mainProvider = provider; } await provider.init(); this.providerMap.set(provider.id, provider); } setMainProvider(providerId: string) { this._mainProvider = this.providerMap.get(providerId); } get providers() { return Array.from(this.providerMap.values()); } public get workspaces() { return this._workspaceUnitCollection.workspaces; } public async refreshWorkspaces() { return Promise.allSettled( Object.values(this.providerMap).map(provider => provider.loadWorkspaces()) ); } /** * create new workspace , new workspace is a local workspace * @param {string} name workspace name * @returns {Promise} */ public async createWorkspace(params: CreateWorkspaceInfoParams) { assert( this._mainProvider, 'There is no provider. You should add provider first.' ); const workspaceUnit = await this._mainProvider.createWorkspace(params); return workspaceUnit; } /** * delete workspace by id * @param {string} workspaceId workspace id */ public async deleteWorkspace(workspaceId: string) { const workspaceInfo = this._workspaceUnitCollection.find(workspaceId); assert(workspaceInfo, 'Workspace not found'); const provider = this.providerMap.get(workspaceInfo.provider); assert(provider, `Workspace exists, but we couldn't find its provider.`); await provider.deleteWorkspace(workspaceId); } /** * get a new workspace only has room id * @param {string} workspaceId workspace id */ private _getBlocksuiteWorkspace(workspaceId: string, params: StoreOptions) { // const workspaceInfo = this._workspaceUnitCollection.find(workspaceId); // assert(workspaceInfo, 'Workspace not found'); return ( // this._workspaceInstances.get(workspaceId) || createBlocksuiteWorkspace(workspaceId, params) ); } /** * login to all providers, it will default run all auth , * maybe need a params to control which provider to auth */ public async login(providerId = 'affine') { const provider = this.providerMap.get(providerId); assert(provider, `provide '${providerId}' is not registered`); await provider.auth(); provider.loadWorkspaces(); } /** * logout from all providers */ public async logout(providerId = 'affine') { const provider = this.providerMap.get(providerId); assert(provider, `provide '${providerId}' is not registered`); await provider.logout(); } /** * load workspace instance by id * @param {string} workspaceId workspace id * @returns {Promise} */ public async loadWorkspace( workspaceId: string ): Promise { const workspaceUnit = this._workspaceUnitCollection.find(workspaceId); assert(workspaceUnit, 'Workspace not found'); const currentProvider = this.providerMap.get(workspaceUnit.provider); if (currentProvider) { currentProvider.closeWorkspace(workspaceId); } const provider = this.providerMap.get(workspaceUnit.provider); assert(provider, `provide '${workspaceUnit.provider}' is not registered`); this._logger(`Loading ${workspaceUnit.provider} workspace: `, workspaceId); const params: StoreOptions = {}; if (provider.id === 'affine') { params.blobOptionsGetter = (k: string) => ({ api: '/api/workspace', token: provider.getToken() }[k]); } else { params.blobOptionsGetter = (k: string) => undefined; } const workspace = this._getBlocksuiteWorkspace(workspaceId, params); this._workspaceInstances.set(workspaceId, workspace); await provider.warpWorkspace(workspace); this._workspaceUnitCollection.workspaces.forEach(workspaceUnit => { const provider = this.providerMap.get(workspaceUnit.provider); assert(provider); provider.closeWorkspace(workspaceUnit.id); }); workspaceUnit.setBlocksuiteWorkspace(workspace); return workspaceUnit; } public async loadPublicWorkspace(workspaceId: string) { // FIXME: hard code for public workspace const provider = this.providerMap.get('affine'); assert(provider); const blocksuiteWorkspace = this._getBlocksuiteWorkspace(workspaceId, {}); await provider.loadPublicWorkspace(blocksuiteWorkspace); const workspaceUnitForPublic = new WorkspaceUnit({ id: workspaceId, name: blocksuiteWorkspace.meta.name, avatar: blocksuiteWorkspace.meta.avatar, owner: undefined, published: true, provider: 'affine', memberCount: 1, syncMode: 'core', }); workspaceUnitForPublic.setBlocksuiteWorkspace(blocksuiteWorkspace); return workspaceUnitForPublic; } /** * get user info by provider id * @param {string} providerId the provider name of workspace * @returns {Promise} */ public async getUserInfo(providerId = 'affine') { // XXX: maybe return all user info const provider = this.providerMap.get(providerId); assert(provider, `provide '${providerId}' is not registered`); return provider.getUserInfo(); } /** * listen workspaces list change * @param {Function} callback callback function */ public async onWorkspacesChange( callback: (workspaces: WorkspaceUnitCollectionChangeEvent) => void, { immediate = true }: { immediate?: boolean } = {} ) { if (immediate) { callback({ added: this._workspaceUnitCollection.workspaces, }); } this._workspaceUnitCollection.on('change', callback); } /** * change workspaces meta * @param {WorkspaceMeta} workspaceMeta workspace meta * @param {WorkspaceUnit} workspace workspace instance */ public async updateWorkspaceMeta( { name, avatar }: UpdateWorkspaceMetaParams, workspaceUnit: WorkspaceUnit ) { assert(workspaceUnit?.id, 'No workspace to set meta'); const workspace = workspaceUnit.blocksuiteWorkspace; assert(workspace); const update: Partial = {}; if (name) { workspace.meta.setName(name); update.name = name; } if (avatar) { workspace.meta.setAvatar(avatar); update.avatar = avatar; } // may run for change workspace meta const workspaceInfo = this._workspaceUnitCollection.find(workspaceUnit.id); assert(workspaceInfo, 'Workspace not found'); const provider = this.providerMap.get(workspaceInfo.provider); provider?.updateWorkspaceMeta(workspaceUnit.id, update); } /** * * leave workspace by id * @param id workspace id */ public async leaveWorkspace(workspaceId: string) { const workspaceInfo = this._workspaceUnitCollection.find(workspaceId); assert(workspaceInfo, 'Workspace not found'); const provider = this.providerMap.get(workspaceInfo.provider); if (provider) { await provider.closeWorkspace(workspaceId); await provider.leaveWorkspace(workspaceId); } } public async setWorkspacePublish(workspaceId: string, isPublish: boolean) { const workspaceInfo = this._workspaceUnitCollection.find(workspaceId); assert(workspaceInfo, 'Workspace not found'); const provider = this.providerMap.get(workspaceInfo.provider); if (provider) { await provider.publish(workspaceId, isPublish); } } /** * 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(workspaceId, email); } } /** * remove the new member to the workspace * @param {number} permissionId permission id */ public async removeMember(workspaceId: string, permissionId: number) { const workspaceInfo = this._workspaceUnitCollection.find(workspaceId); assert(workspaceInfo, 'Workspace not found'); const provider = this.providerMap.get(workspaceInfo.provider); if (provider) { await provider.removeMember(permissionId); } } /** * get user info by email * @param workspaceId * @param email * @param provider * @returns {Promise} User info */ public async getUserByEmail( workspaceId: string, email: string, provider = 'affine' ) { const providerInstance = this.providerMap.get(provider); if (providerInstance) { return await providerInstance.getUserByEmail(workspaceId, email); } } public async enableProvider( workspaceUnit: WorkspaceUnit, providerId = 'affine' ) { if (workspaceUnit.provider === providerId) { this._logger('Workspace provider is same'); return; } const provider = this.providerMap.get(providerId); assert(provider); const newWorkspaceUnit = await provider.extendWorkspace(workspaceUnit); // Currently we only allow enable one provider, so after enable new provider, // delete the old workspace from its provider. const oldProvider = this.providerMap.get(workspaceUnit.provider); assert(oldProvider); await oldProvider.deleteWorkspace(workspaceUnit.id); return newWorkspaceUnit; } /** * Enable workspace cloud * @param {string} id ID of workspace. */ public async enableWorkspaceCloud(workspaceUnit: WorkspaceUnit) { return this.enableProvider(workspaceUnit); } /** * @deprecated * clear all workspaces and data */ public async clear() { for (const provider of this.providerMap.values()) { await provider.clear(); } } /** * Select a file to import the workspace * @param {File} file file of workspace. */ public async importWorkspace(file: File) { file; return; } /** * Generate a file ,and export it to local file system * @param {string} id ID of workspace. */ public async exportWorkspace(id: string) { id; return; } /** * get blob url by workspaces id * @param id * @returns {Promise} blob url */ async getBlob( workspaceUnit: WorkspaceUnit, id: string ): Promise { const blob = await workspaceUnit.blocksuiteWorkspace?.blobs; return (await blob?.get(id)) || ''; } /** * up load blob and get a blob url * @param id * @returns {Promise} blob url */ async setBlob(workspace: WorkspaceUnit, blob: Blob): Promise { const blobStorage = await workspace.blocksuiteWorkspace?.blobs; return (await blobStorage?.set(blob)) || ''; } /** * get members of a workspace * @param workspaceId */ async getMembers(workspaceId: string) { const workspaceInfo = this._workspaceUnitCollection.find(workspaceId); assert(workspaceInfo, 'Workspace not found'); const provider = this.providerMap.get(workspaceInfo.provider); if (provider) { return await provider.getWorkspaceMembers(workspaceId); } return []; } /** * accept invitation * @param {string} inviteCode * @returns {Promise} permission */ async acceptInvitation(inviteCode: string, providerStr = 'affine') { const provider = this.providerMap.get(providerStr); if (provider) { return await provider.acceptInvitation(inviteCode); } return null; } onMessage(cb: (message: Message) => void) { return this._messageCenter.onMessage(cb); } }