Skip to content

Commit face7ec

Browse files
committed
Fix revision being undefined
1 parent c50fea2 commit face7ec

File tree

3 files changed

+210
-8
lines changed

3 files changed

+210
-8
lines changed

.eslintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@
6969
{
7070
"exceptAfterSingleLine": true
7171
}
72-
]
72+
],
73+
"max-classes-per-file": "off"
7374
},
7475
"overrides": [
7576
{

packages/app/src/app/overmind/effects/live/clients.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Client } from 'ot';
1+
import { OTClient } from './ot/client';
22

33
export type SendOperation = (
44
moduleShortid: string,
5-
revision: string,
5+
revision: number,
66
operation: any
77
) => Promise<unknown>;
88

@@ -22,16 +22,15 @@ function operationToElixir(ot) {
2222
});
2323
}
2424

25-
class CodeSandboxOTClient extends Client {
25+
class CodeSandboxOTClient extends OTClient {
2626
moduleShortid: string;
27-
revision: number;
28-
onSendOperation: (revision: string, operation: any) => Promise<unknown>;
27+
onSendOperation: (revision: number, operation: any) => Promise<unknown>;
2928
onApplyOperation: (operation: any) => void;
3029

3130
constructor(
3231
revision: number,
3332
moduleShortid: string,
34-
onSendOperation: (revision: string, operation: any) => Promise<unknown>,
33+
onSendOperation: (revision: number, operation: any) => Promise<unknown>,
3534
onApplyOperation: (operation: any) => void
3635
) {
3736
super(revision);
@@ -57,7 +56,7 @@ class CodeSandboxOTClient extends Client {
5756
super.serverAck();
5857
} catch (e) {
5958
// Undo the revision increment again
60-
this.revision--;
59+
super.revision--;
6160
throw e;
6261
}
6362
}
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// translation of https://github.com/djspiewak/cccp/blob/master/agent/src/main/scala/com/codecommit/cccp/agent/state.scala
2+
/* eslint-disable react/no-access-state-in-setstate */
3+
4+
type Operation = any;
5+
6+
interface IState {
7+
applyClient(client: OTClient, operation: Operation): IState;
8+
applyServer(client: OTClient, operation: Operation): IState;
9+
serverAck(client: OTClient): IState;
10+
transformSelection<T>(selection: T): T;
11+
resend?(client: OTClient): void;
12+
}
13+
14+
// In the 'Synchronized' state, there is no pending operation that the client
15+
// has sent to the server.
16+
class Synchronized implements IState {
17+
applyClient(client: OTClient, operation: Operation) {
18+
// When the user makes an edit, send the operation to the server and
19+
// switch to the 'AwaitingConfirm' state
20+
client.sendOperation(client.revision, operation);
21+
return new AwaitingConfirm(operation);
22+
}
23+
24+
applyServer(client: OTClient, operation: Operation) {
25+
// When we receive a new operation from the server, the operation can be
26+
// simply applied to the current document
27+
client.applyOperation(operation);
28+
return this;
29+
}
30+
31+
serverAck(client: OTClient): IState {
32+
throw new Error('There is no pending operation.');
33+
}
34+
35+
// Nothing to do because the latest server state and client state are the same.
36+
transformSelection(x) {
37+
return x;
38+
}
39+
}
40+
41+
// Singleton
42+
const synchronized_ = new Synchronized();
43+
44+
// In the 'AwaitingConfirm' state, there's one operation the client has sent
45+
// to the server and is still waiting for an acknowledgement.
46+
class AwaitingConfirm implements IState {
47+
outstanding: Operation;
48+
49+
constructor(outstanding: Operation) {
50+
// Save the pending operation
51+
this.outstanding = outstanding;
52+
}
53+
54+
applyClient(client: OTClient, operation: Operation) {
55+
// When the user makes an edit, don't send the operation immediately,
56+
// instead switch to 'AwaitingWithBuffer' state
57+
return new AwaitingWithBuffer(this.outstanding, operation);
58+
}
59+
60+
applyServer(client: OTClient, operation: Operation) {
61+
// This is another client's operation. Visualization:
62+
//
63+
// /\
64+
// this.outstanding / \ operation
65+
// / \
66+
// \ /
67+
// pair[1] \ / pair[0] (new outstanding)
68+
// (can be applied \/
69+
// to the client's
70+
// current document)
71+
const pair = operation.constructor.transform(this.outstanding, operation);
72+
client.applyOperation(pair[1]);
73+
return new AwaitingConfirm(pair[0]);
74+
}
75+
76+
serverAck(client: OTClient) {
77+
// The client's operation has been acknowledged
78+
// => switch to synchronized state
79+
return synchronized_;
80+
}
81+
82+
transformSelection(selection: any) {
83+
return selection.transform(this.outstanding);
84+
}
85+
86+
resend(client: OTClient) {
87+
// The confirm didn't come because the client was disconnected.
88+
// Now that it has reconnected, we resend the outstanding operation.
89+
client.sendOperation(client.revision, this.outstanding);
90+
}
91+
}
92+
93+
// In the 'AwaitingWithBuffer' state, the client is waiting for an operation
94+
// to be acknowledged by the server while buffering the edits the user makes
95+
class AwaitingWithBuffer implements IState {
96+
outstanding: Operation;
97+
buffer: Operation;
98+
99+
constructor(outstanding: Operation, buffer: Operation) {
100+
// Save the pending operation and the user's edits since then
101+
this.outstanding = outstanding;
102+
this.buffer = buffer;
103+
}
104+
105+
applyClient(client: OTClient, operation: Operation) {
106+
// Compose the user's changes onto the buffer
107+
const newBuffer = this.buffer.compose(operation);
108+
return new AwaitingWithBuffer(this.outstanding, newBuffer);
109+
}
110+
111+
applyServer(client: OTClient, operation: Operation) {
112+
// Operation comes from another client
113+
//
114+
// /\
115+
// this.outstanding / \ operation
116+
// / \
117+
// /\ /
118+
// this.buffer / \* / pair1[0] (new outstanding)
119+
// / \/
120+
// \ /
121+
// pair2[1] \ / pair2[0] (new buffer)
122+
// the transformed \/
123+
// operation -- can
124+
// be applied to the
125+
// client's current
126+
// document
127+
//
128+
// * pair1[1]
129+
const transform = operation.constructor.transform;
130+
const pair1 = transform(this.outstanding, operation);
131+
const pair2 = transform(this.buffer, pair1[1]);
132+
client.applyOperation(pair2[1]);
133+
return new AwaitingWithBuffer(pair1[0], pair2[0]);
134+
}
135+
136+
serverAck(client: OTClient) {
137+
// The pending operation has been acknowledged
138+
// => send buffer
139+
client.sendOperation(client.revision, this.buffer);
140+
return new AwaitingConfirm(this.buffer);
141+
}
142+
143+
transformSelection(selection: any) {
144+
return selection.transform(this.outstanding).transform(this.buffer);
145+
}
146+
147+
resend(client: OTClient) {
148+
// The confirm didn't come because the client was disconnected.
149+
// Now that it has reconnected, we resend the outstanding operation.
150+
client.sendOperation(client.revision, this.outstanding);
151+
}
152+
}
153+
154+
export abstract class OTClient {
155+
revision: number;
156+
state: IState;
157+
158+
constructor(revision: number) {
159+
this.revision = revision;
160+
this.state = synchronized_;
161+
}
162+
163+
setState(state: IState) {
164+
this.state = state;
165+
}
166+
167+
// Call this method when the user changes the document.
168+
applyClient(operation: Operation) {
169+
this.setState(this.state.applyClient(this, operation));
170+
}
171+
172+
// Call this method with a new operation from the server
173+
applyServer(operation: Operation) {
174+
this.revision++;
175+
this.setState(this.state.applyServer(this, operation));
176+
}
177+
178+
serverAck() {
179+
this.revision++;
180+
this.setState(this.state.serverAck(this));
181+
}
182+
183+
serverReconnect() {
184+
if (typeof this.state.resend === 'function') {
185+
this.state.resend(this);
186+
}
187+
}
188+
189+
// Transforms a selection from the latest known server state to the current
190+
// client state. For example, if we get from the server the information that
191+
// another user's cursor is at position 3, but the server hasn't yet received
192+
// our newest operation, an insertion of 5 characters at the beginning of the
193+
// document, the correct position of the other user's cursor in our current
194+
// document is 8.
195+
transformSelection(selection) {
196+
return this.state.transformSelection(selection);
197+
}
198+
199+
abstract sendOperation(revision: number, operation: Operation): void;
200+
201+
abstract applyOperation(operation: Operation): void;
202+
}

0 commit comments

Comments
 (0)