mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
feat: add broad cast channel provider (#1237)
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import { BlockSuiteWorkspace, Provider } from '../shared';
|
||||
import { config } from '../shared/env';
|
||||
import { createIndexedDBProvider, createWebSocketProvider } from './providers';
|
||||
import {
|
||||
createBroadCastChannelProvider,
|
||||
createIndexedDBProvider,
|
||||
createWebSocketProvider,
|
||||
} from './providers';
|
||||
|
||||
export const createAffineProviders = (
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace
|
||||
@@ -8,6 +12,8 @@ export const createAffineProviders = (
|
||||
return (
|
||||
[
|
||||
createWebSocketProvider(blockSuiteWorkspace),
|
||||
config.enableBroadCastChannelProvider &&
|
||||
createBroadCastChannelProvider(blockSuiteWorkspace),
|
||||
config.enableIndexedDBProvider &&
|
||||
createIndexedDBProvider(blockSuiteWorkspace),
|
||||
] as any[]
|
||||
@@ -19,6 +25,8 @@ export const createLocalProviders = (
|
||||
): Provider[] => {
|
||||
return (
|
||||
[
|
||||
config.enableBroadCastChannelProvider &&
|
||||
createBroadCastChannelProvider(blockSuiteWorkspace),
|
||||
config.enableIndexedDBProvider &&
|
||||
createIndexedDBProvider(blockSuiteWorkspace),
|
||||
] as any[]
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
import {
|
||||
applyAwarenessUpdate,
|
||||
Awareness,
|
||||
encodeAwarenessUpdate,
|
||||
} from 'y-protocols/awareness';
|
||||
|
||||
import { BlockSuiteWorkspace, BroadCastChannelProvider } from '../../../shared';
|
||||
import {
|
||||
BroadcastChannelMessageEvent,
|
||||
getClients,
|
||||
TypedBroadcastChannel,
|
||||
} from './type';
|
||||
|
||||
export const createBroadCastChannelProvider = (
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace
|
||||
): BroadCastChannelProvider => {
|
||||
const Y = BlockSuiteWorkspace.Y;
|
||||
const doc = blockSuiteWorkspace.doc;
|
||||
const awareness = blockSuiteWorkspace.awarenessStore
|
||||
.awareness as unknown as Awareness;
|
||||
let broadcastChannel: TypedBroadcastChannel | null = null;
|
||||
const handleBroadcastChannelMessage = (
|
||||
event: BroadcastChannelMessageEvent
|
||||
) => {
|
||||
const [eventName] = event.data;
|
||||
switch (eventName) {
|
||||
case 'doc:diff': {
|
||||
const [, diff, clientId] = event.data;
|
||||
const updateV2 = Y.encodeStateAsUpdateV2(doc, diff);
|
||||
broadcastChannel!.postMessage(['doc:update', updateV2, clientId]);
|
||||
break;
|
||||
}
|
||||
case 'doc:update': {
|
||||
const [, updateV2, clientId] = event.data;
|
||||
Y.applyUpdateV2(doc, updateV2, clientId);
|
||||
break;
|
||||
}
|
||||
case 'awareness:query': {
|
||||
const [, clientId] = event.data;
|
||||
const clients = getClients(awareness);
|
||||
const update = encodeAwarenessUpdate(awareness, clients);
|
||||
broadcastChannel!.postMessage(['awareness:update', update, clientId]);
|
||||
break;
|
||||
}
|
||||
case 'awareness:update': {
|
||||
const [, update, clientId] = event.data;
|
||||
applyAwarenessUpdate(awareness, update, clientId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
flavour: 'broadcast-channel',
|
||||
connect: () => {
|
||||
assertExists(blockSuiteWorkspace.room);
|
||||
broadcastChannel = Object.assign(
|
||||
new BroadcastChannel(blockSuiteWorkspace.room),
|
||||
{
|
||||
onmessage: handleBroadcastChannelMessage,
|
||||
}
|
||||
);
|
||||
const docDiff = Y.encodeStateVector(doc);
|
||||
broadcastChannel.postMessage(['doc:diff', docDiff, awareness.clientID]);
|
||||
const docUpdateV2 = Y.encodeStateAsUpdateV2(doc);
|
||||
broadcastChannel.postMessage(['doc:update', docUpdateV2]);
|
||||
broadcastChannel.postMessage(['awareness:query', awareness.clientID]);
|
||||
const awarenessUpdate = encodeAwarenessUpdate(awareness, [
|
||||
awareness.clientID,
|
||||
]);
|
||||
broadcastChannel.postMessage(['awareness:update', awarenessUpdate]);
|
||||
const handleDocUpdate = (updateV1: Uint8Array, origin: any) => {
|
||||
if (origin !== awareness.clientID) {
|
||||
// not self update, ignore
|
||||
return;
|
||||
}
|
||||
const updateV2 = Y.convertUpdateFormatV1ToV2(updateV1);
|
||||
broadcastChannel?.postMessage(['doc:update', updateV2]);
|
||||
};
|
||||
doc.on('update', handleDocUpdate);
|
||||
},
|
||||
disconnect: () => {
|
||||
assertExists(broadcastChannel);
|
||||
broadcastChannel.close();
|
||||
},
|
||||
cleanup: () => {
|
||||
assertExists(broadcastChannel);
|
||||
broadcastChannel.close();
|
||||
},
|
||||
};
|
||||
};
|
||||
81
apps/web/src/blocksuite/providers/broad-cast-channel/type.ts
Normal file
81
apps/web/src/blocksuite/providers/broad-cast-channel/type.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Awareness as YAwareness } from 'y-protocols/awareness';
|
||||
|
||||
export type ClientId = YAwareness['clientID'];
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export type DefaultClientData = {};
|
||||
|
||||
type EventHandler = (...args: any[]) => void;
|
||||
export type DefaultEvents = {
|
||||
[eventName: string]: EventHandler;
|
||||
};
|
||||
|
||||
type EventNameWithScope<
|
||||
Scope extends string,
|
||||
Type extends string = string
|
||||
> = `${Scope}:${Type}`;
|
||||
|
||||
type DataScope = 'data';
|
||||
type RoomScope = 'room';
|
||||
|
||||
type YDocScope = 'doc';
|
||||
type AwarenessScope = 'awareness';
|
||||
type ObservableScope = YDocScope | AwarenessScope;
|
||||
type ObservableEventName = EventNameWithScope<ObservableScope>;
|
||||
|
||||
type ValidEventScope = DataScope | RoomScope | ObservableScope;
|
||||
|
||||
type ValidateEvents<
|
||||
Events extends DefaultEvents & {
|
||||
[EventName in keyof Events]: EventName extends EventNameWithScope<
|
||||
infer EventScope
|
||||
>
|
||||
? EventScope extends ValidEventScope
|
||||
? Events[EventName]
|
||||
: never
|
||||
: Events[EventName];
|
||||
}
|
||||
> = Events;
|
||||
|
||||
export type DefaultServerToClientEvents<
|
||||
ClientData extends DefaultClientData = DefaultClientData
|
||||
> = ValidateEvents<{
|
||||
['data:update']: (data: ClientData) => void;
|
||||
['doc:diff']: (diff: ArrayBuffer) => void;
|
||||
['doc:update']: (update: ArrayBuffer) => void;
|
||||
['awareness:update']: (update: ArrayBuffer) => void;
|
||||
}>;
|
||||
|
||||
export type ServerToClientEvents<
|
||||
ClientData extends DefaultClientData = DefaultClientData
|
||||
> = DefaultServerToClientEvents<ClientData>;
|
||||
|
||||
export type DefaultClientToServerEvents = ValidateEvents<{
|
||||
['room:close']: () => void;
|
||||
['doc:diff']: (diff: Uint8Array) => void;
|
||||
['doc:update']: (update: Uint8Array, callback?: () => void) => void;
|
||||
['awareness:update']: (update: Uint8Array) => void;
|
||||
}>;
|
||||
|
||||
export type ClientToServerEvents = DefaultClientToServerEvents;
|
||||
|
||||
type ClientToServerEventNames = keyof ClientToServerEvents;
|
||||
|
||||
export type BroadcastChannelMessageData<
|
||||
EventName extends ClientToServerEventNames = ClientToServerEventNames
|
||||
> =
|
||||
| (EventName extends ObservableEventName
|
||||
? [eventName: EventName, payload: Uint8Array, clientId?: ClientId]
|
||||
: never)
|
||||
| [eventName: `${AwarenessScope}:query`, clientId: ClientId];
|
||||
|
||||
export type BroadcastChannelMessageEvent =
|
||||
MessageEvent<BroadcastChannelMessageData>;
|
||||
|
||||
export interface TypedBroadcastChannel extends BroadcastChannel {
|
||||
onmessage: ((event: BroadcastChannelMessageEvent) => void) | null;
|
||||
postMessage: (message: BroadcastChannelMessageData) => void;
|
||||
}
|
||||
|
||||
export const getClients = (awareness: YAwareness): ClientId[] => [
|
||||
...awareness.getStates().keys(),
|
||||
];
|
||||
@@ -8,8 +8,9 @@ import {
|
||||
LocalIndexedDBProvider,
|
||||
} from '../../shared';
|
||||
import { apis } from '../../shared/apis';
|
||||
import { createBroadCastChannelProvider } from './broad-cast-channel';
|
||||
|
||||
export const createWebSocketProvider = (
|
||||
const createWebSocketProvider = (
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace
|
||||
): AffineWebSocketProvider => {
|
||||
let webSocketProvider: WebsocketProvider | null = null;
|
||||
@@ -31,6 +32,8 @@ export const createWebSocketProvider = (
|
||||
params: { token: apis.auth.refresh },
|
||||
// @ts-expect-error ignore the type
|
||||
awareness: blockSuiteWorkspace.awarenessStore.awareness,
|
||||
// we maintain broadcast channel by ourselves
|
||||
disableBc: true,
|
||||
}
|
||||
);
|
||||
console.log('connect', webSocketProvider.roomname);
|
||||
@@ -44,7 +47,7 @@ export const createWebSocketProvider = (
|
||||
};
|
||||
};
|
||||
|
||||
export const createIndexedDBProvider = (
|
||||
const createIndexedDBProvider = (
|
||||
blockSuiteWorkspace: BlockSuiteWorkspace
|
||||
): LocalIndexedDBProvider => {
|
||||
let indexdbProvider: IndexeddbPersistence | null = null;
|
||||
@@ -69,3 +72,9 @@ export const createIndexedDBProvider = (
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
createBroadCastChannelProvider,
|
||||
createIndexedDBProvider,
|
||||
createWebSocketProvider,
|
||||
};
|
||||
|
||||
@@ -91,6 +91,10 @@ export type BaseProvider = {
|
||||
cleanup: () => void;
|
||||
};
|
||||
|
||||
export interface BroadCastChannelProvider extends BaseProvider {
|
||||
flavour: 'broadcast-channel';
|
||||
}
|
||||
|
||||
export interface LocalIndexedDBProvider extends BaseProvider {
|
||||
flavour: 'local-indexeddb';
|
||||
}
|
||||
@@ -99,7 +103,10 @@ export interface AffineWebSocketProvider extends BaseProvider {
|
||||
flavour: 'affine-websocket';
|
||||
}
|
||||
|
||||
export type Provider = LocalIndexedDBProvider | AffineWebSocketProvider;
|
||||
export type Provider =
|
||||
| LocalIndexedDBProvider
|
||||
| AffineWebSocketProvider
|
||||
| BroadCastChannelProvider;
|
||||
|
||||
export type AffineRemoteWorkspace =
|
||||
| AffineRemoteSyncedWorkspace
|
||||
|
||||
@@ -8,6 +8,7 @@ export const publicRuntimeConfigSchema = z.object({
|
||||
serverAPI: z.string(),
|
||||
editorVersion: z.string(),
|
||||
enableIndexedDBProvider: z.boolean(),
|
||||
enableBroadCastChannelProvider: z.boolean(),
|
||||
prefetchWorkspace: z.boolean(),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user