Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@bangle.dev/collab-client

Package Overview
Dependencies
Maintainers
1
Versions
112
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bangle.dev/collab-client - npm Package Compare versions

Comparing version 0.30.0-alpha.13 to 0.30.0-alpha.14

177

__tests__/collab-client.test.ts

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc