@bangle.dev/collab-client
Advanced tools
Comparing version 0.30.0-alpha.13 to 0.30.0-alpha.14
@@ -30,4 +30,4 @@ /** | ||
hardResetClient, | ||
onUpstreamChanges, | ||
queryFatalError, | ||
updateServerVersion, | ||
} from '../src/commands'; | ||
@@ -39,3 +39,2 @@ import { collabMonitorKey } from '../src/common'; | ||
const docName = 'test-doc'; | ||
jest.setTimeout(30000); | ||
@@ -595,61 +594,2 @@ const clientMessagingSetup = ({ | ||
test('hard reset', async () => { | ||
let server = setupServer({ managerId: 'manager-test-1' }); | ||
const client1 = setupClient(server, { clientID: 'client1' }); | ||
await waitForExpect(async () => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("hello world!"))`); | ||
}); | ||
client1.typeText('one'); | ||
client1.typeText('two'); | ||
client1.typeText('three'); | ||
await waitForExpect(async () => { | ||
expect(server.manager.getCollabState(docName)?.version).toEqual(3); | ||
}); | ||
server = setupServer({ | ||
managerId: 'manager-test-1', | ||
rawDoc: { | ||
type: 'doc', | ||
content: [ | ||
{ | ||
type: 'paragraph', | ||
content: [ | ||
{ | ||
type: 'text', | ||
text: 'reset bro!', | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
}); | ||
client1.changeServer(server); | ||
hardResetClient()(client1.view.state, client1.view.dispatch); | ||
// should be reset | ||
await waitForExpect(async () => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("reset bro!"))`); | ||
}); | ||
expect(collabMonitorKey.getState(client1.view.state)?.serverVersion).toBe( | ||
undefined, | ||
); | ||
// should continue to sync | ||
client1.typeText('hi again'); | ||
await waitForExpect(async () => { | ||
expect(server.manager.getCollabState(docName)?.doc.toString()).toEqual( | ||
`doc(paragraph("reset bro!hi again"))`, | ||
); | ||
}); | ||
expect(client1.debugString()).toEqual(`doc(paragraph("reset bro!hi again"))`); | ||
expect(collabMonitorKey.getState(client1.view.state)?.serverVersion).toBe(1); | ||
}); | ||
describe('failures', () => { | ||
@@ -1220,2 +1160,3 @@ test('handles ApplyFailed', async () => { | ||
...body, | ||
version: 0, | ||
steps: [ | ||
@@ -1320,3 +1261,3 @@ { | ||
onUpstreamChanges(783)(client1.view.state, client1.view.dispatch); | ||
updateServerVersion(783)(client1.view.state, client1.view.dispatch); | ||
expect(collabMonitorKey.getState(client1.view.state)?.serverVersion).toBe( | ||
@@ -1347,1 +1288,113 @@ 783, | ||
}); | ||
test('hard reset', async () => { | ||
let server = setupServer({ managerId: 'manager-test-1' }); | ||
const client1 = setupClient(server, { clientID: 'client1' }); | ||
await waitForExpect(async () => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("hello world!"))`); | ||
}); | ||
client1.typeText('one'); | ||
client1.typeText('two'); | ||
client1.typeText('three'); | ||
await waitForExpect(async () => { | ||
expect(server.manager.getCollabState(docName)?.version).toEqual(3); | ||
}); | ||
server = setupServer({ | ||
managerId: 'manager-test-1', | ||
rawDoc: { | ||
type: 'doc', | ||
content: [ | ||
{ | ||
type: 'paragraph', | ||
content: [ | ||
{ | ||
type: 'text', | ||
text: 'reset bro!', | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
}); | ||
client1.changeServer(server); | ||
hardResetClient()(client1.view.state, client1.view.dispatch); | ||
// should be reset | ||
await waitForExpect(async () => { | ||
expect(client1.debugString()).toEqual(`doc(paragraph("reset bro!"))`); | ||
}); | ||
expect(collabMonitorKey.getState(client1.view.state)?.serverVersion).toBe( | ||
undefined, | ||
); | ||
// should continue to sync | ||
client1.typeText('hi again'); | ||
await waitForExpect(async () => { | ||
expect(server.manager.getCollabState(docName)?.doc.toString()).toEqual( | ||
`doc(paragraph("reset bro!hi again"))`, | ||
); | ||
}); | ||
expect(client1.debugString()).toEqual(`doc(paragraph("reset bro!hi again"))`); | ||
expect(collabMonitorKey.getState(client1.view.state)?.serverVersion).toBe(1); | ||
}); | ||
test('hard reset to trigger a content refresh', async () => { | ||
let server = setupServer({ | ||
managerId: 'manager-test-1', | ||
rawDoc: { | ||
type: 'doc', | ||
content: [ | ||
{ | ||
type: 'bulletList', | ||
attrs: { | ||
tight: true, | ||
}, | ||
content: [ | ||
{ | ||
type: 'listItem', | ||
attrs: { | ||
todoChecked: null, | ||
}, | ||
content: [ | ||
{ | ||
type: 'paragraph', | ||
content: [ | ||
{ | ||
type: 'text', | ||
text: 'Hello world', | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
}); | ||
const client1 = setupClient(server, { clientID: 'client1' }); | ||
await waitForExpect(async () => { | ||
expect(client1.debugString()).toEqual( | ||
`doc(bulletList(listItem(paragraph("Hello world"))))`, | ||
); | ||
}); | ||
// should continue to sync | ||
client1.typeText('X'); | ||
hardResetClient()(client1.view.state, client1.view.dispatch); | ||
server.manager.removeCollabState(docName); | ||
queueMicrotask(() => { | ||
server.manager.removeCollabState(docName); | ||
}); | ||
await sleep(5); | ||
}); |
@@ -35,3 +35,3 @@ --- | ||
PushState --> PushPullErrorState: PushPullErrorEvent | ||
PullState --> ReadyState: ReadyEvent | ||
PullState --> ReadyState: ReadyAfterPull | ||
PullState --> PushPullErrorState: PushPullErrorEvent | ||
@@ -38,0 +38,0 @@ PushPullErrorState --> InitState: RestartEvent |
@@ -16,2 +16,3 @@ import { CollabFail, ClientCommunication, CollabMessageBus } from '@bangle.dev/collab-comms'; | ||
dispatchCollabPluginEvent(data: { | ||
signal: AbortSignal; | ||
collabEvent?: ValidEvents; | ||
@@ -38,3 +39,3 @@ debugInfo?: string; | ||
}, debugInfo?: string | undefined); | ||
dispatch(state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: never, debugInfo?: string): boolean; | ||
dispatch(signal: AbortSignal, state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: never, debugInfo?: string): boolean; | ||
runAction({ signal, clientInfo, logger }: ActionParam): Promise<void>; | ||
@@ -50,3 +51,3 @@ transition(event: any, debugInfo?: string): this; | ||
constructor(state?: {}, debugInfo?: string | undefined); | ||
dispatch(state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<InitState['transition']>[0], debugInfo?: string): boolean; | ||
dispatch(signal: AbortSignal, state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<InitState['transition']>[0], debugInfo?: string): boolean; | ||
runAction({ clientInfo, signal, view }: ActionParam): Promise<void>; | ||
@@ -72,3 +73,3 @@ transition(event: InitDocEvent | InitErrorEvent, debugInfo?: string): InitDocState | InitErrorState | undefined; | ||
}, debugInfo?: string | undefined); | ||
dispatch(state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<InitDocState['transition']>[0], debugInfo?: string): boolean; | ||
dispatch(signal: AbortSignal, state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<InitDocState['transition']>[0], debugInfo?: string): boolean; | ||
runAction({ signal, view }: ActionParam): Promise<void>; | ||
@@ -88,3 +89,3 @@ transition(event: ReadyEvent | FatalErrorEvent, debugInfo?: string): ReadyState | FatalErrorState | undefined; | ||
}, debugInfo?: string | undefined); | ||
dispatch(state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<InitErrorState['transition']>[0], debugInfo?: string): boolean; | ||
dispatch(signal: AbortSignal, state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<InitErrorState['transition']>[0], debugInfo?: string): boolean; | ||
runAction(param: ActionParam): Promise<void>; | ||
@@ -100,5 +101,5 @@ transition(event: RestartEvent | FatalErrorEvent, debugInfo?: string): FatalErrorState | InitState | undefined; | ||
constructor(state: InitDocState['state'], debugInfo?: string | undefined); | ||
dispatch(state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: PullEvent | PushEvent, debugInfo?: string): boolean; | ||
dispatch(signal: AbortSignal, state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: PullEvent | PushEvent, debugInfo?: string): boolean; | ||
runAction({ signal, view }: ActionParam): Promise<void>; | ||
transition(event: Parameters<ReadyState['dispatch']>[2], debugInfo?: string): PullState | PushState | undefined; | ||
transition(event: Parameters<ReadyState['dispatch']>[3], debugInfo?: string): PullState | PushState | undefined; | ||
} | ||
@@ -112,3 +113,3 @@ declare class PushState extends CollabBaseState { | ||
constructor(state: InitDocState['state'], debugInfo?: string | undefined); | ||
dispatch(state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<PushState['transition']>[0], debugInfo?: string): boolean; | ||
dispatch(signal: AbortSignal, state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<PushState['transition']>[0], debugInfo?: string): boolean; | ||
runAction({ clientInfo, signal, view }: ActionParam): Promise<void>; | ||
@@ -124,3 +125,3 @@ transition(event: ReadyEvent | PullEvent | PushPullErrorEvent, debugInfo?: string): PullState | PushPullErrorState | ReadyState | undefined; | ||
constructor(state: InitDocState['state'], debugInfo?: string | undefined); | ||
dispatch(state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<PullState['transition']>[0], debugInfo?: string): boolean; | ||
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>; | ||
@@ -142,3 +143,3 @@ transition(event: ReadyEvent | PushPullErrorEvent, debugInfo?: string): PushPullErrorState | ReadyState | undefined; | ||
}, debugInfo?: string | undefined); | ||
dispatch(state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<PushPullErrorState['transition']>[0], debugInfo?: string): boolean; | ||
dispatch(signal: AbortSignal, state: EditorState, dispatch: EditorView['dispatch'] | undefined, event: Parameters<PushPullErrorState['transition']>[0], debugInfo?: string): boolean; | ||
runAction(param: ActionParam): Promise<void>; | ||
@@ -157,4 +158,4 @@ transition(event: RestartEvent | PullEvent | FatalErrorEvent, debugInfo?: string): FatalErrorState | InitState | PullState | undefined; | ||
InitError = "INIT_ERROR_EVENT", | ||
Pull = "PULL_EVENT", | ||
Push = "PUSH_EVENT", | ||
Pull = "PULL_EVENT", | ||
PushPullError = "PUSH_PULL_ERROR_EVENT", | ||
@@ -219,2 +220,6 @@ Ready = "READY_EVENT", | ||
previousStates: CollabBaseState[]; | ||
infiniteTransitionGuard: { | ||
counter: number; | ||
lastChecked: number; | ||
}; | ||
} | ||
@@ -221,0 +226,0 @@ interface ClientInfo { |
@@ -19,4 +19,4 @@ import { getVersion, receiveTransaction, sendableSteps, collab } from 'prosemirror-collab'; | ||
EventType["InitError"] = "INIT_ERROR_EVENT"; | ||
EventType["Pull"] = "PULL_EVENT"; | ||
EventType["Push"] = "PUSH_EVENT"; | ||
EventType["Pull"] = "PULL_EVENT"; | ||
EventType["PushPullError"] = "PUSH_PULL_ERROR_EVENT"; | ||
@@ -134,2 +134,3 @@ EventType["Ready"] = "READY_EVENT"; | ||
collabState === null || collabState === void 0 ? void 0 : collabState.dispatchCollabPluginEvent({ | ||
signal: new AbortController().signal, | ||
collabEvent: { | ||
@@ -145,3 +146,3 @@ type: EventType.HardReset, | ||
// then uses this information to determine whether to pull from server or not. | ||
function onUpstreamChanges(serverVersion) { | ||
function updateServerVersion(serverVersion) { | ||
return (state, dispatch) => { | ||
@@ -164,3 +165,3 @@ const pluginState = collabMonitorKey.getState(state); | ||
if (collabState === null || collabState === void 0 ? void 0 : collabState.isReadyState()) { | ||
collabState === null || collabState === void 0 ? void 0 : collabState.dispatch(state, dispatch, { | ||
collabState === null || collabState === void 0 ? void 0 : collabState.dispatch(new AbortController().signal, state, dispatch, { | ||
type: EventType.Push, | ||
@@ -188,3 +189,3 @@ }, 'onLocalChanges'); | ||
if (collabState === null || collabState === void 0 ? void 0 : collabState.isReadyState()) { | ||
collabState.dispatch(state, dispatch, { | ||
collabState.dispatch(new AbortController().signal, state, dispatch, { | ||
type: EventType.Pull, | ||
@@ -218,4 +219,7 @@ }, 'collabMonitorKey(outdated-local-version)'); | ||
return (state, dispatch) => { | ||
dispatch === null || dispatch === void 0 ? void 0 : dispatch(state.tr.setMeta(collabClientKey, data)); | ||
return true; | ||
if (!data.signal.aborted) { | ||
dispatch === null || dispatch === void 0 ? void 0 : dispatch(state.tr.setMeta(collabClientKey, data)); | ||
return true; | ||
} | ||
return false; | ||
}; | ||
@@ -239,4 +243,5 @@ } | ||
} | ||
dispatch(state, dispatch, event, debugInfo) { | ||
dispatch(signal, state, dispatch, event, debugInfo) { | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -266,4 +271,5 @@ debugInfo, | ||
} | ||
dispatch(state, dispatch, event, debugInfo) { | ||
dispatch(signal, state, dispatch, event, debugInfo) { | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -294,3 +300,3 @@ debugInfo, | ||
if (!result.ok) { | ||
this.dispatch(view.state, view.dispatch, { | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.InitError, | ||
@@ -302,3 +308,3 @@ payload: { failure: result.body }, | ||
const { doc, managerId, version } = result.body; | ||
this.dispatch(view.state, view.dispatch, { | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.InitDoc, | ||
@@ -345,4 +351,5 @@ payload: { | ||
} | ||
dispatch(state, dispatch, event, debugInfo) { | ||
dispatch(signal, state, dispatch, event, debugInfo) { | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -360,3 +367,3 @@ debugInfo, | ||
if (success === false) { | ||
this.dispatch(view.state, view.dispatch, { | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
@@ -369,3 +376,3 @@ payload: { | ||
else { | ||
this.dispatch(view.state, view.dispatch, { | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.Ready, | ||
@@ -399,4 +406,5 @@ }, `runAction:${this.name}`); | ||
} | ||
dispatch(state, dispatch, event, debugInfo) { | ||
dispatch(signal, state, dispatch, event, debugInfo) { | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -432,4 +440,5 @@ debugInfo, | ||
} | ||
dispatch(state, dispatch, event, debugInfo) { | ||
dispatch(signal, state, dispatch, event, debugInfo) { | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -446,3 +455,3 @@ debugInfo, | ||
if (isOutdatedVersion()(view.state)) { | ||
this.dispatch(view.state, view.dispatch, { | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.Pull, | ||
@@ -452,3 +461,3 @@ }, debugString + '(outdated-local-version)'); | ||
else if (sendableSteps(view.state)) { | ||
this.dispatch(view.state, view.dispatch, { | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.Push, | ||
@@ -483,4 +492,5 @@ }, debugString + '(sendable-steps)'); | ||
} | ||
dispatch(state, dispatch, event, debugInfo) { | ||
dispatch(signal, state, dispatch, event, debugInfo) { | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -498,3 +508,3 @@ debugInfo, | ||
if (!steps) { | ||
this.dispatch(view.state, view.dispatch, { | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.Ready, | ||
@@ -520,3 +530,3 @@ }, debugSource + '(no steps):'); | ||
// get any new steps from other clients | ||
this.dispatch(view.state, view.dispatch, { | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.Pull, | ||
@@ -526,3 +536,3 @@ }, debugSource); | ||
else { | ||
this.dispatch(view.state, view.dispatch, { | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.PushPullError, | ||
@@ -563,4 +573,5 @@ payload: { failure: response.body }, | ||
} | ||
dispatch(state, dispatch, event, debugInfo) { | ||
dispatch(signal, state, dispatch, event, debugInfo) { | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -589,3 +600,3 @@ debugInfo, | ||
if (success === false) { | ||
this.dispatch(view.state, view.dispatch, { | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.PushPullError, | ||
@@ -596,3 +607,5 @@ payload: { failure: CollabFail.ApplyFailed }, | ||
else { | ||
this.dispatch(view.state, view.dispatch, { | ||
// keep our local server version up to date with what server responded with | ||
updateServerVersion(response.body.version)(view.state, view.dispatch); | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.Ready, | ||
@@ -603,3 +616,3 @@ }, debugSource); | ||
else { | ||
this.dispatch(view.state, view.dispatch, { | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.PushPullError, | ||
@@ -636,4 +649,5 @@ payload: { failure: response.body }, | ||
} | ||
dispatch(state, dispatch, event, debugInfo) { | ||
dispatch(signal, state, dispatch, event, debugInfo) { | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -671,3 +685,3 @@ debugInfo, | ||
if (isStuckInErrorStates()(view.state)) { | ||
collabState.dispatch(view.state, view.dispatch, { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
@@ -684,3 +698,3 @@ payload: { | ||
if (!signal.aborted) { | ||
collabState.dispatch(view.state, view.dispatch, { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.Restart, | ||
@@ -693,3 +707,3 @@ }, debugSource); | ||
case CollabFail.IncorrectManager: { | ||
collabState.dispatch(view.state, view.dispatch, { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
@@ -703,3 +717,3 @@ payload: { | ||
case CollabFail.HistoryNotAvailable: { | ||
collabState.dispatch(view.state, view.dispatch, { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
@@ -714,3 +728,3 @@ payload: { | ||
logger('Document not found'); | ||
collabState.dispatch(view.state, view.dispatch, { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
@@ -726,3 +740,3 @@ payload: { | ||
if (collabState instanceof PushPullErrorState) { | ||
collabState.dispatch(view.state, view.dispatch, { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.Pull, | ||
@@ -732,3 +746,3 @@ }, debugSource); | ||
else { | ||
collabState.dispatch(view.state, view.dispatch, { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
@@ -747,3 +761,3 @@ payload: { | ||
if (collabState instanceof PushPullErrorState) { | ||
collabState.dispatch(view.state, view.dispatch, { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.Pull, | ||
@@ -753,3 +767,3 @@ }, debugSource); | ||
else if (collabState instanceof InitErrorState) { | ||
collabState.dispatch(view.state, view.dispatch, { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.Restart, | ||
@@ -767,3 +781,3 @@ }, debugSource); | ||
if (!signal.aborted) { | ||
collabState.dispatch(view.state, view.dispatch, { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.Pull, | ||
@@ -775,3 +789,3 @@ }, debugSource); | ||
else { | ||
collabState.dispatch(view.state, view.dispatch, { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
@@ -789,3 +803,3 @@ payload: { | ||
if (collabState instanceof PushPullErrorState) { | ||
collabState.dispatch(view.state, view.dispatch, { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.Pull, | ||
@@ -795,3 +809,3 @@ }, debugSource); | ||
else if (collabState instanceof InitErrorState) { | ||
collabState.dispatch(view.state, view.dispatch, { | ||
collabState.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.Restart, | ||
@@ -818,2 +832,4 @@ }, debugSource); | ||
}; | ||
const INFINITE_TRANSITION_SAMPLE = 500; | ||
const INFINITE_TRANSITION_THRESHOLD_TIME = 1000; | ||
function collabClientPlugin({ requestTimeout, clientID, collabMessageBus, docName, managerId, cooldownTime, userId, warmupTime, }) { | ||
@@ -847,2 +863,3 @@ const logger = (state) => (...args) => { | ||
previousStates: [], | ||
infiniteTransitionGuard: { counter: 0, lastChecked: 0 }, | ||
}; | ||
@@ -860,4 +877,28 @@ }, | ||
previousStates: [], | ||
infiniteTransitionGuard: { counter: 0, lastChecked: 0 }, | ||
}; | ||
} | ||
value.infiniteTransitionGuard.counter++; | ||
// A guard to prevent infinite transitions in case of a bug | ||
if (value.infiniteTransitionGuard.counter % | ||
INFINITE_TRANSITION_SAMPLE === | ||
0) { | ||
if (Date.now() - value.infiniteTransitionGuard.lastChecked <= | ||
INFINITE_TRANSITION_THRESHOLD_TIME) { | ||
queueMicrotask(() => { | ||
throw new Error('Stuck in infinite transitions. Last few states: ' + | ||
value.previousStates | ||
.map((s) => s.name + (s.debugInfo ? `:${s.debugInfo}` : '')) | ||
.join(', ') | ||
.slice(0, 5)); | ||
}); | ||
return { | ||
collabState: new FatalErrorState({ message: 'Infinite transitions' }, '(stuck in infinite transitions)'), | ||
previousStates: [value.collabState, ...value.previousStates], | ||
infiniteTransitionGuard: { counter: 0, lastChecked: 0 }, | ||
}; | ||
} | ||
value.infiniteTransitionGuard.lastChecked = Date.now(); | ||
value.infiniteTransitionGuard.counter = 0; | ||
} | ||
const newCollabState = value.collabState.transition(meta.collabEvent, meta.debugInfo); | ||
@@ -897,3 +938,3 @@ if (!newCollabState) { | ||
onNewVersion: ({ version }) => { | ||
onUpstreamChanges(version)(view.state, view.dispatch); | ||
updateServerVersion(version)(view.state, view.dispatch); | ||
}, | ||
@@ -900,0 +941,0 @@ }); |
{ | ||
"name": "@bangle.dev/collab-client", | ||
"version": "0.30.0-alpha.13", | ||
"version": "0.30.0-alpha.14", | ||
"homepage": "https://bangle.dev", | ||
@@ -42,8 +42,8 @@ "authors": [ | ||
"devDependencies": { | ||
"@bangle.dev/all-base-components": "0.30.0-alpha.13", | ||
"@bangle.dev/collab-manager": "0.30.0-alpha.13", | ||
"@bangle.dev/core": "0.30.0-alpha.13", | ||
"@bangle.dev/disk": "0.30.0-alpha.13", | ||
"@bangle.dev/pm": "0.30.0-alpha.13", | ||
"@bangle.dev/test-helpers": "0.30.0-alpha.13", | ||
"@bangle.dev/all-base-components": "0.30.0-alpha.14", | ||
"@bangle.dev/collab-manager": "0.30.0-alpha.14", | ||
"@bangle.dev/core": "0.30.0-alpha.14", | ||
"@bangle.dev/disk": "0.30.0-alpha.14", | ||
"@bangle.dev/pm": "0.30.0-alpha.14", | ||
"@bangle.dev/test-helpers": "0.30.0-alpha.14", | ||
"@types/node": "^17.0.43", | ||
@@ -55,4 +55,4 @@ "localforage": "^1.9.0", | ||
"dependencies": { | ||
"@bangle.dev/collab-comms": "0.30.0-alpha.13", | ||
"@bangle.dev/utils": "0.30.0-alpha.13", | ||
"@bangle.dev/collab-comms": "0.30.0-alpha.14", | ||
"@bangle.dev/utils": "0.30.0-alpha.14", | ||
"@types/jest": "^27.5.2", | ||
@@ -59,0 +59,0 @@ "prosemirror-collab": "^1.3.0", |
@@ -14,3 +14,3 @@ import { getVersion, sendableSteps } from 'prosemirror-collab'; | ||
onOutdatedVersion, | ||
onUpstreamChanges, | ||
updateServerVersion, | ||
} from './commands'; | ||
@@ -29,3 +29,3 @@ import { | ||
import { getCollabState } from './helpers'; | ||
import { CollabBaseState, InitState } from './state'; | ||
import { CollabBaseState, FatalErrorState, InitState } from './state'; | ||
@@ -41,2 +41,5 @@ const LOG = true; | ||
const INFINITE_TRANSITION_SAMPLE = 500; | ||
const INFINITE_TRANSITION_THRESHOLD_TIME = 1000; | ||
export function collabClientPlugin({ | ||
@@ -97,2 +100,3 @@ requestTimeout, | ||
previousStates: [], | ||
infiniteTransitionGuard: { counter: 0, lastChecked: 0 }, | ||
}; | ||
@@ -119,5 +123,43 @@ }, | ||
previousStates: [], | ||
infiniteTransitionGuard: { counter: 0, lastChecked: 0 }, | ||
}; | ||
} | ||
value.infiniteTransitionGuard.counter++; | ||
// A guard to prevent infinite transitions in case of a bug | ||
if ( | ||
value.infiniteTransitionGuard.counter % | ||
INFINITE_TRANSITION_SAMPLE === | ||
0 | ||
) { | ||
if ( | ||
Date.now() - value.infiniteTransitionGuard.lastChecked <= | ||
INFINITE_TRANSITION_THRESHOLD_TIME | ||
) { | ||
queueMicrotask(() => { | ||
throw new Error( | ||
'Stuck in infinite transitions. Last few states: ' + | ||
value.previousStates | ||
.map( | ||
(s) => s.name + (s.debugInfo ? `:${s.debugInfo}` : ''), | ||
) | ||
.join(', ') | ||
.slice(0, 5), | ||
); | ||
}); | ||
return { | ||
collabState: new FatalErrorState( | ||
{ message: 'Infinite transitions' }, | ||
'(stuck in infinite transitions)', | ||
), | ||
previousStates: [value.collabState, ...value.previousStates], | ||
infiniteTransitionGuard: { counter: 0, lastChecked: 0 }, | ||
}; | ||
} | ||
value.infiniteTransitionGuard.lastChecked = Date.now(); | ||
value.infiniteTransitionGuard.counter = 0; | ||
} | ||
const newCollabState = value.collabState.transition( | ||
@@ -180,3 +222,3 @@ meta.collabEvent, | ||
onNewVersion: ({ version }) => { | ||
onUpstreamChanges(version)(view.state, view.dispatch); | ||
updateServerVersion(version)(view.state, view.dispatch); | ||
}, | ||
@@ -183,0 +225,0 @@ }); |
@@ -28,2 +28,3 @@ import { getVersion } from 'prosemirror-collab'; | ||
collabState?.dispatchCollabPluginEvent({ | ||
signal: new AbortController().signal, | ||
collabEvent: { | ||
@@ -40,3 +41,5 @@ type: EventType.HardReset, | ||
// then uses this information to determine whether to pull from server or not. | ||
export function onUpstreamChanges(serverVersion: number | undefined): Command { | ||
export function updateServerVersion( | ||
serverVersion: number | undefined, | ||
): Command { | ||
return (state, dispatch) => { | ||
@@ -63,2 +66,3 @@ const pluginState = collabMonitorKey.getState(state); | ||
collabState?.dispatch( | ||
new AbortController().signal, | ||
state, | ||
@@ -95,2 +99,3 @@ dispatch, | ||
collabState.dispatch( | ||
new AbortController().signal, | ||
state, | ||
@@ -97,0 +102,0 @@ dispatch, |
@@ -43,4 +43,4 @@ import { ClientCommunication, CollabFail } from '@bangle.dev/collab-comms'; | ||
InitError = 'INIT_ERROR_EVENT', | ||
Pull = 'PULL_EVENT', | ||
Push = 'PUSH_EVENT', | ||
Pull = 'PULL_EVENT', | ||
PushPullError = 'PUSH_PULL_ERROR_EVENT', | ||
@@ -114,2 +114,3 @@ Ready = 'READY_EVENT', | ||
previousStates: CollabBaseState[]; | ||
infiniteTransitionGuard: { counter: number; lastChecked: number }; | ||
} | ||
@@ -116,0 +117,0 @@ |
@@ -13,4 +13,8 @@ import { getVersion, sendableSteps } from 'prosemirror-collab'; | ||
import { isOutdatedVersion, isStuckInErrorStates } from './commands'; | ||
import { | ||
isOutdatedVersion, | ||
isStuckInErrorStates, | ||
updateServerVersion, | ||
} from './commands'; | ||
import { | ||
ClientInfo, | ||
@@ -56,2 +60,3 @@ collabClientKey, | ||
public dispatchCollabPluginEvent(data: { | ||
signal: AbortSignal; | ||
collabEvent?: ValidEvents; | ||
@@ -61,4 +66,7 @@ debugInfo?: string; | ||
return (state, dispatch) => { | ||
dispatch?.(state.tr.setMeta(collabClientKey, data)); | ||
return true; | ||
if (!data.signal.aborted) { | ||
dispatch?.(state.tr.setMeta(collabClientKey, data)); | ||
return true; | ||
} | ||
return false; | ||
}; | ||
@@ -102,2 +110,3 @@ } | ||
dispatch( | ||
signal: AbortSignal, | ||
state: EditorState, | ||
@@ -109,2 +118,3 @@ dispatch: EditorView['dispatch'] | undefined, | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -140,2 +150,3 @@ debugInfo, | ||
dispatch( | ||
signal: AbortSignal, | ||
state: EditorState, | ||
@@ -147,2 +158,3 @@ dispatch: EditorView['dispatch'] | undefined, | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -179,2 +191,3 @@ debugInfo, | ||
this.dispatch( | ||
signal, | ||
view.state, | ||
@@ -194,2 +207,3 @@ view.dispatch, | ||
this.dispatch( | ||
signal, | ||
view.state, | ||
@@ -262,2 +276,3 @@ view.dispatch, | ||
dispatch( | ||
signal: AbortSignal, | ||
state: EditorState, | ||
@@ -269,2 +284,3 @@ dispatch: EditorView['dispatch'] | undefined, | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -290,3 +306,3 @@ debugInfo, | ||
if (success === false) { | ||
this.dispatch(view.state, view.dispatch, { | ||
this.dispatch(signal, view.state, view.dispatch, { | ||
type: EventType.FatalError, | ||
@@ -299,2 +315,3 @@ payload: { | ||
this.dispatch( | ||
signal, | ||
view.state, | ||
@@ -343,2 +360,3 @@ view.dispatch, | ||
dispatch( | ||
signal: AbortSignal, | ||
state: EditorState, | ||
@@ -350,2 +368,3 @@ dispatch: EditorView['dispatch'] | undefined, | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -384,2 +403,3 @@ debugInfo, | ||
dispatch( | ||
signal: AbortSignal, | ||
state: EditorState, | ||
@@ -391,2 +411,3 @@ dispatch: EditorView['dispatch'] | undefined, | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -406,2 +427,3 @@ debugInfo, | ||
this.dispatch( | ||
signal, | ||
view.state, | ||
@@ -416,2 +438,3 @@ view.dispatch, | ||
this.dispatch( | ||
signal, | ||
view.state, | ||
@@ -429,3 +452,3 @@ view.dispatch, | ||
transition(event: Parameters<ReadyState['dispatch']>[2], debugInfo?: string) { | ||
transition(event: Parameters<ReadyState['dispatch']>[3], debugInfo?: string) { | ||
const type = event.type; | ||
@@ -454,2 +477,3 @@ if (type === EventType.Push) { | ||
dispatch( | ||
signal: AbortSignal, | ||
state: EditorState, | ||
@@ -461,2 +485,3 @@ dispatch: EditorView['dispatch'] | undefined, | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -478,2 +503,3 @@ debugInfo, | ||
this.dispatch( | ||
signal, | ||
view.state, | ||
@@ -509,2 +535,3 @@ view.dispatch, | ||
this.dispatch( | ||
signal, | ||
view.state, | ||
@@ -519,2 +546,3 @@ view.dispatch, | ||
this.dispatch( | ||
signal, | ||
view.state, | ||
@@ -567,2 +595,3 @@ view.dispatch, | ||
dispatch( | ||
signal: AbortSignal, | ||
state: EditorState, | ||
@@ -574,2 +603,3 @@ dispatch: EditorView['dispatch'] | undefined, | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -601,2 +631,3 @@ debugInfo, | ||
this.dispatch( | ||
signal, | ||
view.state, | ||
@@ -611,3 +642,7 @@ view.dispatch, | ||
} else { | ||
// keep our local server version up to date with what server responded with | ||
updateServerVersion(response.body.version)(view.state, view.dispatch); | ||
this.dispatch( | ||
signal, | ||
view.state, | ||
@@ -623,2 +658,3 @@ view.dispatch, | ||
this.dispatch( | ||
signal, | ||
view.state, | ||
@@ -671,2 +707,3 @@ view.dispatch, | ||
dispatch( | ||
signal: AbortSignal, | ||
state: EditorState, | ||
@@ -678,2 +715,3 @@ dispatch: EditorView['dispatch'] | undefined, | ||
return this.dispatchCollabPluginEvent({ | ||
signal, | ||
collabEvent: event, | ||
@@ -725,2 +763,3 @@ debugInfo, | ||
collabState.dispatch( | ||
signal, | ||
view.state, | ||
@@ -745,2 +784,3 @@ view.dispatch, | ||
collabState.dispatch( | ||
signal, | ||
view.state, | ||
@@ -763,2 +803,3 @@ view.dispatch, | ||
collabState.dispatch( | ||
signal, | ||
view.state, | ||
@@ -778,2 +819,3 @@ view.dispatch, | ||
collabState.dispatch( | ||
signal, | ||
view.state, | ||
@@ -794,2 +836,3 @@ view.dispatch, | ||
collabState.dispatch( | ||
signal, | ||
view.state, | ||
@@ -811,2 +854,3 @@ view.dispatch, | ||
collabState.dispatch( | ||
signal, | ||
view.state, | ||
@@ -821,2 +865,3 @@ view.dispatch, | ||
collabState.dispatch( | ||
signal, | ||
view.state, | ||
@@ -844,2 +889,3 @@ view.dispatch, | ||
collabState.dispatch( | ||
signal, | ||
view.state, | ||
@@ -854,2 +900,3 @@ view.dispatch, | ||
collabState.dispatch( | ||
signal, | ||
view.state, | ||
@@ -878,2 +925,3 @@ view.dispatch, | ||
collabState.dispatch( | ||
signal, | ||
view.state, | ||
@@ -893,2 +941,3 @@ view.dispatch, | ||
collabState.dispatch( | ||
signal, | ||
view.state, | ||
@@ -914,2 +963,3 @@ view.dispatch, | ||
collabState.dispatch( | ||
signal, | ||
view.state, | ||
@@ -924,2 +974,3 @@ view.dispatch, | ||
collabState.dispatch( | ||
signal, | ||
view.state, | ||
@@ -926,0 +977,0 @@ view.dispatch, |
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
171272
5051
+ Added@bangle.dev/collab-comms@0.30.0-alpha.14(transitive)
+ Added@bangle.dev/utils@0.30.0-alpha.14(transitive)
- Removed@bangle.dev/collab-comms@0.30.0-alpha.13(transitive)
- Removed@bangle.dev/utils@0.30.0-alpha.13(transitive)