mirror of
https://github.com/cudr/slate-collaborative.git
synced 2024-10-27 20:34:06 +00:00
feat: better client side error handling
This commit is contained in:
parent
c72c42ec05
commit
d4b089a480
@ -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))
|
||||
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user