mirror of
https://github.com/cudr/slate-collaborative.git
synced 2025-06-13 04:44:08 +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'
|
export * from './testUtils'
|
||||||
|
|
||||||
const toJS = (node: any) => {
|
const toJS = (node: any) => JSON.parse(JSON.stringify(node))
|
||||||
try {
|
|
||||||
return JSON.parse(JSON.stringify(node))
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Convert to js failed!!! Return null')
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloneNode = (node: any) => toSync(toJS(node))
|
const cloneNode = (node: any) => toSync(toJS(node))
|
||||||
|
|
||||||
|
@ -45,15 +45,38 @@ export const AutomergeConnector = {
|
|||||||
|
|
||||||
let changed: any
|
let changed: any
|
||||||
|
|
||||||
operations.forEach(op => {
|
for (let i = 0; i < operations.length; i++) {
|
||||||
changed = Automerge.change<SyncDoc>(changed || doc, d =>
|
const op = operations[i]
|
||||||
applyOperation(d.children, op)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
changed = Automerge.change(changed || doc, d => {
|
try {
|
||||||
setCursor(e.clientId, e.selection, d, operations, cursorData || {})
|
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)
|
e.docSet.setDoc(docId, changed)
|
||||||
},
|
},
|
||||||
@ -63,22 +86,32 @@ export const AutomergeConnector = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
receiveDocument: (e: AutomergeEditor, docId: string, data: string) => {
|
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>(
|
e.docSet.setDoc(docId, mergedDoc)
|
||||||
externalDoc,
|
|
||||||
currentDoc || Automerge.init()
|
|
||||||
)
|
|
||||||
|
|
||||||
e.docSet.setDoc(docId, mergedDoc)
|
Editor.withoutNormalizing(e, () => {
|
||||||
|
e.children = toJS(mergedDoc).children
|
||||||
Editor.withoutNormalizing(e, () => {
|
e.onChange()
|
||||||
e.children = toJS(mergedDoc).children
|
})
|
||||||
|
} catch (err) {
|
||||||
e.onChange()
|
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 {
|
try {
|
||||||
const current = e.docSet.getDoc(docId)
|
const current = e.docSet.getDoc(docId)
|
||||||
|
|
||||||
const updated = e.connection.receiveMsg(data)
|
const updated = e.connection.receiveMsg(data)
|
||||||
|
|
||||||
const operations = Automerge.diff(current, updated)
|
const operations = Automerge.diff(current, updated)
|
||||||
|
|
||||||
if (operations.length) {
|
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
|
e.isRemote = true
|
||||||
|
|
||||||
@ -117,13 +157,18 @@ export const AutomergeConnector = {
|
|||||||
|
|
||||||
Promise.resolve().then(_ => (e.isRemote = false))
|
Promise.resolve().then(_ => (e.isRemote = false))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
// unset remove flag
|
// unset remote flag
|
||||||
if (e.isRemote) {
|
if (e.isRemote) {
|
||||||
e.isRemote = false
|
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 { Editor } from 'slate'
|
||||||
import { CollabAction, CursorData, SyncDoc } from '@hiveteams/collab-bridge'
|
import { CollabAction, CursorData, SyncDoc } from '@hiveteams/collab-bridge'
|
||||||
|
|
||||||
interface ErrorData {
|
|
||||||
docId: string
|
|
||||||
serializedData: string
|
|
||||||
opData?: string
|
|
||||||
slateOperations?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AutomergeOptions {
|
export interface AutomergeOptions {
|
||||||
docId: string
|
docId: string
|
||||||
cursorData?: CursorData
|
cursorData?: CursorData
|
||||||
preserveExternalHistory?: boolean
|
preserveExternalHistory?: boolean
|
||||||
onError?: (msg: string | Error, data: ErrorData) => void
|
onError?: (msg: string | Error, data: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutomergeEditor extends Editor {
|
export interface AutomergeEditor extends Editor {
|
||||||
@ -32,11 +25,11 @@ export interface AutomergeEditor extends Editor {
|
|||||||
receiveDocument: (data: string) => void
|
receiveDocument: (data: string) => void
|
||||||
receiveOperation: (data: Automerge.Message) => void
|
receiveOperation: (data: Automerge.Message) => void
|
||||||
|
|
||||||
gabageCursor: () => void
|
garbageCursor: () => void
|
||||||
|
|
||||||
onCursor: (data: any) => void
|
onCursor: (data: any) => void
|
||||||
|
|
||||||
handleError: (err: Error | string, opData?: string) => void
|
handleError: (err: Error | string, data?: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SocketIOPluginOptions {
|
export interface SocketIOPluginOptions {
|
||||||
@ -44,7 +37,7 @@ export interface SocketIOPluginOptions {
|
|||||||
connectOpts: SocketIOClient.ConnectOpts
|
connectOpts: SocketIOClient.ConnectOpts
|
||||||
onConnect?: () => void
|
onConnect?: () => void
|
||||||
onDisconnect?: () => void
|
onDisconnect?: () => void
|
||||||
onError?: (msg: string | Error, data: ErrorData) => void
|
onError?: (msg: string | Error, data: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WithSocketIOEditor {
|
export interface WithSocketIOEditor {
|
||||||
|
@ -31,16 +31,10 @@ const withAutomerge = <T extends Editor>(
|
|||||||
* Helper function for handling errors
|
* Helper function for handling errors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
editor.handleError = (err: Error | string, opData?: string) => {
|
editor.handleError = (err: Error | string, data: any = {}) => {
|
||||||
const { docId, cursorData, onError } = options
|
const { onError } = options
|
||||||
if (onError && cursorData) {
|
if (onError) {
|
||||||
const document = editor.docSet.getDoc(docId)
|
onError(err, data)
|
||||||
onError(err, {
|
|
||||||
docId: docId,
|
|
||||||
serializedData: document ? Automerge.save(document) : 'No document',
|
|
||||||
opData,
|
|
||||||
slateOperations: JSON.stringify(editor.operations)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,12 +66,8 @@ const withAutomerge = <T extends Editor>(
|
|||||||
* Clear cursor data
|
* Clear cursor data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
editor.gabageCursor = () => {
|
editor.garbageCursor = () => {
|
||||||
try {
|
AutomergeConnector.garbageCursor(editor, docId)
|
||||||
AutomergeConnector.garbageCursor(editor, docId)
|
|
||||||
} catch (err) {
|
|
||||||
editor.handleError(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,12 +77,7 @@ const withAutomerge = <T extends Editor>(
|
|||||||
const operations = editor.operations
|
const operations = editor.operations
|
||||||
|
|
||||||
if (!editor.isRemote) {
|
if (!editor.isRemote) {
|
||||||
try {
|
AutomergeConnector.applySlateOps(editor, docId, operations, cursorData)
|
||||||
AutomergeConnector.applySlateOps(editor, docId, operations, cursorData)
|
|
||||||
} catch (err) {
|
|
||||||
editor.handleError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange()
|
onChange()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,11 +87,7 @@ const withAutomerge = <T extends Editor>(
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
editor.receiveDocument = data => {
|
editor.receiveDocument = data => {
|
||||||
try {
|
AutomergeConnector.receiveDocument(editor, docId, data)
|
||||||
AutomergeConnector.receiveDocument(editor, docId, data)
|
|
||||||
} catch (err) {
|
|
||||||
editor.handleError(err, JSON.stringify(data))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,16 +98,12 @@ const withAutomerge = <T extends Editor>(
|
|||||||
// ignore document updates for differnt docIds
|
// ignore document updates for differnt docIds
|
||||||
if (docId !== data.docId) return
|
if (docId !== data.docId) return
|
||||||
|
|
||||||
try {
|
AutomergeConnector.applyOperation(
|
||||||
AutomergeConnector.applyOperation(
|
editor,
|
||||||
editor,
|
docId,
|
||||||
docId,
|
data,
|
||||||
data,
|
preserveExternalHistory
|
||||||
preserveExternalHistory
|
)
|
||||||
)
|
|
||||||
} catch (err) {
|
|
||||||
editor.handleError(err, JSON.stringify(data))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return editor
|
return editor
|
||||||
|
@ -47,7 +47,7 @@ const withSocketIO = <T extends AutomergeEditor>(
|
|||||||
|
|
||||||
// On socket io disconnect, cleanup cursor and call the provided onDisconnect callback
|
// On socket io disconnect, cleanup cursor and call the provided onDisconnect callback
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
editor.gabageCursor()
|
editor.garbageCursor()
|
||||||
onDisconnect && onDisconnect()
|
onDisconnect && onDisconnect()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user