feat: add channel

This commit is contained in:
MingLiang Wang
2023-01-11 17:21:41 +08:00
parent 6459faeeb9
commit 62826f7ab7
5 changed files with 138 additions and 9 deletions

View File

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

View File

@@ -7,33 +7,47 @@ import type {
import type { User } from '../../types'; import type { User } from '../../types';
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store'; import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
import { BlockSchema } from '@blocksuite/blocks/models'; import { BlockSchema } from '@blocksuite/blocks/models';
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { storage } from './storage.js'; import { storage } from './storage.js';
import assert from 'assert'; import assert from 'assert';
import { WebsocketProvider } from './sync.js'; import { WebsocketProvider } from './sync.js';
// import { IndexedDBProvider } from '../local/indexeddb'; // 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 type { Apis, WorkspaceDetail, Callback } from './apis';
import { setDefaultAvatar } from '../utils.js'; import { setDefaultAvatar } from '../utils.js';
import { MessageCode } from '../../message'; import { MessageCode } from '../../message';
import { token } from './apis/token.js'; import { token } from './apis/token.js';
import { WebsocketClient } from './channel';
export interface AffineProviderConstructorParams export interface AffineProviderConstructorParams
extends ProviderConstructorParams { extends ProviderConstructorParams {
apis?: Apis; apis?: Apis;
} }
const {
Y: { applyUpdate, encodeStateAsUpdate },
} = BlocksuiteWorkspace;
export class AffineProvider extends BaseProvider { export class AffineProvider extends BaseProvider {
public id = 'affine'; public id = 'affine';
private _workspacesCache: Map<string, BlocksuiteWorkspace> = new Map(); 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;
private _channel: WebsocketClient;
// private _idbMap: Map<string, IndexedDBProvider> = new Map(); // private _idbMap: Map<string, IndexedDBProvider> = new Map();
constructor({ apis, ...params }: AffineProviderConstructorParams) { constructor({ apis, ...params }: AffineProviderConstructorParams) {
super(params); super(params);
this._apis = apis || getApis(); 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() { 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) { private _getWebsocketProvider(workspace: BlocksuiteWorkspace) {
const { doc, room } = workspace; const { doc, room } = workspace;
assert(room); assert(room);
@@ -206,6 +229,9 @@ export class AffineProvider extends BaseProvider {
} }
} }
const user = await this._apis.signInWithGoogle?.(); const user = await this._apis.signInWithGoogle?.();
if (!this._channel.connected) {
this._connectChannel();
}
if (!user) { if (!user) {
this._messageCenter.send(MessageCode.loginError); this._messageCenter.send(MessageCode.loginError);
} }
@@ -363,6 +389,8 @@ export class AffineProvider extends BaseProvider {
public override async logout(): Promise<void> { public override async logout(): Promise<void> {
token.clear(); token.clear();
this._channel.disconnect();
this._wsMap.forEach(ws => ws.disconnect());
storage.removeItem('token'); storage.removeItem('token');
} }

View File

@@ -0,0 +1,53 @@
import 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

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

52
pnpm-lock.yaml generated
View File

@@ -140,10 +140,9 @@ importers:
swr: ^2.0.0 swr: ^2.0.0
typescript: ^4.8.4 typescript: ^4.8.4
y-protocols: ^1.0.5 y-protocols: ^1.0.5
yjs: ^13.5.44
dependencies: dependencies:
'@blocksuite/blocks': 0.4.0-20230110112105-ef07332_yjs@13.5.44 '@blocksuite/blocks': 0.4.0-20230110112105-ef07332
'@blocksuite/store': 0.4.0-20230110112105-ef07332_yjs@13.5.44 '@blocksuite/store': 0.4.0-20230110112105-ef07332
debug: 4.3.4 debug: 4.3.4
encoding: 0.1.13 encoding: 0.1.13
firebase: 9.15.0_encoding@0.1.13 firebase: 9.15.0_encoding@0.1.13
@@ -153,7 +152,6 @@ importers:
lib0: 0.2.58 lib0: 0.2.58
swr: 2.0.0 swr: 2.0.0
y-protocols: 1.0.5 y-protocols: 1.0.5
yjs: 13.5.44
devDependencies: devDependencies:
'@playwright/test': 1.29.1 '@playwright/test': 1.29.1
'@types/debug': 4.1.7 '@types/debug': 4.1.7
@@ -1455,6 +1453,26 @@ packages:
to-fast-properties: 2.0.0 to-fast-properties: 2.0.0
dev: true dev: true
/@blocksuite/blocks/0.4.0-20230110112105-ef07332:
resolution: {integrity: sha512-dtwZRCWtirmheRQaITPOC/D9LZ3yFuYztqL/y2mz/BRSfHj8I71OVcX0HjFXx2TUdzhkea1GNYbp4226zZIiRA==}
dependencies:
'@blocksuite/phasor': 0.4.0-20230110112105-ef07332
'@blocksuite/store': 0.4.0-20230110112105-ef07332
'@tldraw/intersect': 1.8.0
autosize: 5.0.2
highlight.js: 11.7.0
hotkeys-js: 3.10.1
lit: 2.5.0
perfect-freehand: 1.2.0
quill: 1.3.7
quill-cursors: 4.0.0
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
- yjs
dev: false
/@blocksuite/blocks/0.4.0-20230110112105-ef07332_yjs@13.5.44: /@blocksuite/blocks/0.4.0-20230110112105-ef07332_yjs@13.5.44:
resolution: {integrity: sha512-dtwZRCWtirmheRQaITPOC/D9LZ3yFuYztqL/y2mz/BRSfHj8I71OVcX0HjFXx2TUdzhkea1GNYbp4226zZIiRA==} resolution: {integrity: sha512-dtwZRCWtirmheRQaITPOC/D9LZ3yFuYztqL/y2mz/BRSfHj8I71OVcX0HjFXx2TUdzhkea1GNYbp4226zZIiRA==}
dependencies: dependencies:
@@ -1500,6 +1518,12 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@blocksuite/phasor/0.4.0-20230110112105-ef07332:
resolution: {integrity: sha512-R5j/iK7WBFSk7vSk8HEAsxDwr+xDeY7JuzdGzzwnkcOaxuM6Eea6g0vMw7DPuWDAkvlcP7JsgXLnzWgFZh7qmA==}
peerDependencies:
yjs: ^13
dev: false
/@blocksuite/phasor/0.4.0-20230110112105-ef07332_yjs@13.5.44: /@blocksuite/phasor/0.4.0-20230110112105-ef07332_yjs@13.5.44:
resolution: {integrity: sha512-R5j/iK7WBFSk7vSk8HEAsxDwr+xDeY7JuzdGzzwnkcOaxuM6Eea6g0vMw7DPuWDAkvlcP7JsgXLnzWgFZh7qmA==} resolution: {integrity: sha512-R5j/iK7WBFSk7vSk8HEAsxDwr+xDeY7JuzdGzzwnkcOaxuM6Eea6g0vMw7DPuWDAkvlcP7JsgXLnzWgFZh7qmA==}
peerDependencies: peerDependencies:
@@ -1508,6 +1532,26 @@ packages:
yjs: 13.5.44 yjs: 13.5.44
dev: false dev: false
/@blocksuite/store/0.4.0-20230110112105-ef07332:
resolution: {integrity: sha512-NisHLf0uSyFu5DUZD13QSsp33C9vnd7Jf7jdLOAPztQ2U05NcGFopjM2InhwBmtmQSHrd/qi25PjgnAJ7/HSNQ==}
peerDependencies:
yjs: ^13
dependencies:
'@types/flexsearch': 0.7.3
'@types/quill': 1.3.10
buffer: 6.0.3
flexsearch: 0.7.21
idb-keyval: 6.2.0
ky: 0.33.1
lib0: 0.2.58
y-protocols: 1.0.5
y-webrtc: 10.2.3
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
dev: false
/@blocksuite/store/0.4.0-20230110112105-ef07332_yjs@13.5.44: /@blocksuite/store/0.4.0-20230110112105-ef07332_yjs@13.5.44:
resolution: {integrity: sha512-NisHLf0uSyFu5DUZD13QSsp33C9vnd7Jf7jdLOAPztQ2U05NcGFopjM2InhwBmtmQSHrd/qi25PjgnAJ7/HSNQ==} resolution: {integrity: sha512-NisHLf0uSyFu5DUZD13QSsp33C9vnd7Jf7jdLOAPztQ2U05NcGFopjM2InhwBmtmQSHrd/qi25PjgnAJ7/HSNQ==}
peerDependencies: peerDependencies: