@bangle.dev/collab-client
Advanced tools
Comparing version 0.31.2 to 0.31.3
@@ -30,6 +30,11 @@ /** | ||
hardResetClient, | ||
queryCollabState, | ||
queryFatalError, | ||
updateServerVersion, | ||
} from '../src/commands'; | ||
import { collabMonitorKey } from '../src/common'; | ||
import { | ||
collabMonitorKey, | ||
CollabStateName, | ||
FatalErrorCode, | ||
} from '../src/common'; | ||
@@ -141,2 +146,3 @@ waitForExpect.defaults.timeout = 500; | ||
const setupServer = ({ | ||
instanceDeleteGuardOpts, | ||
managerId = DEFAULT_MANAGER_ID, | ||
@@ -158,2 +164,5 @@ rawDoc = { | ||
}: { | ||
instanceDeleteGuardOpts?: ConstructorParameters< | ||
typeof CollabManager | ||
>[0]['instanceDeleteGuardOpts']; | ||
managerId?: string; | ||
@@ -262,2 +271,3 @@ rawDoc?: { | ||
}, | ||
instanceDeleteGuardOpts, | ||
}); | ||
@@ -710,6 +720,4 @@ | ||
expect(console.error).toBeCalledTimes(1); | ||
expect(console.error).nthCalledWith( | ||
1, | ||
expect.stringContaining('In FatalErrorState message=Document not found'), | ||
expect(queryFatalError()(client1.view.state)?.errorCode).toBe( | ||
FatalErrorCode.DocumentNotFound, | ||
); | ||
@@ -804,8 +812,4 @@ }); | ||
expect(console.error).toBeCalledTimes(1); | ||
expect(console.error).nthCalledWith( | ||
1, | ||
expect.stringContaining( | ||
'In FatalErrorState message=History/Server not available', | ||
), | ||
expect(queryFatalError()(client1.view.state)?.errorCode).toBe( | ||
FatalErrorCode.HistoryNotAvailable, | ||
); | ||
@@ -860,2 +864,3 @@ }); | ||
expect(queryFatalError()(client1.view.state)).toEqual({ | ||
errorCode: FatalErrorCode.IncorrectManager, | ||
message: 'Incorrect manager', | ||
@@ -866,8 +871,2 @@ }); | ||
expect(document.querySelectorAll('.bangle-collab-active').length).toBe(0); | ||
expect(console.error).toBeCalledTimes(1); | ||
expect(console.error).nthCalledWith( | ||
1, | ||
expect.stringContaining('In FatalErrorState message=Incorrect manager'), | ||
); | ||
}); | ||
@@ -1042,2 +1041,3 @@ | ||
expect(queryFatalError()(client1.view.state)).toStrictEqual({ | ||
errorCode: FatalErrorCode.StuckInInfiniteLoop, | ||
message: | ||
@@ -1047,19 +1047,2 @@ 'Stuck in error loop, last failure: CollabFail.ManagerUnresponsive', | ||
expect(console.error).toBeCalledTimes(1); | ||
expect(console.error).toMatchInlineSnapshot(` | ||
[MockFunction] { | ||
"calls": Array [ | ||
Array [ | ||
"@bangle.dev/collab-client: In FatalErrorState message=Stuck in error loop, last failure: CollabFail.ManagerUnresponsive", | ||
], | ||
], | ||
"results": Array [ | ||
Object { | ||
"type": "return", | ||
"value": undefined, | ||
}, | ||
], | ||
} | ||
`); | ||
server = setupServer(); | ||
@@ -1452,1 +1435,205 @@ | ||
}); | ||
describe('deleting instance', () => { | ||
let server: ReturnType<typeof setupServer>; | ||
beforeEach(() => { | ||
server = setupServer({ | ||
managerId: 'manager-test-1', | ||
instanceDeleteGuardOpts: { | ||
deleteWaitTime: 30, | ||
maxDurationToKeepRecord: 150, | ||
}, | ||
}); | ||
}); | ||
test('when an instance is deleted client gets history not available error', async () => { | ||
const client1 = setupClient(server, { clientID: 'client1' }); | ||
await waitForExpect(async () => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("hello world!"))`); | ||
}); | ||
client1.typeText('X'); | ||
await waitForExpect(() => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("Xhello world!"))`); | ||
}); | ||
server.manager.requestDeleteInstance(docName); | ||
client1.typeText('Y'); | ||
await waitForExpect(() => { | ||
expect(queryCollabState()(client1.view.state)?.name).toBe( | ||
CollabStateName.Fatal, | ||
); | ||
}); | ||
expect(queryFatalError()(client1.view.state)?.errorCode).toBe( | ||
FatalErrorCode.HistoryNotAvailable, | ||
); | ||
}); | ||
test('resetting a client should work', async () => { | ||
const client1 = setupClient(server, { clientID: 'client1' }); | ||
await waitForExpect(async () => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("hello world!"))`); | ||
}); | ||
client1.typeText('X'); | ||
await waitForExpect(() => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("Xhello world!"))`); | ||
}); | ||
server.manager.requestDeleteInstance(docName); | ||
client1.typeText('Y'); | ||
await waitForExpect(() => { | ||
expect(queryCollabState()(client1.view.state)?.name).toBe( | ||
CollabStateName.Fatal, | ||
); | ||
}); | ||
expect(queryFatalError()(client1.view.state)?.errorCode).toBe( | ||
FatalErrorCode.HistoryNotAvailable, | ||
); | ||
await sleep(8); | ||
const sentTimes = Array.from( | ||
new Set((await server.getRequests()).map((r) => r.body.clientCreatedAt)), | ||
); | ||
// should just send one time | ||
expect(sentTimes.length).toBe(1); | ||
hardResetClient()(client1.view.state, client1.view.dispatch); | ||
await waitForExpect(() => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("Xhello world!"))`); | ||
}); | ||
const sentTimes2 = Array.from( | ||
new Set((await server.getRequests()).map((r) => r.body.clientCreatedAt)), | ||
); | ||
// after resetting should be sending new create time | ||
expect(sentTimes2.length).toBe(2); | ||
await sleep(5); | ||
client1.typeText('Z', 1); | ||
await sleep(5); | ||
expect(queryCollabState()(client1.view.state)?.name).toBe( | ||
CollabStateName.Ready, | ||
); | ||
await waitForExpect(() => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("ZXhello world!"))`); | ||
}); | ||
// wait a long time to make instance is not deleted | ||
await sleep(100); | ||
expect(server.manager.getAllDocNames().size).toBe(1); | ||
// now deleting after resetting the instance should still work | ||
server.manager.requestDeleteInstance(docName); | ||
client1.typeText('C'); | ||
await waitForExpect(() => { | ||
expect(queryFatalError()(client1.view.state)?.errorCode).toBe( | ||
FatalErrorCode.HistoryNotAvailable, | ||
); | ||
}); | ||
}); | ||
test('after deletion if a newer client connects, server responds ok', async () => { | ||
const client1 = setupClient(server, { clientID: 'client1' }); | ||
await waitForExpect(async () => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("hello world!"))`); | ||
}); | ||
client1.typeText('X'); | ||
await waitForExpect(() => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("Xhello world!"))`); | ||
}); | ||
server.manager.requestDeleteInstance(docName); | ||
client1.typeText('Y'); | ||
await waitForExpect(() => { | ||
expect(queryFatalError()(client1.view.state)?.errorCode).toBe( | ||
FatalErrorCode.HistoryNotAvailable, | ||
); | ||
}); | ||
// assert that it is deleted | ||
await waitForExpect(() => { | ||
expect(server.manager.getAllDocNames().size).toBe(0); | ||
}); | ||
const client2 = setupClient(server, { clientID: 'client2' }); | ||
await waitForExpect(async () => { | ||
expect(client2.debugString()).toEqual(`doc(paragraph("hello world!"))`); | ||
}); | ||
client2.typeText('Z'); | ||
await waitForExpect(() => { | ||
expect(client2.debugString()).toEqual(`doc(paragraph("Zhello world!"))`); | ||
}); | ||
}); | ||
test('a newer client cancels any previous deletion', async () => { | ||
const client1 = setupClient(server, { clientID: 'client1' }); | ||
await waitForExpect(async () => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("hello world!"))`); | ||
}); | ||
client1.typeText('X'); | ||
await waitForExpect(() => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("Xhello world!"))`); | ||
}); | ||
server.manager.requestDeleteInstance(docName); | ||
await sleep(4); | ||
// setup a new client right before the deletion is executed | ||
const client2 = setupClient(server, { clientID: 'client2' }); | ||
// type in the old client and get the error | ||
client1.typeText('Y'); | ||
await waitForExpect(() => { | ||
expect(queryFatalError()(client1.view.state)?.errorCode).toBe( | ||
FatalErrorCode.HistoryNotAvailable, | ||
); | ||
}); | ||
await waitForExpect(async () => { | ||
expect(client2.debugString()).toEqual(`doc(paragraph("Xhello world!"))`); | ||
}); | ||
client2.typeText('Z'); | ||
// wait plenty of time to make assert instance is not deleted | ||
await sleep(100); | ||
expect(server.manager.getAllDocNames().size).toBe(1); | ||
// should have 'ZX' to signal instance wasn't deleted | ||
await waitForExpect(async () => { | ||
expect(client2.debugString()).toEqual(`doc(paragraph("ZXhello world!"))`); | ||
}); | ||
}); | ||
}); |
@@ -27,5 +27,5 @@ --- | ||
InitDocState --> ReadyState: ReadyEvent | ||
InitDocState --> FatalErrorState: FatalErrorEvent | ||
InitDocState --> FatalState: FatalEvent | ||
InitErrorState --> InitState: RestartEvent | ||
InitErrorState --> FatalErrorState: FatalErrorEvent | ||
InitErrorState --> FatalState: FatalEvent | ||
ReadyState --> PushState: PushEvent | ||
@@ -40,4 +40,4 @@ ReadyState --> PullState: PullEvent | ||
PushPullErrorState --> PullState: PullEvent | ||
PushPullErrorState --> FatalErrorState: FatalErrorEvent | ||
FatalErrorState --> [*] | ||
PushPullErrorState --> FatalState: FatalEvent | ||
FatalState --> [*] | ||
``` |
import { CollabFail, ClientCommunication, CollabMessageBus } from '@bangle.dev/collab-comms'; | ||
import { Command, EditorState, EditorView, Node, TextSelection, Plugin } from '@bangle.dev/pm'; | ||
import { EditorState, EditorView, Node, TextSelection, Command, Plugin } from '@bangle.dev/pm'; | ||
@@ -10,5 +10,6 @@ interface ActionParam { | ||
} | ||
declare type ValidCollabStates = FatalErrorState | InitDocState | InitErrorState | InitState | PullState | PushPullErrorState | PushState | ReadyState; | ||
declare type ValidCollabState = FatalState | InitDocState | InitErrorState | InitState | PullState | PushPullErrorState | PushState | ReadyState; | ||
declare abstract class CollabBaseState { | ||
editingAllowed: boolean; | ||
isEditingBlocked: boolean; | ||
isTaggedError: boolean; | ||
debugInfo?: string; | ||
@@ -21,19 +22,20 @@ createdAt: number; | ||
}): Command; | ||
abstract isErrorState: boolean; | ||
isFatalState(): this is FatalErrorState; | ||
isFatalState(): this is FatalState; | ||
isReadyState(): this is ReadyState; | ||
abstract name: CollabStateName; | ||
abstract runAction(param: ActionParam): Promise<void>; | ||
abstract transition(event: ValidEvents, debugInfo?: string): ValidCollabStates | undefined; | ||
abstract transition(event: ValidEvents, debugInfo?: string): ValidCollabState | undefined; | ||
} | ||
declare class FatalErrorState extends CollabBaseState { | ||
declare class FatalState extends CollabBaseState { | ||
state: { | ||
message: string; | ||
errorCode: FatalErrorCode | undefined; | ||
}; | ||
debugInfo?: string | undefined; | ||
editingAllowed: boolean; | ||
isErrorState: boolean; | ||
isEditingBlocked: boolean; | ||
isTaggedError: boolean; | ||
name: CollabStateName; | ||
constructor(state: { | ||
message: string; | ||
errorCode: FatalErrorCode | undefined; | ||
}, debugInfo?: string | undefined); | ||
@@ -47,5 +49,5 @@ dispatch(signal: AbortSignal, state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: never, debugInfo?: string): boolean; | ||
debugInfo?: string | undefined; | ||
editingAllowed: boolean; | ||
isErrorState: boolean; | ||
isEditingBlocked: boolean; | ||
name: CollabStateName; | ||
clientCreatedAt: number; | ||
constructor(state?: {}, debugInfo?: string | undefined); | ||
@@ -62,6 +64,6 @@ dispatch(signal: AbortSignal, state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<InitState['transition']>[0], debugInfo?: string): boolean; | ||
managerId: string; | ||
clientCreatedAt: number; | ||
}; | ||
debugInfo?: string | undefined; | ||
editingAllowed: boolean; | ||
isErrorState: boolean; | ||
isEditingBlocked: boolean; | ||
name: CollabStateName; | ||
@@ -73,6 +75,7 @@ constructor(state: { | ||
managerId: string; | ||
clientCreatedAt: number; | ||
}, debugInfo?: string | undefined); | ||
dispatch(signal: AbortSignal, state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<InitDocState['transition']>[0], debugInfo?: string): boolean; | ||
runAction({ signal, view }: ActionParam): Promise<void>; | ||
transition(event: ReadyEvent | FatalErrorEvent, debugInfo?: string): ReadyState | FatalErrorState | undefined; | ||
transition(event: ReadyEvent | FatalEvent, debugInfo?: string): ReadyState | FatalState | undefined; | ||
} | ||
@@ -84,4 +87,4 @@ declare class InitErrorState extends CollabBaseState { | ||
debugInfo?: string | undefined; | ||
editingAllowed: boolean; | ||
isErrorState: boolean; | ||
isEditingBlocked: boolean; | ||
isTaggedError: boolean; | ||
name: CollabStateName; | ||
@@ -93,3 +96,3 @@ constructor(state: { | ||
runAction(param: ActionParam): Promise<void>; | ||
transition(event: RestartEvent | FatalErrorEvent, debugInfo?: string): FatalErrorState | InitState | undefined; | ||
transition(event: RestartEvent | FatalEvent, debugInfo?: string): FatalState | InitState | undefined; | ||
} | ||
@@ -99,4 +102,2 @@ declare class ReadyState extends CollabBaseState { | ||
debugInfo?: string | undefined; | ||
editingAllowed: boolean; | ||
isErrorState: boolean; | ||
name: CollabStateName; | ||
@@ -111,4 +112,2 @@ constructor(state: InitDocState['state'], debugInfo?: string | undefined); | ||
debugInfo?: string | undefined; | ||
editingAllowed: boolean; | ||
isErrorState: boolean; | ||
name: CollabStateName; | ||
@@ -123,8 +122,6 @@ constructor(state: InitDocState['state'], debugInfo?: string | undefined); | ||
debugInfo?: string | undefined; | ||
editingAllowed: boolean; | ||
isErrorState: boolean; | ||
name: CollabStateName; | ||
constructor(state: InitDocState['state'], debugInfo?: string | undefined); | ||
dispatch(signal: AbortSignal, state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<PullState['transition']>[0], debugInfo?: string): boolean; | ||
runAction({ logger, clientInfo, signal, view }: ActionParam): Promise<void>; | ||
runAction({ clientInfo, logger, signal, view }: ActionParam): Promise<void>; | ||
transition(event: ReadyEvent | PushPullErrorEvent, debugInfo?: string): PushPullErrorState | ReadyState | undefined; | ||
@@ -138,4 +135,3 @@ } | ||
debugInfo?: string | undefined; | ||
editingAllowed: boolean; | ||
isErrorState: boolean; | ||
isTaggedError: boolean; | ||
name: CollabStateName; | ||
@@ -148,3 +144,3 @@ constructor(state: { | ||
runAction(param: ActionParam): Promise<void>; | ||
transition(event: RestartEvent | PullEvent | FatalErrorEvent, debugInfo?: string): FatalErrorState | InitState | PullState | undefined; | ||
transition(event: RestartEvent | PullEvent | FatalEvent, debugInfo?: string): FatalState | InitState | PullState | undefined; | ||
} | ||
@@ -155,5 +151,5 @@ | ||
} | ||
declare type ValidEvents = FatalErrorEvent | HardResetEvent | InitDocEvent | InitErrorEvent | PullEvent | PushEvent | PushPullErrorEvent | ReadyEvent | RestartEvent; | ||
declare type ValidEvents = FatalEvent | HardResetEvent | InitDocEvent | InitErrorEvent | PullEvent | PushEvent | PushPullErrorEvent | ReadyEvent | RestartEvent; | ||
declare enum EventType { | ||
FatalError = "FATAL_ERROR_EVENT", | ||
Fatal = "FATAL_EVENT", | ||
HardReset = "HARD_RESET_EVENT", | ||
@@ -168,6 +164,15 @@ InitDoc = "INIT_DOC_EVENT", | ||
} | ||
interface FatalErrorEvent { | ||
type: EventType.FatalError; | ||
declare enum FatalErrorCode { | ||
InitialDocLoadFailed = "INITIAL_DOC_LOAD_FAILED", | ||
StuckInInfiniteLoop = "STUCK_IN_INFINITE_LOOP", | ||
IncorrectManager = "INCORRECT_MANAGER", | ||
HistoryNotAvailable = "HISTORY_NOT_AVAILABLE", | ||
DocumentNotFound = "DOCUMENT_NOT_FOUND", | ||
UnexpectedState = "UNEXPECTED_STATE" | ||
} | ||
interface FatalEvent { | ||
type: EventType.Fatal; | ||
payload: { | ||
message: string; | ||
errorCode: FatalErrorCode; | ||
}; | ||
@@ -212,3 +217,3 @@ } | ||
declare enum CollabStateName { | ||
FatalError = "FATAL_ERROR_STATE", | ||
Fatal = "FATAL_STATE", | ||
Init = "INIT_STATE", | ||
@@ -223,4 +228,4 @@ InitDoc = "INIT_DOC_STATE", | ||
interface CollabPluginState { | ||
collabState: CollabBaseState; | ||
previousStates: CollabBaseState[]; | ||
collabState: ValidCollabState; | ||
previousStates: ValidCollabState[]; | ||
infiniteTransitionGuard: { | ||
@@ -241,5 +246,11 @@ counter: number; | ||
/** | ||
* If in fatal state (a terminal state) and returns the error information. | ||
* @returns | ||
*/ | ||
declare function queryFatalError(): (state: EditorState) => { | ||
message: string; | ||
errorCode: FatalErrorCode | undefined; | ||
} | undefined; | ||
declare function queryCollabState(): (state: EditorState) => ValidCollabState | undefined; | ||
declare function hardResetClient(): Command; | ||
@@ -251,2 +262,3 @@ | ||
hardResetClient: typeof hardResetClient; | ||
queryCollabState: typeof queryCollabState; | ||
}; | ||
@@ -284,2 +296,2 @@ interface CollabExtensionOptions { | ||
export { collabExtension_d as collabClient }; | ||
export { CollabStateName, FatalErrorCode, ValidCollabState, collabExtension_d as collabClient }; |
@@ -15,3 +15,3 @@ import { getVersion, receiveTransaction, sendableSteps, collab } from 'prosemirror-collab'; | ||
(function (EventType) { | ||
EventType["FatalError"] = "FATAL_ERROR_EVENT"; | ||
EventType["Fatal"] = "FATAL_EVENT"; | ||
EventType["HardReset"] = "HARD_RESET_EVENT"; | ||
@@ -26,5 +26,14 @@ EventType["InitDoc"] = "INIT_DOC_EVENT"; | ||
})(EventType || (EventType = {})); | ||
var FatalErrorCode; | ||
(function (FatalErrorCode) { | ||
FatalErrorCode["InitialDocLoadFailed"] = "INITIAL_DOC_LOAD_FAILED"; | ||
FatalErrorCode["StuckInInfiniteLoop"] = "STUCK_IN_INFINITE_LOOP"; | ||
FatalErrorCode["IncorrectManager"] = "INCORRECT_MANAGER"; | ||
FatalErrorCode["HistoryNotAvailable"] = "HISTORY_NOT_AVAILABLE"; | ||
FatalErrorCode["DocumentNotFound"] = "DOCUMENT_NOT_FOUND"; | ||
FatalErrorCode["UnexpectedState"] = "UNEXPECTED_STATE"; | ||
})(FatalErrorCode || (FatalErrorCode = {})); | ||
var CollabStateName; | ||
(function (CollabStateName) { | ||
CollabStateName["FatalError"] = "FATAL_ERROR_STATE"; | ||
CollabStateName["Fatal"] = "FATAL_STATE"; | ||
CollabStateName["Init"] = "INIT_STATE"; | ||
@@ -122,9 +131,21 @@ CollabStateName["InitDoc"] = "INIT_DOC_STATE"; | ||
// If in a fatal error (error which will not be recovered), it returns a fatal error message. | ||
/** | ||
* If in fatal state (a terminal state) and returns the error information. | ||
* @returns | ||
*/ | ||
function queryFatalError() { | ||
return (state) => { | ||
const collabState = getCollabState(state); | ||
return (collabState === null || collabState === void 0 ? void 0 : collabState.isFatalState()) ? collabState.state : undefined; | ||
if (collabState === null || collabState === void 0 ? void 0 : collabState.isFatalState()) { | ||
return collabState.state; | ||
} | ||
return undefined; | ||
}; | ||
} | ||
function queryCollabState() { | ||
return (state) => { | ||
const collabState = getCollabState(state); | ||
return collabState; | ||
}; | ||
} | ||
// Discards any editor changes that have not yet been sent to the server. | ||
@@ -203,3 +224,3 @@ // and sets the editor doc to the one provider by server. | ||
} | ||
return (previousStates.filter((s) => s.isErrorState).length > | ||
return (previousStates.filter((s) => s.isTaggedError).length > | ||
STUCK_IN_ERROR_THRESHOLD); | ||
@@ -213,5 +234,7 @@ }; | ||
// Some collab tr's are allowed. | ||
this.editingAllowed = true; | ||
this.isEditingBlocked = false; | ||
this.isTaggedError = false; | ||
this.createdAt = Date.now(); | ||
} | ||
// A helper function to dispatch events in correct shape | ||
dispatchCollabPluginEvent(data) { | ||
@@ -227,3 +250,3 @@ return (state, dispatch) => { | ||
isFatalState() { | ||
return this instanceof FatalErrorState; | ||
return this instanceof FatalState; | ||
} | ||
@@ -234,3 +257,3 @@ isReadyState() { | ||
} | ||
class FatalErrorState extends CollabBaseState { | ||
class FatalState extends CollabBaseState { | ||
constructor(state, debugInfo) { | ||
@@ -240,5 +263,5 @@ super(); | ||
this.debugInfo = debugInfo; | ||
this.editingAllowed = false; | ||
this.isErrorState = true; | ||
this.name = CollabStateName.FatalError; | ||
this.isEditingBlocked = true; | ||
this.isTaggedError = true; | ||
this.name = CollabStateName.Fatal; | ||
} | ||
@@ -256,3 +279,3 @@ dispatch(signal, state, dispatch, event, debugInfo) { | ||
} | ||
logger(`Freezing document(${clientInfo.docName}) to prevent further edits due to FatalError`); | ||
logger(`Freezing document(${clientInfo.docName}) to prevent further edits due to FatalState`); | ||
return; | ||
@@ -269,5 +292,8 @@ } | ||
this.debugInfo = debugInfo; | ||
this.editingAllowed = false; | ||
this.isErrorState = false; | ||
this.isEditingBlocked = true; | ||
this.name = CollabStateName.Init; | ||
// clientCreatedAt should just be created once at the start of the client | ||
// and not at any state where the value might change during the course of the clients life. | ||
// this field setup order is instrumental to connect with the server. | ||
this.clientCreatedAt = Date.now(); | ||
} | ||
@@ -295,2 +321,3 @@ dispatch(signal, state, dispatch, event, debugInfo) { | ||
const result = await clientCom.getDocument({ | ||
clientCreatedAt: this.clientCreatedAt, | ||
docName, | ||
@@ -330,2 +357,3 @@ userId, | ||
managerId: payload.managerId, | ||
clientCreatedAt: this.clientCreatedAt, | ||
}, debugInfo); | ||
@@ -349,4 +377,3 @@ } | ||
this.debugInfo = debugInfo; | ||
this.editingAllowed = false; | ||
this.isErrorState = false; | ||
this.isEditingBlocked = true; | ||
this.name = CollabStateName.InitDoc; | ||
@@ -370,5 +397,6 @@ } | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: 'Failed to load initial doc', | ||
errorCode: FatalErrorCode.InitialDocLoadFailed, | ||
}, | ||
@@ -389,4 +417,4 @@ }); | ||
} | ||
else if (type === EventType.FatalError) { | ||
return new FatalErrorState({ message: event.payload.message }, debugInfo); | ||
else if (type === EventType.Fatal) { | ||
return new FatalState({ message: event.payload.message, errorCode: event.payload.errorCode }, debugInfo); | ||
} | ||
@@ -404,4 +432,4 @@ else { | ||
this.debugInfo = debugInfo; | ||
this.editingAllowed = false; | ||
this.isErrorState = true; | ||
this.isEditingBlocked = true; | ||
this.isTaggedError = true; | ||
this.name = CollabStateName.InitError; | ||
@@ -424,4 +452,4 @@ } | ||
} | ||
else if (type === EventType.FatalError) { | ||
return new FatalErrorState({ message: event.payload.message }, debugInfo); | ||
else if (type === EventType.Fatal) { | ||
return new FatalState({ message: event.payload.message, errorCode: event.payload.errorCode }, debugInfo); | ||
} | ||
@@ -439,4 +467,2 @@ else { | ||
this.debugInfo = debugInfo; | ||
this.editingAllowed = true; | ||
this.isErrorState = false; | ||
this.name = CollabStateName.Ready; | ||
@@ -489,4 +515,2 @@ } | ||
this.debugInfo = debugInfo; | ||
this.editingAllowed = true; | ||
this.isErrorState = false; | ||
this.name = CollabStateName.Push; | ||
@@ -516,2 +540,3 @@ } | ||
const response = await clientCom.pushEvents({ | ||
clientCreatedAt: this.state.clientCreatedAt, | ||
version: getVersion(view.state), | ||
@@ -568,4 +593,2 @@ steps: steps ? steps.steps.map((s) => s.toJSON()) : [], | ||
this.debugInfo = debugInfo; | ||
this.editingAllowed = true; | ||
this.isErrorState = false; | ||
this.name = CollabStateName.Pull; | ||
@@ -580,3 +603,3 @@ } | ||
} | ||
async runAction({ logger, clientInfo, signal, view }) { | ||
async runAction({ clientInfo, logger, signal, view }) { | ||
if (signal.aborted) { | ||
@@ -588,2 +611,3 @@ return; | ||
const response = await clientCom.pullEvents({ | ||
clientCreatedAt: this.state.clientCreatedAt, | ||
version: getVersion(view.state), | ||
@@ -643,4 +667,3 @@ docName: docName, | ||
this.debugInfo = debugInfo; | ||
this.editingAllowed = true; | ||
this.isErrorState = true; | ||
this.isTaggedError = true; | ||
this.name = CollabStateName.PushPullError; | ||
@@ -666,4 +689,4 @@ } | ||
} | ||
else if (type === EventType.FatalError) { | ||
return new FatalErrorState({ message: event.payload.message }, debugInfo); | ||
else if (type === EventType.Fatal) { | ||
return new FatalState({ message: event.payload.message, errorCode: event.payload.errorCode }, debugInfo); | ||
} | ||
@@ -685,5 +708,6 @@ else { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: 'Stuck in error loop, last failure: ' + failure, | ||
errorCode: FatalErrorCode.StuckInInfiniteLoop, | ||
}, | ||
@@ -706,5 +730,6 @@ }, debugSource); | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: 'Incorrect manager', | ||
errorCode: FatalErrorCode.IncorrectManager, | ||
}, | ||
@@ -716,5 +741,6 @@ }, debugSource); | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: 'History/Server not available', | ||
errorCode: FatalErrorCode.HistoryNotAvailable, | ||
}, | ||
@@ -727,5 +753,6 @@ }, debugSource); | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: 'Document not found', | ||
errorCode: FatalErrorCode.DocumentNotFound, | ||
}, | ||
@@ -744,5 +771,7 @@ }, debugSource); | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: `Cannot handle ${failure} in state=${collabState.name}`, | ||
// TODO: is this the right error code? | ||
errorCode: FatalErrorCode.UnexpectedState, | ||
}, | ||
@@ -784,5 +813,6 @@ }, debugSource); | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: `Cannot handle ${failure} in state=${collabState.name}`, | ||
errorCode: FatalErrorCode.UnexpectedState, | ||
}, | ||
@@ -843,3 +873,3 @@ }, debugSource); | ||
// prevent any other tr while state is in one of the no-edit state | ||
if (tr.docChanged && ((_a = getCollabState(state)) === null || _a === void 0 ? void 0 : _a.editingAllowed) === false) { | ||
if (tr.docChanged && ((_a = getCollabState(state)) === null || _a === void 0 ? void 0 : _a.isEditingBlocked) === true) { | ||
console.debug('@bangle.dev/collab-client blocking transaction'); | ||
@@ -863,2 +893,3 @@ return false; | ||
} | ||
// By pass any logic, if we receive this event and set the state to Init | ||
if (meta.collabEvent.type === EventType.HardReset) { | ||
@@ -887,3 +918,6 @@ logger(newState)('apply state HARD RESET, newStateName=', CollabStateName.Init, 'oldStateName=', value.collabState.name); | ||
return { | ||
collabState: new FatalErrorState({ message: 'Infinite transitions' }, '(stuck in infinite transitions)'), | ||
collabState: new FatalState({ | ||
message: 'Infinite transitions', | ||
errorCode: FatalErrorCode.StuckInInfiniteLoop, | ||
}, '(stuck in infinite transitions)'), | ||
previousStates: [value.collabState, ...value.previousStates], | ||
@@ -906,5 +940,2 @@ infiniteTransitionGuard: { counter: 0, lastChecked: 0 }, | ||
} | ||
if (newCollabState.isFatalState()) { | ||
console.error(`@bangle.dev/collab-client: In FatalErrorState message=${newCollabState.state.message}`); | ||
} | ||
return { | ||
@@ -987,7 +1018,7 @@ ...value, | ||
var _a; | ||
const editingAllowed = (_a = getCollabState(state)) === null || _a === void 0 ? void 0 : _a.editingAllowed; | ||
const editingBlocked = (_a = getCollabState(state)) === null || _a === void 0 ? void 0 : _a.isEditingBlocked; | ||
return { | ||
class: editingAllowed | ||
? 'bangle-collab-active' | ||
: 'bangle-collab-frozen', | ||
class: editingBlocked | ||
? 'bangle-collab-frozen' | ||
: 'bangle-collab-active', | ||
}; | ||
@@ -1046,3 +1077,3 @@ }, | ||
const plugins = pluginsFactory; | ||
const commands = { queryFatalError, hardResetClient }; | ||
const commands = { queryFatalError, hardResetClient, queryCollabState }; | ||
/** | ||
@@ -1049,0 +1080,0 @@ * |
{ | ||
"name": "@bangle.dev/collab-client", | ||
"version": "0.31.2", | ||
"version": "0.31.3", | ||
"homepage": "https://bangle.dev", | ||
@@ -42,8 +42,8 @@ "authors": [ | ||
"devDependencies": { | ||
"@bangle.dev/all-base-components": "0.31.2", | ||
"@bangle.dev/collab-manager": "0.31.2", | ||
"@bangle.dev/core": "0.31.2", | ||
"@bangle.dev/disk": "0.31.2", | ||
"@bangle.dev/pm": "0.31.2", | ||
"@bangle.dev/test-helpers": "0.31.2", | ||
"@bangle.dev/all-base-components": "0.31.3", | ||
"@bangle.dev/collab-manager": "0.31.3", | ||
"@bangle.dev/core": "0.31.3", | ||
"@bangle.dev/disk": "0.31.3", | ||
"@bangle.dev/pm": "0.31.3", | ||
"@bangle.dev/test-helpers": "0.31.3", | ||
"@types/node": "^17.0.43", | ||
@@ -55,7 +55,6 @@ "localforage": "^1.9.0", | ||
"dependencies": { | ||
"@bangle.dev/collab-comms": "0.31.2", | ||
"@bangle.dev/utils": "0.31.2", | ||
"@bangle.dev/collab-comms": "0.31.3", | ||
"@bangle.dev/utils": "0.31.3", | ||
"@types/jest": "^27.5.2", | ||
"prosemirror-collab": "^1.3.0", | ||
"xstate": "^4.32.1" | ||
"prosemirror-collab": "^1.3.0" | ||
}, | ||
@@ -62,0 +61,0 @@ "exports": { |
@@ -26,6 +26,7 @@ import { getVersion, sendableSteps } from 'prosemirror-collab'; | ||
EventType, | ||
FatalErrorCode, | ||
MAX_STATES_TO_KEEP, | ||
} from './common'; | ||
import { getCollabState } from './helpers'; | ||
import { CollabBaseState, FatalErrorState, InitState } from './state'; | ||
import { CollabBaseState, FatalState, InitState } from './state'; | ||
@@ -37,3 +38,3 @@ const LOG = true; | ||
const collabMonitorInitialState = { | ||
const collabMonitorInitialState: CollabMonitor = { | ||
serverVersion: undefined, | ||
@@ -88,3 +89,3 @@ }; | ||
// prevent any other tr while state is in one of the no-edit state | ||
if (tr.docChanged && getCollabState(state)?.editingAllowed === false) { | ||
if (tr.docChanged && getCollabState(state)?.isEditingBlocked === true) { | ||
console.debug('@bangle.dev/collab-client blocking transaction'); | ||
@@ -113,2 +114,3 @@ return false; | ||
// By pass any logic, if we receive this event and set the state to Init | ||
if (meta.collabEvent.type === EventType.HardReset) { | ||
@@ -121,2 +123,3 @@ logger(newState)( | ||
); | ||
return { | ||
@@ -154,4 +157,7 @@ collabState: new InitState(undefined, '(HardReset)'), | ||
return { | ||
collabState: new FatalErrorState( | ||
{ message: 'Infinite transitions' }, | ||
collabState: new FatalState( | ||
{ | ||
message: 'Infinite transitions', | ||
errorCode: FatalErrorCode.StuckInInfiniteLoop, | ||
}, | ||
'(stuck in infinite transitions)', | ||
@@ -191,8 +197,2 @@ ), | ||
if (newCollabState.isFatalState()) { | ||
console.error( | ||
`@bangle.dev/collab-client: In FatalErrorState message=${newCollabState.state.message}`, | ||
); | ||
} | ||
return { | ||
@@ -217,2 +217,3 @@ ...value, | ||
let clientComController = new AbortController(); | ||
let clientCom = new ClientCommunication({ | ||
@@ -287,7 +288,7 @@ clientId: clientID, | ||
attributes: (state: any) => { | ||
const editingAllowed = getCollabState(state)?.editingAllowed; | ||
const editingBlocked = getCollabState(state)?.isEditingBlocked; | ||
return { | ||
class: editingAllowed | ||
? 'bangle-collab-active' | ||
: 'bangle-collab-frozen', | ||
class: editingBlocked | ||
? 'bangle-collab-frozen' | ||
: 'bangle-collab-active', | ||
}; | ||
@@ -294,0 +295,0 @@ }, |
@@ -8,6 +8,6 @@ import { collab } from 'prosemirror-collab'; | ||
import { collabClientPlugin } from './collab-client'; | ||
import { hardResetClient, queryFatalError } from './commands'; | ||
import { hardResetClient, queryCollabState, queryFatalError } from './commands'; | ||
export const plugins = pluginsFactory; | ||
export const commands = { queryFatalError, hardResetClient }; | ||
export const commands = { queryFatalError, hardResetClient, queryCollabState }; | ||
@@ -14,0 +14,0 @@ export interface CollabExtensionOptions { |
@@ -14,10 +14,23 @@ import { getVersion } from 'prosemirror-collab'; | ||
// If in a fatal error (error which will not be recovered), it returns a fatal error message. | ||
/** | ||
* If in fatal state (a terminal state) and returns the error information. | ||
* @returns | ||
*/ | ||
export function queryFatalError() { | ||
return (state: EditorState) => { | ||
const collabState = getCollabState(state); | ||
return collabState?.isFatalState() ? collabState.state : undefined; | ||
if (collabState?.isFatalState()) { | ||
return collabState.state; | ||
} | ||
return undefined; | ||
}; | ||
} | ||
export function queryCollabState() { | ||
return (state: EditorState) => { | ||
const collabState = getCollabState(state); | ||
return collabState; | ||
}; | ||
} | ||
// Discards any editor changes that have not yet been sent to the server. | ||
@@ -120,3 +133,3 @@ // and sets the editor doc to the one provider by server. | ||
return ( | ||
previousStates.filter((s) => s.isErrorState).length > | ||
previousStates.filter((s) => s.isTaggedError).length > | ||
STUCK_IN_ERROR_THRESHOLD | ||
@@ -123,0 +136,0 @@ ); |
import { ClientCommunication, CollabFail } from '@bangle.dev/collab-comms'; | ||
import { Node, PluginKey, TextSelection } from '@bangle.dev/pm'; | ||
import type { CollabBaseState } from './state'; | ||
import type { ValidCollabState } from './state'; | ||
@@ -28,3 +28,3 @@ export const MAX_STATES_TO_KEEP = 15; | ||
export type ValidEvents = | ||
| FatalErrorEvent | ||
| FatalEvent | ||
| HardResetEvent | ||
@@ -40,3 +40,3 @@ | InitDocEvent | ||
export enum EventType { | ||
FatalError = 'FATAL_ERROR_EVENT', | ||
Fatal = 'FATAL_EVENT', | ||
HardReset = 'HARD_RESET_EVENT', | ||
@@ -52,6 +52,16 @@ InitDoc = 'INIT_DOC_EVENT', | ||
export interface FatalErrorEvent { | ||
type: EventType.FatalError; | ||
export enum FatalErrorCode { | ||
InitialDocLoadFailed = 'INITIAL_DOC_LOAD_FAILED', | ||
StuckInInfiniteLoop = 'STUCK_IN_INFINITE_LOOP', | ||
IncorrectManager = 'INCORRECT_MANAGER', | ||
HistoryNotAvailable = 'HISTORY_NOT_AVAILABLE', | ||
DocumentNotFound = 'DOCUMENT_NOT_FOUND', | ||
UnexpectedState = 'UNEXPECTED_STATE', | ||
} | ||
export interface FatalEvent { | ||
type: EventType.Fatal; | ||
payload: { | ||
message: string; | ||
errorCode: FatalErrorCode; | ||
}; | ||
@@ -103,3 +113,3 @@ } | ||
export enum CollabStateName { | ||
FatalError = 'FATAL_ERROR_STATE', | ||
Fatal = 'FATAL_STATE', | ||
Init = 'INIT_STATE', | ||
@@ -115,4 +125,4 @@ InitDoc = 'INIT_DOC_STATE', | ||
export interface CollabPluginState { | ||
collabState: CollabBaseState; | ||
previousStates: CollabBaseState[]; | ||
collabState: ValidCollabState; | ||
previousStates: ValidCollabState[]; | ||
infiniteTransitionGuard: { counter: number; lastChecked: number }; | ||
@@ -119,0 +129,0 @@ } |
import * as collabClient from './collab-extension'; | ||
export type { CollabStateName, FatalErrorCode } from './common'; | ||
export { collabClient }; | ||
export type { ValidCollabState } from './state'; |
118
src/state.ts
@@ -23,3 +23,4 @@ import { getVersion, sendableSteps } from 'prosemirror-collab'; | ||
EventType, | ||
FatalErrorEvent, | ||
FatalErrorCode, | ||
FatalEvent, | ||
InitDocEvent, | ||
@@ -43,4 +44,4 @@ InitErrorEvent, | ||
export type ValidCollabStates = | ||
| FatalErrorState | ||
export type ValidCollabState = | ||
| FatalState | ||
| InitDocState | ||
@@ -57,5 +58,9 @@ | InitErrorState | ||
// Some collab tr's are allowed. | ||
editingAllowed: boolean = true; | ||
isEditingBlocked: boolean = false; | ||
isTaggedError: boolean = false; | ||
debugInfo?: string; | ||
createdAt = Date.now(); | ||
// A helper function to dispatch events in correct shape | ||
public dispatchCollabPluginEvent(data: { | ||
@@ -75,6 +80,4 @@ signal: AbortSignal; | ||
abstract isErrorState: boolean; | ||
public isFatalState(): this is FatalErrorState { | ||
return this instanceof FatalErrorState; | ||
public isFatalState(): this is FatalState { | ||
return this instanceof FatalState; | ||
} | ||
@@ -93,13 +96,14 @@ | ||
debugInfo?: string, | ||
): ValidCollabStates | undefined; | ||
): ValidCollabState | undefined; | ||
} | ||
export class FatalErrorState extends CollabBaseState { | ||
editingAllowed = false; | ||
isErrorState = true; | ||
name = CollabStateName.FatalError; | ||
export class FatalState extends CollabBaseState { | ||
isEditingBlocked = true; | ||
isTaggedError = true; | ||
name = CollabStateName.Fatal; | ||
constructor( | ||
public state: { | ||
message: string; | ||
errorCode: FatalErrorCode | undefined; | ||
}, | ||
@@ -130,3 +134,3 @@ public debugInfo?: string, | ||
logger( | ||
`Freezing document(${clientInfo.docName}) to prevent further edits due to FatalError`, | ||
`Freezing document(${clientInfo.docName}) to prevent further edits due to FatalState`, | ||
); | ||
@@ -142,6 +146,10 @@ return; | ||
export class InitState extends CollabBaseState { | ||
editingAllowed = false; | ||
isErrorState = false; | ||
isEditingBlocked = true; | ||
name = CollabStateName.Init; | ||
// clientCreatedAt should just be created once at the start of the client | ||
// and not at any state where the value might change during the course of the clients life. | ||
// this field setup order is instrumental to connect with the server. | ||
clientCreatedAt = Date.now(); | ||
constructor(public state = {}, public debugInfo?: string) { | ||
@@ -181,2 +189,3 @@ super(); | ||
const result = await clientCom.getDocument({ | ||
clientCreatedAt: this.clientCreatedAt, | ||
docName, | ||
@@ -237,2 +246,3 @@ userId, | ||
managerId: payload.managerId, | ||
clientCreatedAt: this.clientCreatedAt, | ||
}, | ||
@@ -259,4 +269,3 @@ debugInfo, | ||
export class InitDocState extends CollabBaseState { | ||
editingAllowed = false; | ||
isErrorState = false; | ||
isEditingBlocked = true; | ||
name = CollabStateName.InitDoc; | ||
@@ -270,2 +279,3 @@ | ||
managerId: string; | ||
clientCreatedAt: number; | ||
}, | ||
@@ -307,5 +317,6 @@ public debugInfo?: string, | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: 'Failed to load initial doc', | ||
errorCode: FatalErrorCode.InitialDocLoadFailed, | ||
}, | ||
@@ -328,10 +339,13 @@ }); | ||
transition( | ||
event: ReadyEvent | FatalErrorEvent, | ||
event: ReadyEvent | FatalEvent, | ||
debugInfo?: string, | ||
): ReadyState | FatalErrorState | undefined { | ||
): ReadyState | FatalState | undefined { | ||
const type = event.type; | ||
if (type === EventType.Ready) { | ||
return new ReadyState(this.state, debugInfo); | ||
} else if (type === EventType.FatalError) { | ||
return new FatalErrorState({ message: event.payload.message }, debugInfo); | ||
} else if (type === EventType.Fatal) { | ||
return new FatalState( | ||
{ message: event.payload.message, errorCode: event.payload.errorCode }, | ||
debugInfo, | ||
); | ||
} else { | ||
@@ -346,4 +360,4 @@ let val: never = type; | ||
export class InitErrorState extends CollabBaseState { | ||
editingAllowed = false; | ||
isErrorState = true; | ||
isEditingBlocked = true; | ||
isTaggedError = true; | ||
name = CollabStateName.InitError; | ||
@@ -378,8 +392,11 @@ | ||
transition(event: RestartEvent | FatalErrorEvent, debugInfo?: string) { | ||
transition(event: RestartEvent | FatalEvent, debugInfo?: string) { | ||
const type = event.type; | ||
if (type === EventType.Restart) { | ||
return new InitState(undefined, debugInfo); | ||
} else if (type === EventType.FatalError) { | ||
return new FatalErrorState({ message: event.payload.message }, debugInfo); | ||
} else if (type === EventType.Fatal) { | ||
return new FatalState( | ||
{ message: event.payload.message, errorCode: event.payload.errorCode }, | ||
debugInfo, | ||
); | ||
} else { | ||
@@ -394,4 +411,2 @@ let val: never = type; | ||
export class ReadyState extends CollabBaseState { | ||
editingAllowed = true; | ||
isErrorState = false; | ||
name = CollabStateName.Ready; | ||
@@ -464,4 +479,2 @@ | ||
export class PushState extends CollabBaseState { | ||
editingAllowed = true; | ||
isErrorState = false; | ||
name = CollabStateName.Push; | ||
@@ -512,2 +525,3 @@ | ||
const response = await clientCom.pushEvents({ | ||
clientCreatedAt: this.state.clientCreatedAt, | ||
version: getVersion(view.state), | ||
@@ -579,4 +593,2 @@ steps: steps ? steps.steps.map((s) => s.toJSON()) : [], | ||
export class PullState extends CollabBaseState { | ||
editingAllowed = true; | ||
isErrorState = false; | ||
name = CollabStateName.Pull; | ||
@@ -602,3 +614,3 @@ | ||
async runAction({ logger, clientInfo, signal, view }: ActionParam) { | ||
async runAction({ clientInfo, logger, signal, view }: ActionParam) { | ||
if (signal.aborted) { | ||
@@ -610,2 +622,3 @@ return; | ||
const response = await clientCom.pullEvents({ | ||
clientCreatedAt: this.state.clientCreatedAt, | ||
version: getVersion(view.state), | ||
@@ -683,4 +696,4 @@ docName: docName, | ||
export class PushPullErrorState extends CollabBaseState { | ||
editingAllowed = true; | ||
isErrorState = true; | ||
isTaggedError = true; | ||
name = CollabStateName.PushPullError; | ||
@@ -716,6 +729,3 @@ | ||
transition( | ||
event: RestartEvent | PullEvent | FatalErrorEvent, | ||
debugInfo?: string, | ||
) { | ||
transition(event: RestartEvent | PullEvent | FatalEvent, debugInfo?: string) { | ||
const type = event.type; | ||
@@ -726,4 +736,7 @@ if (type === EventType.Restart) { | ||
return new PullState(this.state.initDocState, debugInfo); | ||
} else if (type === EventType.FatalError) { | ||
return new FatalErrorState({ message: event.payload.message }, debugInfo); | ||
} else if (type === EventType.Fatal) { | ||
return new FatalState( | ||
{ message: event.payload.message, errorCode: event.payload.errorCode }, | ||
debugInfo, | ||
); | ||
} else { | ||
@@ -759,5 +772,6 @@ let val: never = type; | ||
{ | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: 'Stuck in error loop, last failure: ' + failure, | ||
errorCode: FatalErrorCode.StuckInInfiniteLoop, | ||
}, | ||
@@ -798,5 +812,6 @@ }, | ||
{ | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: 'Incorrect manager', | ||
errorCode: FatalErrorCode.IncorrectManager, | ||
}, | ||
@@ -814,5 +829,6 @@ }, | ||
{ | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: 'History/Server not available', | ||
errorCode: FatalErrorCode.HistoryNotAvailable, | ||
}, | ||
@@ -831,5 +847,6 @@ }, | ||
{ | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: 'Document not found', | ||
errorCode: FatalErrorCode.DocumentNotFound, | ||
}, | ||
@@ -859,5 +876,7 @@ }, | ||
{ | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: `Cannot handle ${failure} in state=${collabState.name}`, | ||
// TODO: is this the right error code? | ||
errorCode: FatalErrorCode.UnexpectedState, | ||
}, | ||
@@ -932,5 +951,6 @@ }, | ||
{ | ||
type: EventType.FatalError, | ||
type: EventType.Fatal, | ||
payload: { | ||
message: `Cannot handle ${failure} in state=${collabState.name}`, | ||
errorCode: FatalErrorCode.UnexpectedState, | ||
}, | ||
@@ -937,0 +957,0 @@ }, |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
184753
6
5376
+ Added@bangle.dev/collab-comms@0.31.3(transitive)
+ Added@bangle.dev/utils@0.31.3(transitive)
- Removedxstate@^4.32.1
- Removed@bangle.dev/collab-comms@0.31.2(transitive)
- Removed@bangle.dev/utils@0.31.2(transitive)
- Removedxstate@4.38.3(transitive)
Updated@bangle.dev/utils@0.31.3