@dra2020/baseclient 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/all/all.d.ts +18 -0
- package/dist/baseclient.js +9567 -0
- package/dist/baseclient.js.map +1 -0
- package/dist/context/all.d.ts +1 -0
- package/dist/context/context.d.ts +13 -0
- package/dist/filterexpr/all.d.ts +1 -0
- package/dist/filterexpr/filterexpr.d.ts +64 -0
- package/dist/fsm/all.d.ts +1 -0
- package/dist/fsm/fsm.d.ts +118 -0
- package/dist/logabstract/all.d.ts +1 -0
- package/dist/logabstract/log.d.ts +26 -0
- package/dist/logclient/all.d.ts +1 -0
- package/dist/logclient/log.d.ts +6 -0
- package/dist/ot-editutil/all.d.ts +2 -0
- package/dist/ot-editutil/oteditutil.d.ts +14 -0
- package/dist/ot-editutil/otmaputil.d.ts +21 -0
- package/dist/ot-js/all.d.ts +9 -0
- package/dist/ot-js/otarray.d.ts +111 -0
- package/dist/ot-js/otclientengine.d.ts +38 -0
- package/dist/ot-js/otcomposite.d.ts +37 -0
- package/dist/ot-js/otcounter.d.ts +17 -0
- package/dist/ot-js/otengine.d.ts +22 -0
- package/dist/ot-js/otmap.d.ts +19 -0
- package/dist/ot-js/otserverengine.d.ts +38 -0
- package/dist/ot-js/otsession.d.ts +111 -0
- package/dist/ot-js/ottypes.d.ts +29 -0
- package/dist/poly/all.d.ts +15 -0
- package/dist/poly/blend.d.ts +1 -0
- package/dist/poly/boundbox.d.ts +16 -0
- package/dist/poly/cartesian.d.ts +5 -0
- package/dist/poly/graham-scan.d.ts +8 -0
- package/dist/poly/hash.d.ts +1 -0
- package/dist/poly/matrix.d.ts +24 -0
- package/dist/poly/minbound.d.ts +1 -0
- package/dist/poly/poly.d.ts +52 -0
- package/dist/poly/polybin.d.ts +5 -0
- package/dist/poly/polylabel.d.ts +7 -0
- package/dist/poly/polypack.d.ts +30 -0
- package/dist/poly/polyround.d.ts +1 -0
- package/dist/poly/polysimplify.d.ts +1 -0
- package/dist/poly/quad.d.ts +48 -0
- package/dist/poly/selfintersect.d.ts +1 -0
- package/dist/poly/shamos.d.ts +1 -0
- package/dist/poly/simplify.d.ts +2 -0
- package/dist/poly/topo.d.ts +46 -0
- package/dist/poly/union.d.ts +48 -0
- package/dist/util/all.d.ts +5 -0
- package/dist/util/bintrie.d.ts +93 -0
- package/dist/util/countedhash.d.ts +19 -0
- package/dist/util/gradient.d.ts +15 -0
- package/dist/util/indexedarray.d.ts +15 -0
- package/dist/util/util.d.ts +68 -0
- package/docs/context.md +2 -0
- package/docs/fsm.md +243 -0
- package/docs/logabstract.md +2 -0
- package/docs/logclient.md +2 -0
- package/docs/ot-editutil.md +2 -0
- package/docs/ot-js.md +95 -0
- package/docs/poly.md +103 -0
- package/docs/util.md +2 -0
- package/lib/all/all.ts +19 -0
- package/lib/context/all.ts +1 -0
- package/lib/context/context.ts +82 -0
- package/lib/filterexpr/all.ts +1 -0
- package/lib/filterexpr/filterexpr.ts +625 -0
- package/lib/fsm/all.ts +1 -0
- package/lib/fsm/fsm.ts +549 -0
- package/lib/logabstract/all.ts +1 -0
- package/lib/logabstract/log.ts +55 -0
- package/lib/logclient/all.ts +1 -0
- package/lib/logclient/log.ts +105 -0
- package/lib/ot-editutil/all.ts +2 -0
- package/lib/ot-editutil/oteditutil.ts +180 -0
- package/lib/ot-editutil/otmaputil.ts +209 -0
- package/lib/ot-js/all.ts +9 -0
- package/lib/ot-js/otarray.ts +1168 -0
- package/lib/ot-js/otclientengine.ts +327 -0
- package/lib/ot-js/otcomposite.ts +247 -0
- package/lib/ot-js/otcounter.ts +145 -0
- package/lib/ot-js/otengine.ts +71 -0
- package/lib/ot-js/otmap.ts +144 -0
- package/lib/ot-js/otserverengine.ts +329 -0
- package/lib/ot-js/otsession.ts +199 -0
- package/lib/ot-js/ottypes.ts +98 -0
- package/lib/poly/all.ts +15 -0
- package/lib/poly/blend.ts +27 -0
- package/lib/poly/boundbox.ts +102 -0
- package/lib/poly/cartesian.ts +130 -0
- package/lib/poly/graham-scan.ts +401 -0
- package/lib/poly/hash.ts +15 -0
- package/lib/poly/matrix.ts +309 -0
- package/lib/poly/minbound.ts +211 -0
- package/lib/poly/poly.ts +767 -0
- package/lib/poly/polybin.ts +218 -0
- package/lib/poly/polylabel.ts +204 -0
- package/lib/poly/polypack.ts +458 -0
- package/lib/poly/polyround.ts +30 -0
- package/lib/poly/polysimplify.ts +24 -0
- package/lib/poly/quad.ts +272 -0
- package/lib/poly/selfintersect.ts +87 -0
- package/lib/poly/shamos.ts +297 -0
- package/lib/poly/simplify.ts +119 -0
- package/lib/poly/topo.ts +525 -0
- package/lib/poly/union.ts +371 -0
- package/lib/util/all.ts +5 -0
- package/lib/util/bintrie.ts +603 -0
- package/lib/util/countedhash.ts +83 -0
- package/lib/util/gradient.ts +108 -0
- package/lib/util/indexedarray.ts +80 -0
- package/lib/util/util.ts +695 -0
- package/package.json +52 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as LogAbstract from "../logabstract/all";
|
|
2
|
+
|
|
3
|
+
import * as OT from "./ottypes";
|
|
4
|
+
import * as OTC from "./otcomposite";
|
|
5
|
+
|
|
6
|
+
export interface IOTEngine
|
|
7
|
+
{
|
|
8
|
+
cid(): string;
|
|
9
|
+
rid(): string;
|
|
10
|
+
|
|
11
|
+
toPartialValue(resourceName: string): any;
|
|
12
|
+
|
|
13
|
+
startLocalEdit(): OTC.OTCompositeResource;
|
|
14
|
+
addLocalEdit(e: OTC.OTCompositeResource): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class OTEngine implements IOTEngine
|
|
18
|
+
{
|
|
19
|
+
ilog: LogAbstract.ILog;
|
|
20
|
+
onList: any;
|
|
21
|
+
|
|
22
|
+
// Constructor
|
|
23
|
+
constructor(ilog: LogAbstract.ILog)
|
|
24
|
+
{
|
|
25
|
+
this.ilog = ilog;
|
|
26
|
+
this.onList = {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
on(eventName: string, cb: any): void
|
|
30
|
+
{
|
|
31
|
+
let aCB: any = this.onList[eventName];
|
|
32
|
+
if (aCB === undefined)
|
|
33
|
+
{
|
|
34
|
+
aCB = [];
|
|
35
|
+
this.onList[eventName] = aCB;
|
|
36
|
+
}
|
|
37
|
+
aCB.push(cb);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
off(eventName: string, cb: any): void
|
|
41
|
+
{
|
|
42
|
+
let aCB: any = this.onList[eventName];
|
|
43
|
+
if (aCB !== undefined)
|
|
44
|
+
{
|
|
45
|
+
for (let i: number = 0; i < aCB.length; i++)
|
|
46
|
+
{
|
|
47
|
+
if (aCB[i] === cb)
|
|
48
|
+
{
|
|
49
|
+
aCB.splice(i, 1);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
emit(eventName: string): void
|
|
57
|
+
{
|
|
58
|
+
let aCB: any[] = this.onList[eventName];
|
|
59
|
+
if (aCB !== undefined)
|
|
60
|
+
for (let i: number = 0; i < aCB.length; i++)
|
|
61
|
+
(aCB[i])();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
cid(): string { return ''; }
|
|
65
|
+
rid(): string { return ''; }
|
|
66
|
+
|
|
67
|
+
toPartialValue(resourceName: string): any { return null; }
|
|
68
|
+
|
|
69
|
+
startLocalEdit(): OTC.OTCompositeResource { return null; }
|
|
70
|
+
addLocalEdit(e: OTC.OTCompositeResource): void { }
|
|
71
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import * as Util from '../util/all';
|
|
2
|
+
|
|
3
|
+
import * as OT from './ottypes';
|
|
4
|
+
|
|
5
|
+
// This implements OT for a dictionary of objects. OT is pretty trivial for maps - last wins.
|
|
6
|
+
|
|
7
|
+
export const OpMapSet = 1;
|
|
8
|
+
export const OpMapDel = 2;
|
|
9
|
+
export type MapEdit = [ number, string, any ]; // Op, Key, Value
|
|
10
|
+
|
|
11
|
+
export class OTMapResource extends OT.OTResourceBase
|
|
12
|
+
{
|
|
13
|
+
constructor(rid: string)
|
|
14
|
+
{
|
|
15
|
+
super(rid, 'map');
|
|
16
|
+
}
|
|
17
|
+
static factory(rid: string): OTMapResource { return new OTMapResource(rid); }
|
|
18
|
+
|
|
19
|
+
// Set a property
|
|
20
|
+
set(p: string, a: any): OTMapResource
|
|
21
|
+
{
|
|
22
|
+
this.edits.push([ OpMapSet, p, a ]);
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Delete a property
|
|
27
|
+
del(p: string): OTMapResource
|
|
28
|
+
{
|
|
29
|
+
this.edits.push([ OpMapDel, p, 0 ]);
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// copy an instance
|
|
34
|
+
copy(): OTMapResource
|
|
35
|
+
{
|
|
36
|
+
let c: OTMapResource = new OTMapResource(this.resourceName);
|
|
37
|
+
for (let i: number = 0; i < this.length; i++)
|
|
38
|
+
{
|
|
39
|
+
let e: MapEdit = this.edits[i];
|
|
40
|
+
c.edits.push([ e[0], e[1], e[2] ]);
|
|
41
|
+
}
|
|
42
|
+
return c;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Test whether two operations are effectively equivalent
|
|
46
|
+
effectivelyEqual(rhs: OTMapResource): boolean
|
|
47
|
+
{
|
|
48
|
+
// This should really be a structural error
|
|
49
|
+
if (this.length != rhs.length)
|
|
50
|
+
return false;
|
|
51
|
+
|
|
52
|
+
// This checks for exact structural equivalency. Really the ordering shouldn't matter for Map so
|
|
53
|
+
// an improvement to this algorithm would be to be more robust to ordering differences.
|
|
54
|
+
for (let i: number = 0; i < this.length; i++)
|
|
55
|
+
{
|
|
56
|
+
let e1: MapEdit = this.edits[i];
|
|
57
|
+
let e2: MapEdit = rhs.edits[i];
|
|
58
|
+
if (e1[0] != e2[0] || e1[1] != e2[1] || !Util.deepEqual(e1[2], e2[2]))
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Core OT algorithm for this type
|
|
65
|
+
transform(prior: OTMapResource, bPriorIsService: boolean): void
|
|
66
|
+
{
|
|
67
|
+
// Last wins - if I'm last, my sets and deletes are all preserved
|
|
68
|
+
if (bPriorIsService)
|
|
69
|
+
return;
|
|
70
|
+
|
|
71
|
+
// OK, remove any operations (either sets or deletes), that conflict with me
|
|
72
|
+
// First load in my properties
|
|
73
|
+
let myEdits: any = this.toObject();
|
|
74
|
+
|
|
75
|
+
// Now delete any that are overridden
|
|
76
|
+
for (let i: number = 0; i < prior.length; i++)
|
|
77
|
+
delete myEdits[(prior.edits[i])[1]];
|
|
78
|
+
|
|
79
|
+
// Now restore edit array from edited object
|
|
80
|
+
this.fromObject(myEdits);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// compose two edit actions
|
|
84
|
+
compose(rhs: OTMapResource): void // throws on error
|
|
85
|
+
{
|
|
86
|
+
let o: any = this.toObject();
|
|
87
|
+
for (let i: number = 0; i < rhs.length; i++)
|
|
88
|
+
{
|
|
89
|
+
let eR: MapEdit = rhs.edits[i];
|
|
90
|
+
o[eR[1]] = [ eR[0], eR[1], eR[2] ]; // Note this overwrites any existing operation on this key, set or del
|
|
91
|
+
}
|
|
92
|
+
this.fromObject(o);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
apply(startValue: any): any
|
|
96
|
+
{
|
|
97
|
+
if (startValue == null)
|
|
98
|
+
startValue = { };
|
|
99
|
+
for (let i: number = 0; i < this.length; i++)
|
|
100
|
+
{
|
|
101
|
+
let e: MapEdit = this.edits[i];
|
|
102
|
+
switch (e[0])
|
|
103
|
+
{
|
|
104
|
+
case OpMapSet:
|
|
105
|
+
startValue[e[1]] = e[2];
|
|
106
|
+
break;
|
|
107
|
+
case OpMapDel:
|
|
108
|
+
delete startValue[e[1]];
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return startValue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
minimize(): any
|
|
116
|
+
{
|
|
117
|
+
// Effectively removes OpMapDel
|
|
118
|
+
let o: any = this.apply({});
|
|
119
|
+
this.edits = [];
|
|
120
|
+
for (var p in o)
|
|
121
|
+
if (o.hasOwnProperty(p))
|
|
122
|
+
this.edits.push([ OpMapSet, p, o[p] ]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
loadObject(o: any): any
|
|
126
|
+
{
|
|
127
|
+
for (let i: number = 0; i < this.length; i++)
|
|
128
|
+
o[(this.edits[i])[1]] = this.edits[i];
|
|
129
|
+
return o;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
toObject(): any
|
|
133
|
+
{
|
|
134
|
+
return this.loadObject({ });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fromObject(o: any): void
|
|
138
|
+
{
|
|
139
|
+
this.edits = [];
|
|
140
|
+
for (var p in o)
|
|
141
|
+
if (o.hasOwnProperty(p))
|
|
142
|
+
this.edits.push(o[p]);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// Shared libraries
|
|
2
|
+
import * as LogAbstract from '../logabstract/all';
|
|
3
|
+
|
|
4
|
+
// Local libraries
|
|
5
|
+
import * as OT from "./ottypes";
|
|
6
|
+
import * as OTC from "./otcomposite";
|
|
7
|
+
import * as OTS from "./otsession";
|
|
8
|
+
import * as OTE from "./otengine";
|
|
9
|
+
|
|
10
|
+
export const ClientIDForServer: string = '-Server-';
|
|
11
|
+
|
|
12
|
+
export class OTServerEngine extends OTE.OTEngine
|
|
13
|
+
{
|
|
14
|
+
// Data members
|
|
15
|
+
stateServer: OTC.OTCompositeResource;
|
|
16
|
+
logServer: OTC.OTCompositeResource[];
|
|
17
|
+
valCache: any;
|
|
18
|
+
fullCache: any;
|
|
19
|
+
highSequence: any;
|
|
20
|
+
clientSequenceNo: number;
|
|
21
|
+
|
|
22
|
+
// Constructor
|
|
23
|
+
constructor(ilog: LogAbstract.ILog, rid: string)
|
|
24
|
+
{
|
|
25
|
+
super(ilog);
|
|
26
|
+
|
|
27
|
+
this.stateServer = new OTC.OTCompositeResource(rid, "");
|
|
28
|
+
this.logServer = [];
|
|
29
|
+
this.highSequence = {};
|
|
30
|
+
this.clientSequenceNo = 0;
|
|
31
|
+
this.valCache = {};
|
|
32
|
+
this.fullCache = null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
serverClock(): number
|
|
36
|
+
{
|
|
37
|
+
return this.stateServer.clock;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
rid(): string
|
|
41
|
+
{
|
|
42
|
+
return this.stateServer.resourceName;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
cid(): string
|
|
46
|
+
{
|
|
47
|
+
return ClientIDForServer;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
startLocalEdit(): OTC.OTCompositeResource
|
|
51
|
+
{
|
|
52
|
+
return new OTC.OTCompositeResource(this.rid(), this.cid());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
resetCaches(): void
|
|
56
|
+
{
|
|
57
|
+
this.valCache = {};
|
|
58
|
+
this.fullCache = null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
toPartialValue(resourceName: string): any
|
|
62
|
+
{
|
|
63
|
+
if (this.valCache[resourceName] === undefined)
|
|
64
|
+
this.valCache[resourceName] = this.stateServer.toPartialValue(resourceName);
|
|
65
|
+
return this.valCache[resourceName];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
toValue(): any
|
|
69
|
+
{
|
|
70
|
+
// In general, server does not require this but maybe for testing purposes
|
|
71
|
+
if (this.fullCache == null)
|
|
72
|
+
this.fullCache = this.stateServer.toValue();
|
|
73
|
+
return this.fullCache;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getProp(s: string): any
|
|
77
|
+
{
|
|
78
|
+
let o = this.toPartialValue('WellKnownName_meta');
|
|
79
|
+
return o == null ? '' : o[s];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getName(): string
|
|
83
|
+
{
|
|
84
|
+
return this.getProp('name');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getType(): string
|
|
88
|
+
{
|
|
89
|
+
return this.getProp('type');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
getDescription(): string
|
|
93
|
+
{
|
|
94
|
+
return this.getProp('description');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
getCreatedBy(): string
|
|
98
|
+
{
|
|
99
|
+
return this.getProp('createdby');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getCreateTime(): string
|
|
103
|
+
{
|
|
104
|
+
return this.getProp('createtime');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getCreatedByName(): string
|
|
108
|
+
{
|
|
109
|
+
let s: string = this.getCreatedBy();
|
|
110
|
+
if (s != '')
|
|
111
|
+
{
|
|
112
|
+
let users: any = this.toPartialValue('WellKnownName_users');
|
|
113
|
+
if (users && users[s] && users[s]['name'])
|
|
114
|
+
return users[s]['name'];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return '';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
hasSeenEvent(orig: OTC.OTCompositeResource): boolean
|
|
121
|
+
{
|
|
122
|
+
let clientSequenceNo: any = this.highSequence[orig.clientID];
|
|
123
|
+
let bSeen = (clientSequenceNo !== undefined && Number(clientSequenceNo) >= orig.clientSequenceNo);
|
|
124
|
+
return bSeen;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
isNextEvent(orig: OTC.OTCompositeResource): boolean
|
|
128
|
+
{
|
|
129
|
+
let nSeen: any = this.highSequence[orig.clientID];
|
|
130
|
+
let bNext = (nSeen === undefined && orig.clientSequenceNo == 0)
|
|
131
|
+
|| (Number(nSeen)+1 == orig.clientSequenceNo);
|
|
132
|
+
if (! bNext)
|
|
133
|
+
{
|
|
134
|
+
if (nSeen === undefined)
|
|
135
|
+
this.ilog.event( { event: 'OT anomaly: non-zero client seqNo for new client', sessionid: this.stateServer.resourceID } );
|
|
136
|
+
else
|
|
137
|
+
this.ilog.event( { event: 'OT anomaly: unexpected client seqNo', sessionid: this.stateServer.resourceID } );
|
|
138
|
+
}
|
|
139
|
+
return bNext;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
rememberSeenEvent(orig: OTC.OTCompositeResource): void
|
|
143
|
+
{
|
|
144
|
+
this.highSequence[orig.clientID] = orig.clientSequenceNo;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
forgetEvents(orig: OTC.OTCompositeResource): void
|
|
148
|
+
{
|
|
149
|
+
delete this.highSequence[orig.clientID];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
clientHighSequence(cid: string): number
|
|
153
|
+
{
|
|
154
|
+
let clientSequenceNo: any = this.highSequence[cid];
|
|
155
|
+
|
|
156
|
+
return clientSequenceNo === undefined ? 0 : Number(clientSequenceNo);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
garbageCollect(): void
|
|
160
|
+
{
|
|
161
|
+
let resources = this.toPartialValue('WellKnownName_resource');
|
|
162
|
+
if (this.stateServer.garbageCollect(resources))
|
|
163
|
+
{
|
|
164
|
+
this.resetCaches();
|
|
165
|
+
this.emit('state');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// TODO: Also remove entries from log to minimize memory use.
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Function: addServer
|
|
172
|
+
//
|
|
173
|
+
// Description:
|
|
174
|
+
// This is the server state update processing upon receiving an event from an endpoint.
|
|
175
|
+
// The received event is transformed (if possible) and added to the server state.
|
|
176
|
+
// The logic here is straight-forward - transform the incoming event so it is relative to
|
|
177
|
+
// the current state and then apply.
|
|
178
|
+
|
|
179
|
+
addServer(orig: OTC.OTCompositeResource): number
|
|
180
|
+
{
|
|
181
|
+
try
|
|
182
|
+
{
|
|
183
|
+
// First transform, then add to log
|
|
184
|
+
let i: number;
|
|
185
|
+
let a: OTC.OTCompositeResource = orig.copy();
|
|
186
|
+
|
|
187
|
+
for (i = this.logServer.length; i > 0; i--)
|
|
188
|
+
{
|
|
189
|
+
let aService: OTC.OTCompositeResource = this.logServer[i-1];
|
|
190
|
+
|
|
191
|
+
if (aService.clock == a.clock)
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Handle if we've seen it already (client did not receive ack, probably communication error on response)
|
|
196
|
+
if (this.hasSeenEvent(orig))
|
|
197
|
+
{
|
|
198
|
+
// Treat this as success if we still have event in log - the client did not receive an
|
|
199
|
+
// ack but is now re-connected and will get resynced correctly.
|
|
200
|
+
if (i > 0)
|
|
201
|
+
{
|
|
202
|
+
this.ilog.event({ sessionid: this.stateServer.resourceID, event: `addServer: duplicate event: no problem` });
|
|
203
|
+
//console.log('addServer: seen event - handling cleanly');
|
|
204
|
+
return OTS.ESuccess;
|
|
205
|
+
}
|
|
206
|
+
else
|
|
207
|
+
{
|
|
208
|
+
//console.log('addServer: seen event - resetting client');
|
|
209
|
+
this.ilog.event({ sessionid: this.stateServer.resourceID, event: `addServer: duplicate event: resetting client` });
|
|
210
|
+
this.forgetEvents(orig); // we are now resetting client in this case, so forget this client
|
|
211
|
+
return OTS.EClockSeen;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// If this isn't next in sequence, I lost one (probably because I went "back in time"
|
|
216
|
+
// due to server restart). In that case client is forced to re-initialize (losing local
|
|
217
|
+
// edits). I also need to re-initialize sequence numbering.
|
|
218
|
+
if (! this.isNextEvent(orig))
|
|
219
|
+
{
|
|
220
|
+
//console.log('addServer: received out-of-order (future) event');
|
|
221
|
+
this.ilog.event({ sessionid: this.stateServer.resourceID, event: `addServer: received out-of-order event` });
|
|
222
|
+
this.forgetEvents(orig);
|
|
223
|
+
return OTS.EClockAnomaly;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Fail if we have discarded that old state
|
|
227
|
+
if (a.clock >= 0 && i == 0)
|
|
228
|
+
{
|
|
229
|
+
this.ilog.event({ sessionid: this.stateServer.resourceID, event: `addServer: received old event` });
|
|
230
|
+
|
|
231
|
+
// This should really be ClockFailure which would force the client to resend with a newer
|
|
232
|
+
// clock value. But there appears to be a bug when session is reloaded that results in
|
|
233
|
+
// client never getting synced up. So for now force a reset (which might result in some
|
|
234
|
+
// client edits being discarded).
|
|
235
|
+
this.forgetEvents(orig);
|
|
236
|
+
return OTS.EClockReset;
|
|
237
|
+
//return OTS.EClockFailure;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// OK, all good, transform and apply
|
|
241
|
+
if (i < this.logServer.length)
|
|
242
|
+
{
|
|
243
|
+
let aPrior: OTC.OTCompositeResource = this.logServer[i].copy();
|
|
244
|
+
|
|
245
|
+
for (i++; i < this.logServer.length; i++)
|
|
246
|
+
aPrior.compose(this.logServer[i]);
|
|
247
|
+
|
|
248
|
+
a.transform(aPrior, true);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
a.clock = this.stateServer.clock + 1;
|
|
252
|
+
this.stateServer.compose(a);
|
|
253
|
+
this.resetCaches();
|
|
254
|
+
this.emit('state');
|
|
255
|
+
this.logServer.push(a.copy());
|
|
256
|
+
|
|
257
|
+
this.rememberSeenEvent(orig);
|
|
258
|
+
return OTS.ESuccess;
|
|
259
|
+
}
|
|
260
|
+
catch (err)
|
|
261
|
+
{
|
|
262
|
+
this.ilog.error('addServer: unexpected exception');
|
|
263
|
+
this.forgetEvents(orig);
|
|
264
|
+
return OTS.EClockReset;
|
|
265
|
+
//return OTS.EClockFailure;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
addLocalEdit(orig: OTC.OTCompositeResource): void
|
|
270
|
+
{
|
|
271
|
+
orig.clock = this.serverClock();
|
|
272
|
+
orig.clientSequenceNo = this.clientSequenceNo++;
|
|
273
|
+
let errno: number = this.addServer(orig);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
toJSON(): any
|
|
277
|
+
{
|
|
278
|
+
let log: any[] = [];
|
|
279
|
+
for (let i: number = 0; i < this.logServer.length; i++)
|
|
280
|
+
log.push(this.logServer[i].toJSON());
|
|
281
|
+
return { state: this.stateServer.toJSON(), highSequence: this.highSequence, log: log };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
validateLog(): void
|
|
285
|
+
{
|
|
286
|
+
// Yikes, invalid log created by bad revision reverting - validate on load and truncate if necessary
|
|
287
|
+
try
|
|
288
|
+
{
|
|
289
|
+
if (this.logServer.length > 0)
|
|
290
|
+
{
|
|
291
|
+
let aPrior: OTC.OTCompositeResource = this.logServer[0].copy();
|
|
292
|
+
|
|
293
|
+
for (let i: number = 1; i < this.logServer.length; i++)
|
|
294
|
+
aPrior.compose(this.logServer[i]);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (err)
|
|
298
|
+
{
|
|
299
|
+
this.ilog.event({ sessionid: this.stateServer.resourceID, event: `OTServer: corrupted log truncated` });
|
|
300
|
+
this.logServer = [];
|
|
301
|
+
this.logServer.push(this.stateServer.copy());
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
loadFromObject(o: any): void
|
|
306
|
+
{
|
|
307
|
+
if (o.state !== undefined)
|
|
308
|
+
{
|
|
309
|
+
this.stateServer = OTC.OTCompositeResource.constructFromObject(o.state);
|
|
310
|
+
this.logServer = [];
|
|
311
|
+
this.resetCaches();
|
|
312
|
+
this.emit('state');
|
|
313
|
+
}
|
|
314
|
+
if (o.log !== undefined)
|
|
315
|
+
{
|
|
316
|
+
for (let i: number = 0; i < o.log.length; i++)
|
|
317
|
+
this.logServer.push(OTC.OTCompositeResource.constructFromObject(o.log[i]));
|
|
318
|
+
this.validateLog();
|
|
319
|
+
}
|
|
320
|
+
else
|
|
321
|
+
{
|
|
322
|
+
this.logServer = [];
|
|
323
|
+
this.logServer.push(this.stateServer.copy());
|
|
324
|
+
}
|
|
325
|
+
if (o.highSequence !== undefined)
|
|
326
|
+
this.highSequence = o.highSequence;
|
|
327
|
+
this.clientSequenceNo = this.clientHighSequence(ClientIDForServer) + 1;
|
|
328
|
+
}
|
|
329
|
+
}
|