refactor: reimplement realtime-communication

This commit refactors a lot of things that are not easy to separate.
It replaces the binary protocol of y-protocols with json.
It introduces event based message processing.
It implements our own code mirror plugins for synchronisation of content and remote cursors

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-03-22 20:21:40 +01:00
parent 67cf1432b2
commit 3a06f84af1
110 changed files with 3920 additions and 2201 deletions

View file

@ -8,7 +8,7 @@ import type { HistoryEntryWithOrigin } from '../api/history/types'
import type { DarkModeConfig } from './dark-mode/types'
import type { EditorConfig } from './editor/types'
import type { NoteDetails } from './note-details/types/note-details'
import type { RealtimeState } from './realtime/types'
import type { RealtimeStatus } from './realtime/types'
import type { RendererStatus } from './renderer-status/types'
import type { OptionalUserState } from './user/types'
@ -20,5 +20,5 @@ export interface ApplicationState {
darkMode: DarkModeConfig
noteDetails: NoteDetails
rendererStatus: RendererStatus
realtime: RealtimeState
realtimeStatus: RealtimeStatus
}

View file

@ -4,33 +4,37 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { store } from '..'
import type { AddOnlineUserAction, OnlineUser, RemoveOnlineUserAction } from './types'
import { RealtimeActionType } from './types'
import type { SetRealtimeSyncStatusAction, SetRealtimeUsersAction, SetRealtimeConnectionStatusAction } from './types'
import { RealtimeStatusActionType } from './types'
import type { RealtimeUser } from '@hedgedoc/commons'
/**
* Dispatches an event to add a user
*
* @param clientId The clientId of the user to add
* @param user The user to add.
*/
export const addOnlineUser = (clientId: number, user: OnlineUser): void => {
const action: AddOnlineUserAction = {
type: RealtimeActionType.ADD_ONLINE_USER,
clientId,
user
export const setRealtimeUsers = (users: RealtimeUser[]): void => {
const action: SetRealtimeUsersAction = {
type: RealtimeStatusActionType.SET_REALTIME_USERS,
users
}
store.dispatch(action)
}
/**
* Dispatches an event to remove a user from the online users list.
*
* @param clientId The yjs client id of the user to remove from the online users list.
*/
export const removeOnlineUser = (clientId: number): void => {
const action: RemoveOnlineUserAction = {
type: RealtimeActionType.REMOVE_ONLINE_USER,
clientId
}
store.dispatch(action)
export const setRealtimeConnectionState = (status: boolean): void => {
store.dispatch({
type: RealtimeStatusActionType.SET_REALTIME_CONNECTION_STATUS,
isConnected: status
} as SetRealtimeConnectionStatusAction)
}
export const setRealtimeSyncedState = (status: boolean): void => {
store.dispatch({
type: RealtimeStatusActionType.SET_REALTIME_SYNCED_STATUS,
isSynced: status
} as SetRealtimeSyncStatusAction)
}
export const resetRealtimeStatus = (): void => {
store.dispatch({
type: RealtimeStatusActionType.RESET_REALTIME_STATUS
})
}

View file

@ -3,32 +3,45 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { buildStateFromAddUser } from './reducers/build-state-from-add-user'
import { buildStateFromRemoveUser } from './reducers/build-state-from-remove-user'
import type { RealtimeActions, RealtimeState } from './types'
import { RealtimeActionType } from './types'
import type { RealtimeStatusActions, RealtimeStatus } from './types'
import { RealtimeStatusActionType } from './types'
import type { Reducer } from 'redux'
const initialState: RealtimeState = {
users: []
const initialState: RealtimeStatus = {
isSynced: false,
isConnected: false,
onlineUsers: []
}
/**
* Applies {@link RealtimeReducer realtime actions} to the global application state.
* Applies {@link RealtimeStatusReducer realtime actions} to the global application state.
*
* @param state the current state
* @param action the action that should get applied
* @return The new changed state
*/
export const RealtimeReducer: Reducer<RealtimeState, RealtimeActions> = (
export const RealtimeStatusReducer: Reducer<RealtimeStatus, RealtimeStatusActions> = (
state = initialState,
action: RealtimeActions
action: RealtimeStatusActions
) => {
switch (action.type) {
case RealtimeActionType.ADD_ONLINE_USER:
return buildStateFromAddUser(state, action.clientId, action.user)
case RealtimeActionType.REMOVE_ONLINE_USER:
return buildStateFromRemoveUser(state, action.clientId)
case RealtimeStatusActionType.SET_REALTIME_USERS:
return {
...state,
onlineUsers: action.users
}
case RealtimeStatusActionType.SET_REALTIME_CONNECTION_STATUS:
return {
...state,
isConnected: action.isConnected
}
case RealtimeStatusActionType.SET_REALTIME_SYNCED_STATUS:
return {
...state,
isSynced: action.isSynced
}
case RealtimeStatusActionType.RESET_REALTIME_STATUS:
return initialState
default:
return state
}

View file

@ -1,23 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { OnlineUser, RealtimeState } from '../types'
/**
* Builds a new {@link RealtimeState} with a new client id that is shown as online.
*
* @param oldState The old state that will be copied
* @param clientId The identifier of the new client
* @param user The information about the new user
* @return the generated state
*/
export const buildStateFromAddUser = (oldState: RealtimeState, clientId: number, user: OnlineUser): RealtimeState => {
return {
users: {
...oldState.users,
[clientId]: user
}
}
}

View file

@ -1,21 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { RealtimeState } from '../types'
/**
* Builds a new {@link RealtimeState} but removes the information about a client.
*
* @param oldState The old state that will be copied
* @param clientIdToRemove The identifier of the client that should be removed
* @return the generated state
*/
export const buildStateFromRemoveUser = (oldState: RealtimeState, clientIdToRemove: number): RealtimeState => {
const newUsers = { ...oldState.users }
delete newUsers[clientIdToRemove]
return {
users: newUsers
}
}

View file

@ -3,38 +3,43 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { RealtimeUser } from '@hedgedoc/commons'
import type { Action } from 'redux'
export enum RealtimeActionType {
ADD_ONLINE_USER = 'realtime/add-user',
REMOVE_ONLINE_USER = 'realtime/remove-user',
UPDATE_ONLINE_USER = 'realtime/update-user'
export enum RealtimeStatusActionType {
SET_REALTIME_USERS = 'realtime/set-users',
SET_REALTIME_CONNECTION_STATUS = 'realtime/set-connection-status',
SET_REALTIME_SYNCED_STATUS = 'realtime/set-synced-status',
RESET_REALTIME_STATUS = 'realtime/reset-realtime-status'
}
export interface RealtimeState {
users: Record<number, OnlineUser>
export interface SetRealtimeUsersAction extends Action<RealtimeStatusActionType> {
type: RealtimeStatusActionType.SET_REALTIME_USERS
users: RealtimeUser[]
}
export enum ActiveIndicatorStatus {
ACTIVE = 'active',
INACTIVE = 'inactive'
export interface SetRealtimeConnectionStatusAction extends Action<RealtimeStatusActionType> {
type: RealtimeStatusActionType.SET_REALTIME_CONNECTION_STATUS
isConnected: boolean
}
export interface OnlineUser {
username: string
color: string
active: ActiveIndicatorStatus
export interface SetRealtimeSyncStatusAction extends Action<RealtimeStatusActionType> {
type: RealtimeStatusActionType.SET_REALTIME_SYNCED_STATUS
isSynced: boolean
}
export interface AddOnlineUserAction extends Action<RealtimeActionType> {
type: RealtimeActionType.ADD_ONLINE_USER
clientId: number
user: OnlineUser
export interface ResetRealtimeStatusAction extends Action<RealtimeStatusActionType> {
type: RealtimeStatusActionType.RESET_REALTIME_STATUS
}
export interface RemoveOnlineUserAction extends Action<RealtimeActionType> {
type: RealtimeActionType.REMOVE_ONLINE_USER
clientId: number
export interface RealtimeStatus {
onlineUsers: RealtimeUser[]
isConnected: boolean
isSynced: boolean
}
export type RealtimeActions = AddOnlineUserAction | RemoveOnlineUserAction
export type RealtimeStatusActions =
| SetRealtimeUsersAction
| SetRealtimeConnectionStatusAction
| SetRealtimeSyncStatusAction
| ResetRealtimeStatusAction

View file

@ -9,7 +9,7 @@ import { DarkModeConfigReducer } from './dark-mode/reducers'
import { EditorConfigReducer } from './editor/reducers'
import { HistoryReducer } from './history/reducers'
import { NoteDetailsReducer } from './note-details/reducer'
import { RealtimeReducer } from './realtime/reducers'
import { RealtimeStatusReducer } from './realtime/reducers'
import { RendererStatusReducer } from './renderer-status/reducers'
import { UserReducer } from './user/reducers'
import type { Reducer } from 'redux'
@ -23,5 +23,5 @@ export const allReducers: Reducer<ApplicationState> = combineReducers<Applicatio
darkMode: DarkModeConfigReducer,
noteDetails: NoteDetailsReducer,
rendererStatus: RendererStatusReducer,
realtime: RealtimeReducer
realtimeStatus: RealtimeStatusReducer
})