Simplify tlsync types (#3139)

Replace enums with (const) object types. Was supposed to include
https://github.com/tldraw/tldraw/pull/3144, but had to bail out

### Change Type

<!--  Please select a 'Scope' label ️ -->

- [ ] `sdk` — Changes the tldraw SDK
- [ ] `dotcom` — Changes the tldraw.com web app
- [ ] `docs` — Changes to the documentation, examples, or templates.
- [ ] `vs code` — Changes to the vscode plugin
- [x] `internal` — Does not affect user-facing stuff

<!--  Please select a 'Type' label ️ -->

- [ ] `bugfix` — Bug fix
- [ ] `feature` — New feature
- [ ] `improvement` — Improving existing features
- [ ] `chore` — Updating dependencies, other boring stuff
- [ ] `galaxy brain` — Architectural changes
- [ ] `tests` — Changes to any test code
- [ ] `tools` — Changes to infrastructure, CI, internal scripts,
debugging tools, etc.
- [x] `dunno` — I don't know
This commit is contained in:
Dan Groshev 2024-03-13 17:18:25 +00:00 committed by GitHub
parent 3767a68f0f
commit a933aaf619
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 59 additions and 48 deletions

View file

@ -2,11 +2,13 @@ import { SerializedSchema, UnknownRecord } from '@tldraw/store'
import { TLRoomSocket } from './TLSyncRoom' import { TLRoomSocket } from './TLSyncRoom'
import { TLSocketServerSentDataEvent } from './protocol' import { TLSocketServerSentDataEvent } from './protocol'
export enum RoomSessionState { export const RoomSessionState = {
AWAITING_CONNECT_MESSAGE = 'awaiting-connect-message', AwaitingConnectMessage: 'awaiting-connect-message',
AWAITING_REMOVAL = 'awaiting-removal', AwaitingRemoval: 'awaiting-removal',
CONNECTED = 'connected', Connected: 'connected',
} } as const
export type RoomSessionState = (typeof RoomSessionState)[keyof typeof RoomSessionState]
export const SESSION_START_WAIT_TIME = 10000 export const SESSION_START_WAIT_TIME = 10000
export const SESSION_REMOVAL_WAIT_TIME = 10000 export const SESSION_REMOVAL_WAIT_TIME = 10000
@ -14,21 +16,21 @@ export const SESSION_IDLE_TIMEOUT = 20000
export type RoomSession<R extends UnknownRecord> = export type RoomSession<R extends UnknownRecord> =
| { | {
state: RoomSessionState.AWAITING_CONNECT_MESSAGE state: typeof RoomSessionState.AwaitingConnectMessage
sessionKey: string sessionKey: string
presenceId: string presenceId: string
socket: TLRoomSocket<R> socket: TLRoomSocket<R>
sessionStartTime: number sessionStartTime: number
} }
| { | {
state: RoomSessionState.AWAITING_REMOVAL state: typeof RoomSessionState.AwaitingRemoval
sessionKey: string sessionKey: string
presenceId: string presenceId: string
socket: TLRoomSocket<R> socket: TLRoomSocket<R>
cancellationTime: number cancellationTime: number
} }
| { | {
state: RoomSessionState.CONNECTED state: typeof RoomSessionState.Connected
sessionKey: string sessionKey: string
presenceId: string presenceId: string
socket: TLRoomSocket<R> socket: TLRoomSocket<R>

View file

@ -140,14 +140,14 @@ export class TLSyncRoom<R extends UnknownRecord> {
pruneSessions = () => { pruneSessions = () => {
for (const client of this.sessions.values()) { for (const client of this.sessions.values()) {
switch (client.state) { switch (client.state) {
case RoomSessionState.CONNECTED: { case RoomSessionState.Connected: {
const hasTimedOut = timeSince(client.lastInteractionTime) > SESSION_IDLE_TIMEOUT const hasTimedOut = timeSince(client.lastInteractionTime) > SESSION_IDLE_TIMEOUT
if (hasTimedOut || !client.socket.isOpen) { if (hasTimedOut || !client.socket.isOpen) {
this.cancelSession(client.sessionKey) this.cancelSession(client.sessionKey)
} }
break break
} }
case RoomSessionState.AWAITING_CONNECT_MESSAGE: { case RoomSessionState.AwaitingConnectMessage: {
const hasTimedOut = timeSince(client.sessionStartTime) > SESSION_START_WAIT_TIME const hasTimedOut = timeSince(client.sessionStartTime) > SESSION_START_WAIT_TIME
if (hasTimedOut || !client.socket.isOpen) { if (hasTimedOut || !client.socket.isOpen) {
// remove immediately // remove immediately
@ -155,7 +155,7 @@ export class TLSyncRoom<R extends UnknownRecord> {
} }
break break
} }
case RoomSessionState.AWAITING_REMOVAL: { case RoomSessionState.AwaitingRemoval: {
const hasTimedOut = timeSince(client.cancellationTime) > SESSION_REMOVAL_WAIT_TIME const hasTimedOut = timeSince(client.cancellationTime) > SESSION_REMOVAL_WAIT_TIME
if (hasTimedOut) { if (hasTimedOut) {
this.removeSession(client.sessionKey) this.removeSession(client.sessionKey)
@ -397,7 +397,7 @@ export class TLSyncRoom<R extends UnknownRecord> {
console.warn('Tried to send message to unknown session', message.type) console.warn('Tried to send message to unknown session', message.type)
return return
} }
if (session.state !== RoomSessionState.CONNECTED) { if (session.state !== RoomSessionState.Connected) {
console.warn('Tried to send message to disconnected client', message.type) console.warn('Tried to send message to disconnected client', message.type)
return return
} }
@ -433,7 +433,7 @@ export class TLSyncRoom<R extends UnknownRecord> {
_flushDataMessages(sessionKey: string) { _flushDataMessages(sessionKey: string) {
const session = this.sessions.get(sessionKey) const session = this.sessions.get(sessionKey)
if (!session || session.state !== RoomSessionState.CONNECTED) { if (!session || session.state !== RoomSessionState.Connected) {
return return
} }
@ -489,13 +489,13 @@ export class TLSyncRoom<R extends UnknownRecord> {
return return
} }
if (session.state === RoomSessionState.AWAITING_REMOVAL) { if (session.state === RoomSessionState.AwaitingRemoval) {
console.warn('Tried to cancel session that is already awaiting removal') console.warn('Tried to cancel session that is already awaiting removal')
return return
} }
this.sessions.set(sessionKey, { this.sessions.set(sessionKey, {
state: RoomSessionState.AWAITING_REMOVAL, state: RoomSessionState.AwaitingRemoval,
sessionKey, sessionKey,
presenceId: session.presenceId, presenceId: session.presenceId,
socket: session.socket, socket: session.socket,
@ -517,7 +517,7 @@ export class TLSyncRoom<R extends UnknownRecord> {
sourceSessionKey: string sourceSessionKey: string
}) { }) {
this.sessions.forEach((session) => { this.sessions.forEach((session) => {
if (session.state !== RoomSessionState.CONNECTED) return if (session.state !== RoomSessionState.Connected) return
if (sourceSessionKey === session.sessionKey) return if (sourceSessionKey === session.sessionKey) return
if (!session.socket.isOpen) { if (!session.socket.isOpen) {
this.cancelSession(session.sessionKey) this.cancelSession(session.sessionKey)
@ -556,7 +556,7 @@ export class TLSyncRoom<R extends UnknownRecord> {
handleNewSession = (sessionKey: string, socket: TLRoomSocket<R>) => { handleNewSession = (sessionKey: string, socket: TLRoomSocket<R>) => {
const existing = this.sessions.get(sessionKey) const existing = this.sessions.get(sessionKey)
this.sessions.set(sessionKey, { this.sessions.set(sessionKey, {
state: RoomSessionState.AWAITING_CONNECT_MESSAGE, state: RoomSessionState.AwaitingConnectMessage,
sessionKey, sessionKey,
socket, socket,
presenceId: existing?.presenceId ?? this.presenceType.createId(), presenceId: existing?.presenceId ?? this.presenceType.createId(),
@ -628,7 +628,7 @@ export class TLSyncRoom<R extends UnknownRecord> {
return this.handlePushRequest(session, message) return this.handlePushRequest(session, message)
} }
case 'ping': { case 'ping': {
if (session.state === RoomSessionState.CONNECTED) { if (session.state === RoomSessionState.Connected) {
session.lastInteractionTime = Date.now() session.lastInteractionTime = Date.now()
} }
return this.sendMessage(session.sessionKey, { type: 'pong' }) return this.sendMessage(session.sessionKey, { type: 'pong' })
@ -685,7 +685,7 @@ export class TLSyncRoom<R extends UnknownRecord> {
const connect = (msg: TLSocketServerSentEvent<R>) => { const connect = (msg: TLSocketServerSentEvent<R>) => {
this.sessions.set(session.sessionKey, { this.sessions.set(session.sessionKey, {
state: RoomSessionState.CONNECTED, state: RoomSessionState.Connected,
sessionKey: session.sessionKey, sessionKey: session.sessionKey,
presenceId: session.presenceId, presenceId: session.presenceId,
socket: session.socket, socket: session.socket,
@ -786,7 +786,7 @@ export class TLSyncRoom<R extends UnknownRecord> {
message: Extract<TLSocketClientSentEvent<R>, { type: 'push' }> message: Extract<TLSocketClientSentEvent<R>, { type: 'push' }>
) { ) {
// We must be connected to handle push requests // We must be connected to handle push requests
if (session.state !== RoomSessionState.CONNECTED) { if (session.state !== RoomSessionState.Connected) {
return return
} }

View file

@ -3,17 +3,20 @@ import { objectMapEntries, objectMapValues } from '@tldraw/utils'
import isEqual from 'lodash.isequal' import isEqual from 'lodash.isequal'
/** @public */ /** @public */
export enum RecordOpType { export const RecordOpType = {
Put = 'put', Put: 'put',
Patch = 'patch', Patch: 'patch',
Remove = 'remove', Remove: 'remove',
} } as const
/** @public */
export type RecordOpType = (typeof RecordOpType)[keyof typeof RecordOpType]
/** @public */ /** @public */
export type RecordOp<R extends UnknownRecord> = export type RecordOp<R extends UnknownRecord> =
| [RecordOpType.Put, R] | [typeof RecordOpType.Put, R]
| [RecordOpType.Patch, ObjectDiff] | [typeof RecordOpType.Patch, ObjectDiff]
| [RecordOpType.Remove] | [typeof RecordOpType.Remove]
/** /**
* A one-way (non-reversible) diff designed for small json footprint. These are mainly intended to * A one-way (non-reversible) diff designed for small json footprint. These are mainly intended to
@ -60,20 +63,22 @@ export const getNetworkDiff = <R extends UnknownRecord>(
} }
/** @public */ /** @public */
export enum ValueOpType { export const ValueOpType = {
Put = 'put', Put: 'put',
Delete = 'delete', Delete: 'delete',
Append = 'append', Append: 'append',
Patch = 'patch', Patch: 'patch',
} } as const
export type ValueOpType = (typeof ValueOpType)[keyof typeof ValueOpType]
/** @public */ /** @public */
export type PutOp = [type: ValueOpType.Put, value: unknown] export type PutOp = [type: typeof ValueOpType.Put, value: unknown]
/** @public */ /** @public */
export type AppendOp = [type: ValueOpType.Append, values: unknown[], offset: number] export type AppendOp = [type: typeof ValueOpType.Append, values: unknown[], offset: number]
/** @public */ /** @public */
export type PatchOp = [type: ValueOpType.Patch, diff: ObjectDiff] export type PatchOp = [type: typeof ValueOpType.Patch, diff: ObjectDiff]
/** @public */ /** @public */
export type DeleteOp = [type: ValueOpType.Delete] export type DeleteOp = [type: typeof ValueOpType.Delete]
/** @public */ /** @public */
export type ValueOp = PutOp | AppendOp | PatchOp | DeleteOp export type ValueOp = PutOp | AppendOp | PatchOp | DeleteOp

View file

@ -5,12 +5,16 @@ import { NetworkDiff, ObjectDiff, RecordOpType } from './diff'
export const TLSYNC_PROTOCOL_VERSION = 5 export const TLSYNC_PROTOCOL_VERSION = 5
/** @public */ /** @public */
export enum TLIncompatibilityReason { export const TLIncompatibilityReason = {
ClientTooOld = 'clientTooOld', ClientTooOld: 'clientTooOld',
ServerTooOld = 'serverTooOld', ServerTooOld: 'serverTooOld',
InvalidRecord = 'invalidRecord', InvalidRecord: 'invalidRecord',
InvalidOperation = 'invalidOperation', InvalidOperation: 'invalidOperation',
} } as const
/** @public */
export type TLIncompatibilityReason =
(typeof TLIncompatibilityReason)[keyof typeof TLIncompatibilityReason]
/** @public */ /** @public */
export type TLSocketServerSentEvent<R extends UnknownRecord> = export type TLSocketServerSentEvent<R extends UnknownRecord> =
@ -55,7 +59,7 @@ export type TLPushRequest<R extends UnknownRecord> =
| { | {
type: 'push' type: 'push'
clientClock: number clientClock: number
presence: [RecordOpType.Patch, ObjectDiff] | [RecordOpType.Put, R] presence: [typeof RecordOpType.Patch, ObjectDiff] | [typeof RecordOpType.Put, R]
} }
| { | {
type: 'push' type: 'push'

View file

@ -109,7 +109,7 @@ describe('TLServer', () => {
expect(server.roomState?.persistenceKey).toBe('test-persistence-key') expect(server.roomState?.persistenceKey).toBe('test-persistence-key')
expect(server.roomState?.room.sessions.size).toBe(1) expect(server.roomState?.room.sessions.size).toBe(1)
expect(server.roomState?.room.sessions.get('test-session-key')?.state).toBe( expect(server.roomState?.room.sessions.get('test-session-key')?.state).toBe(
RoomSessionState.AWAITING_CONNECT_MESSAGE RoomSessionState.AwaitingConnectMessage
) )
}) })
@ -135,7 +135,7 @@ describe('TLServer', () => {
sockets.client.on('message', onClientMessage) sockets.client.on('message', onClientMessage)
expect(server.roomState?.room.sessions.get('test-session-key')?.state).toBe( expect(server.roomState?.room.sessions.get('test-session-key')?.state).toBe(
RoomSessionState.AWAITING_CONNECT_MESSAGE RoomSessionState.AwaitingConnectMessage
) )
for (const chunk of chunks) { for (const chunk of chunks) {
@ -145,7 +145,7 @@ describe('TLServer', () => {
await receivedPromise await receivedPromise
expect(server.roomState?.room.sessions.get('test-session-key')?.state).toBe( expect(server.roomState?.room.sessions.get('test-session-key')?.state).toBe(
RoomSessionState.CONNECTED RoomSessionState.Connected
) )
expect(onClientMessage).toHaveBeenCalledTimes(1) expect(onClientMessage).toHaveBeenCalledTimes(1)