feat: better client side error handling

This commit is contained in:
Eric Maciel 2021-01-12 09:25:52 -05:00
parent c72c42ec05
commit d4b089a480
5 changed files with 92 additions and 84 deletions

View File

@ -5,14 +5,7 @@ import { CollabAction } from '../model'
export * from './testUtils'
const toJS = (node: any) => {
try {
return JSON.parse(JSON.stringify(node))
} catch (e) {
console.error('Convert to js failed!!! Return null')
return null
}
}
const toJS = (node: any) => JSON.parse(JSON.stringify(node))
const cloneNode = (node: any) => toSync(toJS(node))

View File

@ -45,15 +45,38 @@ export const AutomergeConnector = {
let changed: any
operations.forEach(op => {
changed = Automerge.change<SyncDoc>(changed || doc, d =>
applyOperation(d.children, op)
)
})
for (let i = 0; i < operations.length; i++) {
const op = operations[i]
changed = Automerge.change(changed || doc, d => {
setCursor(e.clientId, e.selection, d, operations, cursorData || {})
})
try {
changed = Automerge.change<SyncDoc>(changed || doc, d =>
applyOperation(d.children, op)
)
} catch (err) {
e.handleError(err, {
type: 'applySlateOps - applyOperation',
automergeChanged: Automerge.save(changed || doc),
operation: op
})
// return early to avoid applying any further operations after we encounter an error
return
}
}
try {
changed = Automerge.change(changed || doc, d => {
setCursor(e.clientId, e.selection, d, operations, cursorData || {})
})
} catch (err) {
e.handleError(err, {
type: 'applySlateOps - setCursor',
clientId: e.clientId,
automergeDocument: Automerge.save(changed || doc),
operations,
cursorData
})
}
e.docSet.setDoc(docId, changed)
},
@ -63,22 +86,32 @@ export const AutomergeConnector = {
*/
receiveDocument: (e: AutomergeEditor, docId: string, data: string) => {
const currentDoc = e.docSet.getDoc(docId)
let currentDoc: Automerge.FreezeObject<SyncDoc> | null = null
let externalDoc: Automerge.FreezeObject<SyncDoc> | null = null
let mergedDoc: Automerge.FreezeObject<SyncDoc> | null = null
const externalDoc = Automerge.load<SyncDoc>(data)
try {
currentDoc = e.docSet.getDoc(docId)
externalDoc = Automerge.load<SyncDoc>(data)
mergedDoc = Automerge.merge<SyncDoc>(
externalDoc,
currentDoc || Automerge.init()
)
const mergedDoc = Automerge.merge<SyncDoc>(
externalDoc,
currentDoc || Automerge.init()
)
e.docSet.setDoc(docId, mergedDoc)
e.docSet.setDoc(docId, mergedDoc)
Editor.withoutNormalizing(e, () => {
e.children = toJS(mergedDoc).children
e.onChange()
})
Editor.withoutNormalizing(e, () => {
e.children = toJS(mergedDoc).children
e.onChange()
})
} catch (err) {
e.handleError(err, {
type: 'receiveDocument',
currentDoc: currentDoc && Automerge.save(currentDoc),
externalDoc: externalDoc && Automerge.save(externalDoc),
mergedDoc: mergedDoc && Automerge.save(mergedDoc)
})
}
},
/**
@ -93,13 +126,20 @@ export const AutomergeConnector = {
) => {
try {
const current = e.docSet.getDoc(docId)
const updated = e.connection.receiveMsg(data)
const operations = Automerge.diff(current, updated)
if (operations.length) {
const slateOps = toSlateOp(operations, current)
let slateOps: any[]
try {
slateOps = toSlateOp(operations, current)
} catch (err) {
e.handleError(err, {
type: 'applyOperation - toSlateOp',
operations,
current: Automerge.save(current)
})
}
e.isRemote = true
@ -117,13 +157,18 @@ export const AutomergeConnector = {
Promise.resolve().then(_ => (e.isRemote = false))
}
} catch (e) {
// unset remove flag
} catch (err) {
// unset remote flag
if (e.isRemote) {
e.isRemote = false
}
throw e
const current = e.docSet.getDoc(docId)
e.handleError(err, {
type: 'applyOperation',
data,
current: current ? Automerge.save(current) : null
})
}
},

View File

@ -2,18 +2,11 @@ import Automerge from 'automerge'
import { Editor } from 'slate'
import { CollabAction, CursorData, SyncDoc } from '@hiveteams/collab-bridge'
interface ErrorData {
docId: string
serializedData: string
opData?: string
slateOperations?: string
}
export interface AutomergeOptions {
docId: string
cursorData?: CursorData
preserveExternalHistory?: boolean
onError?: (msg: string | Error, data: ErrorData) => void
onError?: (msg: string | Error, data: any) => void
}
export interface AutomergeEditor extends Editor {
@ -32,11 +25,11 @@ export interface AutomergeEditor extends Editor {
receiveDocument: (data: string) => void
receiveOperation: (data: Automerge.Message) => void
gabageCursor: () => void
garbageCursor: () => void
onCursor: (data: any) => void
handleError: (err: Error | string, opData?: string) => void
handleError: (err: Error | string, data?: any) => void
}
export interface SocketIOPluginOptions {
@ -44,7 +37,7 @@ export interface SocketIOPluginOptions {
connectOpts: SocketIOClient.ConnectOpts
onConnect?: () => void
onDisconnect?: () => void
onError?: (msg: string | Error, data: ErrorData) => void
onError?: (msg: string | Error, data: any) => void
}
export interface WithSocketIOEditor {

View File

@ -31,16 +31,10 @@ const withAutomerge = <T extends Editor>(
* Helper function for handling errors
*/
editor.handleError = (err: Error | string, opData?: string) => {
const { docId, cursorData, onError } = options
if (onError && cursorData) {
const document = editor.docSet.getDoc(docId)
onError(err, {
docId: docId,
serializedData: document ? Automerge.save(document) : 'No document',
opData,
slateOperations: JSON.stringify(editor.operations)
})
editor.handleError = (err: Error | string, data: any = {}) => {
const { onError } = options
if (onError) {
onError(err, data)
}
}
@ -72,12 +66,8 @@ const withAutomerge = <T extends Editor>(
* Clear cursor data
*/
editor.gabageCursor = () => {
try {
AutomergeConnector.garbageCursor(editor, docId)
} catch (err) {
editor.handleError(err)
}
editor.garbageCursor = () => {
AutomergeConnector.garbageCursor(editor, docId)
}
/**
@ -87,12 +77,7 @@ const withAutomerge = <T extends Editor>(
const operations = editor.operations
if (!editor.isRemote) {
try {
AutomergeConnector.applySlateOps(editor, docId, operations, cursorData)
} catch (err) {
editor.handleError(err)
}
AutomergeConnector.applySlateOps(editor, docId, operations, cursorData)
onChange()
}
}
@ -102,11 +87,7 @@ const withAutomerge = <T extends Editor>(
*/
editor.receiveDocument = data => {
try {
AutomergeConnector.receiveDocument(editor, docId, data)
} catch (err) {
editor.handleError(err, JSON.stringify(data))
}
AutomergeConnector.receiveDocument(editor, docId, data)
}
/**
@ -117,16 +98,12 @@ const withAutomerge = <T extends Editor>(
// ignore document updates for differnt docIds
if (docId !== data.docId) return
try {
AutomergeConnector.applyOperation(
editor,
docId,
data,
preserveExternalHistory
)
} catch (err) {
editor.handleError(err, JSON.stringify(data))
}
AutomergeConnector.applyOperation(
editor,
docId,
data,
preserveExternalHistory
)
}
return editor

View File

@ -47,7 +47,7 @@ const withSocketIO = <T extends AutomergeEditor>(
// On socket io disconnect, cleanup cursor and call the provided onDisconnect callback
socket.on('disconnect', () => {
editor.gabageCursor()
editor.garbageCursor()
onDisconnect && onDisconnect()
})