Compare commits

..

1 Commits

Author SHA1 Message Date
Kevin Jahns
013fee2421 v13.0.0-7 -- distribution files 2017-07-22 01:17:21 +02:00
32 changed files with 1849 additions and 3615 deletions

View File

@@ -22,7 +22,6 @@ is a list of the modules we know of:
|[webrtc](https://github.com/y-js/y-webrtc) | Propagate updates Browser2Browser via WebRTC| |[webrtc](https://github.com/y-js/y-webrtc) | Propagate updates Browser2Browser via WebRTC|
|[websockets](https://github.com/y-js/y-websockets-client) | Set up [a central server](https://github.com/y-js/y-websockets-client), and connect to it via websockets | |[websockets](https://github.com/y-js/y-websockets-client) | Set up [a central server](https://github.com/y-js/y-websockets-client), and connect to it via websockets |
|[xmpp](https://github.com/y-js/y-xmpp) | Propagate updates in a XMPP multi-user-chat room ([XEP-0045](http://xmpp.org/extensions/xep-0045.html))| |[xmpp](https://github.com/y-js/y-xmpp) | Propagate updates in a XMPP multi-user-chat room ([XEP-0045](http://xmpp.org/extensions/xep-0045.html))|
|[ipfs](https://github.com/ipfs-labs/y-ipfs-connector) | Connector for the [Interplanetary File System](https://ipfs.io/)!|
|[test](https://github.com/y-js/y-test) | A Connector for testing purposes. It is designed to simulate delays that happen in worst case scenarios| |[test](https://github.com/y-js/y-test) | A Connector for testing purposes. It is designed to simulate delays that happen in worst case scenarios|
##### Database adapters ##### Database adapters

View File

@@ -9,6 +9,16 @@
"license": "MIT", "license": "MIT",
"ignore": [], "ignore": [],
"dependencies": { "dependencies": {
"yjs": "latest",
"y-array": "latest",
"y-map": "latest",
"y-memory": "latest",
"y-richtext": "latest",
"y-webrtc": "latest",
"y-websockets-client": "latest",
"y-text": "latest",
"y-indexeddb": "latest",
"y-xml": "latest",
"quill": "^1.0.0-rc.2", "quill": "^1.0.0-rc.2",
"ace": "~1.2.3", "ace": "~1.2.3",
"ace-builds": "~1.2.3", "ace-builds": "~1.2.3",

View File

@@ -12,12 +12,7 @@
<input name="message" type="text" style="width:60%;"> <input name="message" type="text" style="width:60%;">
<input type="submit" value="Send"> <input type="submit" value="Send">
</form> </form>
<script src="../../y.js"></script> <script src="../bower_components/yjs/y.js"></script>
<script src="../../../y-array/y-array.js"></script>
<script src="../../../y-map/dist/y-map.js"></script>
<script src="../../../y-text/dist/y-text.js"></script>
<script src="../../../y-memory/y-memory.js"></script>
<script src="../../../y-websockets-client/dist/y-websockets-client.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>
</html> </html>

View File

@@ -12,11 +12,7 @@
</style> </style>
<button type="button" id="clearDrawingCanvas">Clear Drawing</button> <button type="button" id="clearDrawingCanvas">Clear Drawing</button>
<svg id="drawingCanvas" viewbox="0 0 100 100" width="100%"></svg> <svg id="drawingCanvas" viewbox="0 0 100 100" width="100%"></svg>
<script src="../../y.js"></script> <script src="../bower_components/yjs/y.js"></script>
<script src="../../../y-array/y-array.js"></script>
<script src="../../../y-map/dist/y-map.js"></script>
<script src="../../../y-memory/y-memory.js"></script>
<script src="../../../y-websockets-client/y-websockets-client.js"></script>
<script src="../bower_components/d3/d3.js"></script> <script src="../bower_components/d3/d3.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>

View File

@@ -7,8 +7,8 @@ Y({
}, },
connector: { connector: {
name: 'websockets-client', name: 'websockets-client',
room: 'drawing-example', room: 'drawing-example'
url: 'localhost:1234' // url: 'localhost:1234'
}, },
sourceDir: '/bower_components', sourceDir: '/bower_components',
share: { share: {

View File

@@ -16,10 +16,7 @@
<path d="M 325.63989,452.69678 C 317.92163,423.11035 287.36353,445.9126 268.64673,447.82227 C 250.27417,449.69727 239.77563,436.57373 239.77563,419.32568 C 239.77563,402.07812 254.02368,395.70361 265.27222,394.5791 C 276.52075,393.4541 282.14526,396.45361 297.89331,407.32715 C 313.64136,418.20117 326.3894,399.45313 327.8894,392.7041 C 329.3894,385.95508 320.76538,342.83545 320.01538,330.46191 C 319.75855,326.2207 320.56372,321.21484 321.58911,315.6377 C 315.92114,317.13525 309.58862,318.82715 302.39282,320.71338 C 263.77222,330.83691 232.27661,318.46338 221.40259,308.71484 C 210.52905,298.96582 229.27661,284.71777 234.90112,279.09326 C 240.52563,273.46924 243.90015,262.59521 239.77563,247.22217 C 235.65112,231.84912 201.90503,222.10059 179.03296,239.34814 C 156.16089,256.59619 180.90796,285.09277 188.40698,293.3418 C 195.90601,301.59033 186.15698,312.08936 170.03442,313.58887 C 160.60962,314.46582 119.15894,313.67676 86.340576,312.87012 C 85.573975,347.74561 84.581299,386.15088 83.794922,402.07812 C 82.295166,432.44922 109.29175,422.32568 115.66577,420.82568 C 122.04028,419.32568 126.16479,409.57715 143.03735,408.45215 C 185.9231,405.59326 186.09985,466.69629 144.16235,467.69482 C 128.41431,468.06982 113.79126,451.19678 108.16675,447.44727 C 102.54272,443.69775 87.919433,442.94775 83.794922,457.9458 C 82.01709,464.41113 78.118652,481.65137 78.098144,496.18994 C 78.071045,515.38037 82.295166,531.81201 82.295166,531.81201 C 82.295166,531.81201 105.54224,526.5625 149.41187,526.5625 C 193.28149,526.5625 199.65552,547.93506 194.78101,558.80859 C 189.90649,569.68213 181.28296,568.93213 179.40796,583.18066 C 172.7063,634.11133 253.34106,631.08203 249.14917,584.68018 C 247.96948,571.62354 237.16528,571.66699 232.27661,557.68359 C 222.17944,528.80273 244.64966,523.56299 257.39819,524.68799 C 263.59351,525.23437 290.95679,529.73389 320.75757,531.21582 C 321.22437,531.23877 321.69312,531.25928 322.16089,531.28125 C 326.09985,518.06592 329.95825,503.87646 330.1394,498.44092 C 330.51392,487.19238 330.1394,469.94434 325.63989,452.69678 z " style="fill:#d3ea9d;stroke:#000000" id="path2508"/> <path d="M 325.63989,452.69678 C 317.92163,423.11035 287.36353,445.9126 268.64673,447.82227 C 250.27417,449.69727 239.77563,436.57373 239.77563,419.32568 C 239.77563,402.07812 254.02368,395.70361 265.27222,394.5791 C 276.52075,393.4541 282.14526,396.45361 297.89331,407.32715 C 313.64136,418.20117 326.3894,399.45313 327.8894,392.7041 C 329.3894,385.95508 320.76538,342.83545 320.01538,330.46191 C 319.75855,326.2207 320.56372,321.21484 321.58911,315.6377 C 315.92114,317.13525 309.58862,318.82715 302.39282,320.71338 C 263.77222,330.83691 232.27661,318.46338 221.40259,308.71484 C 210.52905,298.96582 229.27661,284.71777 234.90112,279.09326 C 240.52563,273.46924 243.90015,262.59521 239.77563,247.22217 C 235.65112,231.84912 201.90503,222.10059 179.03296,239.34814 C 156.16089,256.59619 180.90796,285.09277 188.40698,293.3418 C 195.90601,301.59033 186.15698,312.08936 170.03442,313.58887 C 160.60962,314.46582 119.15894,313.67676 86.340576,312.87012 C 85.573975,347.74561 84.581299,386.15088 83.794922,402.07812 C 82.295166,432.44922 109.29175,422.32568 115.66577,420.82568 C 122.04028,419.32568 126.16479,409.57715 143.03735,408.45215 C 185.9231,405.59326 186.09985,466.69629 144.16235,467.69482 C 128.41431,468.06982 113.79126,451.19678 108.16675,447.44727 C 102.54272,443.69775 87.919433,442.94775 83.794922,457.9458 C 82.01709,464.41113 78.118652,481.65137 78.098144,496.18994 C 78.071045,515.38037 82.295166,531.81201 82.295166,531.81201 C 82.295166,531.81201 105.54224,526.5625 149.41187,526.5625 C 193.28149,526.5625 199.65552,547.93506 194.78101,558.80859 C 189.90649,569.68213 181.28296,568.93213 179.40796,583.18066 C 172.7063,634.11133 253.34106,631.08203 249.14917,584.68018 C 247.96948,571.62354 237.16528,571.66699 232.27661,557.68359 C 222.17944,528.80273 244.64966,523.56299 257.39819,524.68799 C 263.59351,525.23437 290.95679,529.73389 320.75757,531.21582 C 321.22437,531.23877 321.69312,531.25928 322.16089,531.28125 C 326.09985,518.06592 329.95825,503.87646 330.1394,498.44092 C 330.51392,487.19238 330.1394,469.94434 325.63989,452.69678 z " style="fill:#d3ea9d;stroke:#000000" id="path2508"/>
</g> </g>
</svg> </svg>
<script src="../../y.js"></script> <script src="../bower_components/yjs/y.js"></script>
<script src="../../../y-map/dist/y-map.js"></script>
<script src="../../../y-memory/y-memory.js"></script>
<script src="../../../y-websockets-client/y-websockets-client.js"></script>
<script src="../bower_components/d3/d3.js"></script> <script src="../bower_components/d3/d3.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>

View File

@@ -8,9 +8,9 @@ Y({
}, },
connector: { connector: {
name: 'websockets-client', name: 'websockets-client',
room: 'Puzzle-example', room: 'Puzzle-example'
url: 'http://localhost:1234'
}, },
sourceDir: '/bower_components',
share: { share: {
piece1: 'Map', piece1: 'Map',
piece2: 'Map', piece2: 'Map',

View File

@@ -4,8 +4,8 @@
<!-- quill does not include dist files! We are using the hosted version instead --> <!-- quill does not include dist files! We are using the hosted version instead -->
<!--link rel="stylesheet" href="../bower_components/quill/dist/quill.snow.css" /--> <!--link rel="stylesheet" href="../bower_components/quill/dist/quill.snow.css" /-->
<link href="https://cdn.quilljs.com/1.0.4/quill.snow.css" rel="stylesheet"> <link href="https://cdn.quilljs.com/1.0.4/quill.snow.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css" rel="stylesheet"> <link href="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/styles/monokai-sublime.min.css" rel="stylesheet"> <link href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/styles/monokai-sublime.min.css" rel="stylesheet">
<style> <style>
#quill-container { #quill-container {
border: 1px solid gray; border: 1px solid gray;
@@ -19,17 +19,13 @@
</div> </div>
</div> </div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.js" type="text/javascript"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/highlight.min.js" type="text/javascript"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.2.0/highlight.min.js" type="text/javascript"></script>
<script src="https://cdn.quilljs.com/1.0.4/quill.js"></script> <script src="https://cdn.quilljs.com/1.0.4/quill.js"></script>
<!-- quill does not include dist files! We are using the hosted version instead (see above) <!-- quill does not include dist files! We are using the hosted version instead (see above)
<script src="../bower_components/quill/dist/quill.js"></script> <script src="../bower_components/quill/dist/quill.js"></script>
--> -->
<script src="../../y.js"></script> <script src="../bower_components/yjs/y.js"></script>
<script src="../../../y-array/y-array.js"></script>
<script src="../../../y-richtext/dist/y-richtext.js"></script>
<script src="../../../y-memory/y-memory.js"></script>
<script src="../../../y-websockets-client/y-websockets-client.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>
</html> </html>

View File

@@ -8,8 +8,7 @@ Y({
}, },
connector: { connector: {
name: 'websockets-client', name: 'websockets-client',
room: 'richtext-example-quill-1.0-test', room: 'richtext-example-quill-1.0-test'
url: 'http://localhost:1234'
}, },
sourceDir: '/bower_components', sourceDir: '/bower_components',
share: { share: {

View File

@@ -7,13 +7,12 @@ Y({
}, },
connector: { connector: {
name: 'websockets-client', name: 'websockets-client',
room: 'Textarea-example', room: 'Textarea-example-dev'
url: 'http://127.0.0.1:1234' // url: '127.0.0.1:1234'
}, },
sourceDir: '/bower_components', sourceDir: '/bower_components',
share: { share: {
textarea: 'Text', // y.share.textarea is of type Y.Text textarea: 'Text' // y.share.textarea is of type Y.Text
test: 'Array'
} }
}).then(function (y) { }).then(function (y) {
window.yTextarea = y window.yTextarea = y

592
package-lock.json generated
View File

@@ -3,12 +3,6 @@
"version": "13.0.0-7", "version": "13.0.0-7",
"lockfileVersion": 1, "lockfileVersion": 1,
"dependencies": { "dependencies": {
"accepts": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
"integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=",
"dev": true
},
"acorn": { "acorn": {
"version": "4.0.13", "version": "4.0.13",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
@@ -69,19 +63,8 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz",
"integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=", "integrity": "sha1-o+Uvo5FoyCX/V7AkgSbOWo/5VQc=",
"dev": true "dev": true,
}, "optional": true
"apache-crypt": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.1.tgz",
"integrity": "sha1-1vxyqm0n2ZyVqU/RiNcx7v/6Zjw=",
"dev": true
},
"apache-md5": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.2.tgz",
"integrity": "sha1-7klza2ObTxCLbp5ibG2pkwa0FpI=",
"dev": true
}, },
"argparse": { "argparse": {
"version": "1.0.9", "version": "1.0.9",
@@ -107,12 +90,6 @@
"integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
"dev": true "dev": true
}, },
"array-find-index": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
"integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
"dev": true
},
"array-union": { "array-union": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@@ -153,7 +130,8 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
"integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
"dev": true "dev": true,
"optional": true
}, },
"babel-cli": { "babel-cli": {
"version": "6.24.1", "version": "6.24.1",
@@ -521,29 +499,12 @@
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true "dev": true
}, },
"basic-auth": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz",
"integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=",
"dev": true
},
"batch": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
"integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
"dev": true
},
"bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=",
"dev": true
},
"binary-extensions": { "binary-extensions": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.8.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.8.0.tgz",
"integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=", "integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=",
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.8", "version": "1.1.8",
@@ -595,20 +556,6 @@
"integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=",
"dev": true "dev": true
}, },
"camelcase-keys": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
"dev": true,
"dependencies": {
"camelcase": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
"integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
"dev": true
}
}
},
"center-align": { "center-align": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
@@ -639,7 +586,8 @@
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
"integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
"dev": true "dev": true,
"optional": true
}, },
"circular-json": { "circular-json": {
"version": "0.3.1", "version": "0.3.1",
@@ -677,12 +625,6 @@
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true "dev": true
}, },
"colors": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
"dev": true
},
"commander": { "commander": {
"version": "2.10.0", "version": "2.10.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.10.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-2.10.0.tgz",
@@ -759,26 +701,6 @@
} }
} }
}, },
"connect": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/connect/-/connect-3.5.1.tgz",
"integrity": "sha1-bTDXpjx/FwhXprOqazY9lz3KWI4=",
"dev": true,
"dependencies": {
"debug": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
"dev": true
},
"ms": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
"dev": true
}
}
},
"contains-path": { "contains-path": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
@@ -803,24 +725,6 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true "dev": true
}, },
"cors": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.3.tgz",
"integrity": "sha1-TPeOHSMymnSWsvwiJbd8pbteuAI=",
"dev": true
},
"currently-unhandled": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
"integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
"dev": true
},
"cutest": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/cutest/-/cutest-0.1.9.tgz",
"integrity": "sha512-bRyVi9vWknRWw+wIx0hhsCJKnsvRsB3Jmssl0zlFrKyqrYeBPpMKoZItpl7nziZi9ZqrgYoGo21fWKvnJIo8Dw==",
"dev": true
},
"d": { "d": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
@@ -874,18 +778,6 @@
"integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
"dev": true "dev": true
}, },
"depd": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz",
"integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=",
"dev": true
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
"dev": true
},
"detect-indent": { "detect-indent": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
@@ -898,36 +790,12 @@
"integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=",
"dev": true "dev": true
}, },
"duplexer": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
"integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
"dev": true
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
"dev": true
},
"encodeurl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz",
"integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=",
"dev": true
},
"error-ex": { "error-ex": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
"integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
"dev": true "dev": true
}, },
"error-stack-parser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.1.tgz",
"integrity": "sha1-oyArj7AxFKqbQKDjZp5IsrZaAQo=",
"dev": true
},
"es-abstract": { "es-abstract": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.7.0.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.7.0.tgz",
@@ -976,12 +844,6 @@
"integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
"dev": true "dev": true
}, },
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
"dev": true
},
"escape-string-regexp": { "escape-string-regexp": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -1128,24 +990,12 @@
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true "dev": true
}, },
"etag": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz",
"integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=",
"dev": true
},
"event-emitter": { "event-emitter": {
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
"integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
"dev": true "dev": true
}, },
"event-stream": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
"integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
"dev": true
},
"exit-hook": { "exit-hook": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
@@ -1194,12 +1044,6 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true "dev": true
}, },
"faye-websocket": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz",
"integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=",
"dev": true
},
"figures": { "figures": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
@@ -1224,26 +1068,6 @@
"integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=",
"dev": true "dev": true
}, },
"finalhandler": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.5.1.tgz",
"integrity": "sha1-LEANjUUwk1vCMlScX6OF7Afeb80=",
"dev": true,
"dependencies": {
"debug": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
"dev": true
},
"ms": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
"dev": true
}
}
},
"find-root": { "find-root": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
@@ -1280,18 +1104,6 @@
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=",
"dev": true "dev": true
}, },
"fresh": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz",
"integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=",
"dev": true
},
"from": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
"integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=",
"dev": true
},
"fs-exists-sync": { "fs-exists-sync": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz",
@@ -2081,24 +1893,6 @@
"integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=",
"dev": true "dev": true
}, },
"hosted-git-info": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz",
"integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==",
"dev": true
},
"http-auth": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/http-auth/-/http-auth-3.1.3.tgz",
"integrity": "sha1-lFz63WZSHq+PfISRPTd9exXyTjE=",
"dev": true
},
"http-errors": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz",
"integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=",
"dev": true
},
"ignore": { "ignore": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz",
@@ -2111,12 +1905,6 @@
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true "dev": true
}, },
"indent-string": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
"integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
"dev": true
},
"inflight": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -2163,7 +1951,8 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
"integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
"dev": true "dev": true,
"optional": true
}, },
"is-buffer": { "is-buffer": {
"version": "1.1.5", "version": "1.1.5",
@@ -2171,12 +1960,6 @@
"integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=",
"dev": true "dev": true
}, },
"is-builtin-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
"integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
"dev": true
},
"is-callable": { "is-callable": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
@@ -2303,12 +2086,6 @@
"integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=",
"dev": true "dev": true
}, },
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
"dev": true
},
"is-valid-glob": { "is-valid-glob": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz", "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz",
@@ -2321,12 +2098,6 @@
"integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=",
"dev": true "dev": true
}, },
"is-wsl": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
"integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
"dev": true
},
"isarray": { "isarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -2411,20 +2182,6 @@
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
"dev": true "dev": true
}, },
"live-server": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/live-server/-/live-server-1.2.0.tgz",
"integrity": "sha1-RJhkS7+Bpm8Y3Y3/3vYcTBw3TKM=",
"dev": true,
"dependencies": {
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
}
}
},
"load-json-file": { "load-json-file": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
@@ -2469,74 +2226,24 @@
"integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
"dev": true "dev": true
}, },
"loud-rejection": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
"integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
"dev": true
},
"magic-string": { "magic-string": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.19.1.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.19.1.tgz",
"integrity": "sha1-FNdoATyvLsj96hakmvgvw3fnUgE=", "integrity": "sha1-FNdoATyvLsj96hakmvgvw3fnUgE=",
"dev": true "dev": true
}, },
"map-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
"integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
"dev": true
},
"map-stream": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
"integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=",
"dev": true
},
"matched": { "matched": {
"version": "0.4.4", "version": "0.4.4",
"resolved": "https://registry.npmjs.org/matched/-/matched-0.4.4.tgz", "resolved": "https://registry.npmjs.org/matched/-/matched-0.4.4.tgz",
"integrity": "sha1-Vte36xgDPwz5vFLrIJD6x9weifo=", "integrity": "sha1-Vte36xgDPwz5vFLrIJD6x9weifo=",
"dev": true "dev": true
}, },
"meow": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
"dev": true,
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
}
}
},
"micromatch": { "micromatch": {
"version": "2.3.11", "version": "2.3.11",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
"integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
"dev": true "dev": true
}, },
"mime": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz",
"integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=",
"dev": true
},
"mime-db": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
"integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=",
"dev": true
},
"mime-types": {
"version": "2.1.15",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
"integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=",
"dev": true
},
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@@ -2555,12 +2262,6 @@
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true "dev": true
}, },
"morgan": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.8.2.tgz",
"integrity": "sha1-eErHc05KRTqcbm6GgKkyknXItoc=",
"dev": true
},
"ms": { "ms": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -2585,18 +2286,6 @@
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
"dev": true "dev": true
}, },
"negotiator": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
"dev": true
},
"normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
"integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
"dev": true
},
"normalize-path": { "normalize-path": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
@@ -2633,18 +2322,6 @@
"integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
"dev": true "dev": true
}, },
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
"dev": true
},
"on-headers": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz",
"integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=",
"dev": true
},
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -2657,12 +2334,6 @@
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"dev": true "dev": true
}, },
"opn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-5.1.0.tgz",
"integrity": "sha512-iPNl7SyM8L30Rm1sjGdLLheyHVw5YXVfi3SKWJzBI7efxRwHojfRFjwE/OLM6qp9xJYMgab8WicTU1cPoY+Hpg==",
"dev": true
},
"optionator": { "optionator": {
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
@@ -2725,12 +2396,6 @@
"integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
"dev": true "dev": true
}, },
"parseurl": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz",
"integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=",
"dev": true
},
"path-exists": { "path-exists": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
@@ -2755,18 +2420,6 @@
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
"dev": true "dev": true
}, },
"path-type": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
"integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
"dev": true
},
"pause-stream": {
"version": "0.0.11",
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
"integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
"dev": true
},
"pify": { "pify": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@@ -2853,12 +2506,6 @@
"integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
"dev": true "dev": true
}, },
"proxy-middleware": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz",
"integrity": "sha1-o/3xvvtzD5UZZYcqwvYHTGFHelY=",
"dev": true
},
"randomatic": { "randomatic": {
"version": "1.1.7", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
@@ -2887,38 +2534,6 @@
} }
} }
}, },
"range-parser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=",
"dev": true
},
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
"integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
"dev": true,
"dependencies": {
"load-json-file": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true
},
"strip-bom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
"dev": true
}
}
},
"read-pkg-up": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
"integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
"dev": true
},
"readable-stream": { "readable-stream": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
@@ -2929,7 +2544,8 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz",
"integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=",
"dev": true "dev": true,
"optional": true
}, },
"readline2": { "readline2": {
"version": "1.0.1", "version": "1.0.1",
@@ -2943,12 +2559,6 @@
"integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
"dev": true "dev": true
}, },
"redent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
"integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
"dev": true
},
"regenerate": { "regenerate": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.2.tgz", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.2.tgz",
@@ -3183,26 +2793,6 @@
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
"dev": true "dev": true
}, },
"send": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz",
"integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=",
"dev": true,
"dependencies": {
"debug": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
"integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=",
"dev": true
}
}
},
"serve-index": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.0.tgz",
"integrity": "sha1-0rKA/FYNYW7oG0i/D6gqvtJIXOc=",
"dev": true
},
"set-getter": { "set-getter": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz",
@@ -3213,13 +2803,8 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
"integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=",
"dev": true "dev": true,
}, "optional": true
"setprototypeof": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
"integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=",
"dev": true
}, },
"shelljs": { "shelljs": {
"version": "0.7.8", "version": "0.7.8",
@@ -3227,12 +2812,6 @@
"integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=",
"dev": true "dev": true
}, },
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true
},
"slash": { "slash": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
@@ -3263,60 +2842,12 @@
"integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=",
"dev": true "dev": true
}, },
"spdx-correct": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
"integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=",
"dev": true
},
"spdx-expression-parse": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz",
"integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=",
"dev": true
},
"spdx-license-ids": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz",
"integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=",
"dev": true
},
"split": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
"integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=",
"dev": true
},
"sprintf-js": { "sprintf-js": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true "dev": true
}, },
"stack-generator": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.1.tgz",
"integrity": "sha1-s32LDZoqblLAbMjhhfmPGZ+2OAQ=",
"dev": true
},
"stackframe": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.0.3.tgz",
"integrity": "sha1-/mSrILFw5M5JBEsSbBGd+g5dx8w=",
"dev": true
},
"stacktrace-gps": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.0.1.tgz",
"integrity": "sha1-Hm9Jl4QdK1vaurnmEX6WXrvmQoY=",
"dev": true
},
"stacktrace-js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.0.tgz",
"integrity": "sha1-d2ymRqlbxsayuQd2U2p/xyxt21g=",
"dev": true
},
"standard": { "standard": {
"version": "10.0.2", "version": "10.0.2",
"resolved": "https://registry.npmjs.org/standard/-/standard-10.0.2.tgz", "resolved": "https://registry.npmjs.org/standard/-/standard-10.0.2.tgz",
@@ -3337,18 +2868,6 @@
} }
} }
}, },
"statuses": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
"integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=",
"dev": true
},
"stream-combiner": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
"integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=",
"dev": true
},
"string_decoder": { "string_decoder": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
@@ -3361,16 +2880,6 @@
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true "dev": true
}, },
"string.fromcodepoint": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz",
"integrity": "sha1-jZeDM8C8klOPUPOD5IiPPlYZ1lM="
},
"string.prototype.codepointat": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz",
"integrity": "sha1-aybpvTr8qnvjtCabUm3huCAArHg="
},
"strip-ansi": { "strip-ansi": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
@@ -3383,20 +2892,6 @@
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
"dev": true "dev": true
}, },
"strip-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
"integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
"dev": true,
"dependencies": {
"get-stdin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
"dev": true
}
}
},
"strip-json-comments": { "strip-json-comments": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
@@ -3477,12 +2972,6 @@
"integrity": "sha1-yWPc8DciiS7FnLpWnpQLcZVNFyk=", "integrity": "sha1-yWPc8DciiS7FnLpWnpQLcZVNFyk=",
"dev": true "dev": true
}, },
"trim-newlines": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
"integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
"dev": true
},
"trim-right": { "trim-right": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
@@ -3526,83 +3015,30 @@
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
"dev": true "dev": true
}, },
"unix-crypt-td-js": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.0.0.tgz",
"integrity": "sha1-HAgkFQSBvHoB1J6Y8exmjYJBLzs=",
"dev": true
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
"dev": true
},
"user-home": { "user-home": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz",
"integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=",
"dev": true "dev": true
}, },
"utf-8": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/utf-8/-/utf-8-1.0.0.tgz",
"integrity": "sha1-QpwJ+xrDLOuvVllh7aSMs/RSIZc="
},
"util-deprecate": { "util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true "dev": true
}, },
"utils-merge": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz",
"integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=",
"dev": true
},
"uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==",
"dev": true
},
"v8flags": { "v8flags": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz",
"integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=",
"dev": true "dev": true
}, },
"validate-npm-package-license": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
"integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
"dev": true
},
"vary": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz",
"integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=",
"dev": true
},
"vlq": { "vlq": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.2.tgz", "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.2.tgz",
"integrity": "sha1-4xbVJXtAuGu0PLjV/qXX9U1rDKE=", "integrity": "sha1-4xbVJXtAuGu0PLjV/qXX9U1rDKE=",
"dev": true "dev": true
}, },
"websocket-driver": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz",
"integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=",
"dev": true
},
"websocket-extensions": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz",
"integrity": "sha1-domUmcGEtu91Q3fC27DNbLVdKec=",
"dev": true
},
"which": { "which": {
"version": "1.2.14", "version": "1.2.14",
"resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz",

View File

@@ -1,13 +1,12 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.0.0-9", "version": "13.0.0-7",
"description": "A framework for real-time p2p shared editing on any data", "description": "A framework for real-time p2p shared editing on any data",
"main": "./y.node.js", "main": "./y.node.js",
"browser": "./y.js", "browser": "./y.js",
"module": "./src/y.js", "module": "./src/y.js",
"scripts": { "scripts": {
"test": "npm run lint", "test": "npm run lint",
"debug": "concurrently 'rollup -wc rollup.test.js' 'cutest-serve y.test.js -o'",
"lint": "standard", "lint": "standard",
"dist": "rollup -c rollup.browser.js; rollup -c rollup.node.js", "dist": "rollup -c rollup.browser.js; rollup -c rollup.node.js",
"postversion": "npm run dist", "postversion": "npm run dist",
@@ -50,7 +49,6 @@
"babel-preset-latest": "^6.24.1", "babel-preset-latest": "^6.24.1",
"chance": "^1.0.9", "chance": "^1.0.9",
"concurrently": "^3.4.0", "concurrently": "^3.4.0",
"cutest": "^0.1.9",
"rollup-plugin-babel": "^2.7.1", "rollup-plugin-babel": "^2.7.1",
"rollup-plugin-commonjs": "^8.0.2", "rollup-plugin-commonjs": "^8.0.2",
"rollup-plugin-inject": "^2.0.0", "rollup-plugin-inject": "^2.0.0",
@@ -63,7 +61,6 @@
"tag-dist-files": "^0.1.6" "tag-dist-files": "^0.1.6"
}, },
"dependencies": { "dependencies": {
"debug": "^2.6.8", "debug": "^2.6.8"
"utf-8": "^1.0.0"
} }
} }

View File

@@ -3,8 +3,8 @@ import commonjs from 'rollup-plugin-commonjs'
import multiEntry from 'rollup-plugin-multi-entry' import multiEntry from 'rollup-plugin-multi-entry'
export default { export default {
entry: 'test/*', entry: 'tests/*.js',
moduleName: 'y-tests', moduleName: 'y-array-tests',
format: 'umd', format: 'umd',
plugins: [ plugins: [
nodeResolve({ nodeResolve({
@@ -15,6 +15,6 @@ export default {
commonjs(), commonjs(),
multiEntry() multiEntry()
], ],
dest: 'y.test.js', dest: 'y-array.test.js',
sourceMap: true sourceMap: true
} }

View File

@@ -1,18 +1,38 @@
import { BinaryEncoder, BinaryDecoder } from './Encoding.js'
import { sendSyncStep1, computeMessageSyncStep1, computeMessageSyncStep2, computeMessageUpdate } from './MessageHandler.js' function canRead (auth) { return auth === 'read' || auth === 'write' }
function canWrite (auth) { return auth === 'write' }
export default function extendConnector (Y/* :any */) { export default function extendConnector (Y/* :any */) {
class AbstractConnector { class AbstractConnector {
/* ::
y: YConfig;
role: SyncRole;
connections: Object;
isSynced: boolean;
userEventListeners: Array<Function>;
whenSyncedListeners: Array<Function>;
currentSyncTarget: ?UserId;
syncingClients: Array<UserId>;
forwardToSyncingClients: boolean;
debug: boolean;
syncStep2: Promise;
userId: UserId;
send: Function;
broadcast: Function;
broadcastOpBuffer: Array<Operation>;
protocolVersion: number;
*/
/* /*
opts contains the following information: opts contains the following information:
role : String Role of this client ("master" or "slave") role : String Role of this client ("master" or "slave")
userId : String Uniquely defines the user.
debug: Boolean Whether to print debug messages (optional)
*/ */
constructor (y, opts) { constructor (y, opts) {
this.y = y this.y = y
if (opts == null) { if (opts == null) {
opts = {} opts = {}
} }
this.opts = opts
// Prefer to receive untransformed operations. This does only work if // Prefer to receive untransformed operations. This does only work if
// this client receives operations from only one other client. // this client receives operations from only one other client.
// In particular, this does not work with y-webrtc. // In particular, this does not work with y-webrtc.
@@ -29,18 +49,29 @@ export default function extendConnector (Y/* :any */) {
this.logMessage = Y.debug('y:connector-message') this.logMessage = Y.debug('y:connector-message')
this.y.db.forwardAppliedOperations = opts.forwardAppliedOperations || false this.y.db.forwardAppliedOperations = opts.forwardAppliedOperations || false
this.role = opts.role this.role = opts.role
this.connections = new Map() this.connections = {}
this.isSynced = false this.isSynced = false
this.userEventListeners = [] this.userEventListeners = []
this.whenSyncedListeners = [] this.whenSyncedListeners = []
this.currentSyncTarget = null this.currentSyncTarget = null
this.syncingClients = []
this.forwardToSyncingClients = opts.forwardToSyncingClients !== false
this.debug = opts.debug === true this.debug = opts.debug === true
this.broadcastOpBuffer = [] this.broadcastOpBuffer = []
this.protocolVersion = 11 this.protocolVersion = 11
this.authInfo = opts.auth || null this.authInfo = opts.auth || null
this.checkAuth = opts.checkAuth || function () { return Promise.resolve('write') } // default is everyone has write access this.checkAuth = opts.checkAuth || function () { return Promise.resolve('write') } // default is everyone has write access
if (opts.generateUserId !== false) { if (opts.generateUserId !== false) {
this.setUserId(Y.utils.generateUserId()) this.setUserId(Y.utils.generateGuid())
}
}
resetAuth (auth) {
if (this.authInfo !== auth) {
this.authInfo = auth
this.broadcast({
type: 'auth',
auth: this.authInfo
})
} }
} }
reconnect () { reconnect () {
@@ -49,27 +80,25 @@ export default function extendConnector (Y/* :any */) {
} }
disconnect () { disconnect () {
this.log('discronnecting..') this.log('discronnecting..')
this.connections = new Map() this.connections = {}
this.isSynced = false this.isSynced = false
this.currentSyncTarget = null this.currentSyncTarget = null
this.syncingClients = []
this.whenSyncedListeners = [] this.whenSyncedListeners = []
this.y.db.stopGarbageCollector() this.y.db.stopGarbageCollector()
return this.y.db.whenTransactionsFinished() return this.y.db.whenTransactionsFinished()
} }
repair () { repair () {
this.log('Repairing the state of Yjs. This can happen if messages get lost, and Yjs detects that something is wrong. If this happens often, please report an issue here: https://github.com/y-js/yjs/issues') this.log('Repairing the state of Yjs. This can happen if messages get lost, and Yjs detects that something is wrong. If this happens often, please report an issue here: https://github.com/y-js/yjs/issues')
this.connections.forEach(user => { user.isSynced = false }) for (var name in this.connections) {
this.connections[name].isSynced = false
}
this.isSynced = false this.isSynced = false
this.currentSyncTarget = null this.currentSyncTarget = null
this.findNextSyncTarget() this.findNextSyncTarget()
} }
setUserId (userId) { setUserId (userId) {
if (this.userId == null) { if (this.userId == null) {
if (!Number.isInteger(userId)) {
let err = new Error('UserId must be an integer!')
this.y.emit('error', err)
throw err
}
this.log('Set userId to "%s"', userId) this.log('Set userId to "%s"', userId)
this.userId = userId this.userId = userId
return this.y.db.setUserId(userId) return this.y.db.setUserId(userId)
@@ -84,13 +113,16 @@ export default function extendConnector (Y/* :any */) {
this.userEventListeners = this.userEventListeners.filter(g => f !== g) this.userEventListeners = this.userEventListeners.filter(g => f !== g)
} }
userLeft (user) { userLeft (user) {
if (this.connections.has(user)) { if (this.connections[user] != null) {
this.log('%s: User left %s', this.userId, user) this.log('User left: %s', user)
this.connections.delete(user) delete this.connections[user]
if (user === this.currentSyncTarget) { if (user === this.currentSyncTarget) {
this.currentSyncTarget = null this.currentSyncTarget = null
this.findNextSyncTarget() this.findNextSyncTarget()
} }
this.syncingClients = this.syncingClients.filter(function (cli) {
return cli !== user
})
for (var f of this.userEventListeners) { for (var f of this.userEventListeners) {
f({ f({
action: 'userLeft', action: 'userLeft',
@@ -103,21 +135,19 @@ export default function extendConnector (Y/* :any */) {
if (role == null) { if (role == null) {
throw new Error('You must specify the role of the joined user!') throw new Error('You must specify the role of the joined user!')
} }
if (this.connections.has(user)) { if (this.connections[user] != null) {
throw new Error('This user already joined!') throw new Error('This user already joined!')
} }
this.log('%s: User joined %s', this.userId, user) this.log('User joined: %s', user)
this.connections.set(user, { this.connections[user] = {
uid: user,
isSynced: false, isSynced: false,
role: role, role: role,
processAfterAuth: [], waitingMessages: [],
auth: null, auth: null
receivedSyncStep2: false }
})
let defer = {} let defer = {}
defer.promise = new Promise(function (resolve) { defer.resolve = resolve }) defer.promise = new Promise(function (resolve) { defer.resolve = resolve })
this.connections.get(user).syncStep2 = defer this.connections[user].syncStep2 = defer
for (var f of this.userEventListeners) { for (var f of this.userEventListeners) {
f({ f({
action: 'userJoined', action: 'userJoined',
@@ -139,13 +169,13 @@ export default function extendConnector (Y/* :any */) {
} }
} }
findNextSyncTarget () { findNextSyncTarget () {
if (this.currentSyncTarget != null || this.role === 'slave') { if (this.currentSyncTarget != null) {
return // "The current sync has not finished or this is controlled by a master!" return // "The current sync has not finished!"
} }
var syncUser = null var syncUser = null
for (var [uid, user] of this.connections) { for (var uid in this.connections) {
if (!user.isSynced) { if (!this.connections[uid].isSynced) {
syncUser = uid syncUser = uid
break break
} }
@@ -153,7 +183,21 @@ export default function extendConnector (Y/* :any */) {
var conn = this var conn = this
if (syncUser != null) { if (syncUser != null) {
this.currentSyncTarget = syncUser this.currentSyncTarget = syncUser
sendSyncStep1(this, syncUser) this.y.db.requestTransaction(function * () {
var stateSet = yield * this.getStateSet()
// var deleteSet = yield * this.getDeleteSet()
var answer = {
type: 'sync step 1',
stateSet: stateSet,
// deleteSet: deleteSet,
protocolVersion: conn.protocolVersion,
auth: conn.authInfo
}
if (conn.preferUntransformed && Object.keys(stateSet).length === 0) {
answer.preferUntransformed = true
}
conn.send(syncUser, answer)
})
} else { } else {
if (!conn.isSynced) { if (!conn.isSynced) {
this.y.db.requestTransaction(function * () { this.y.db.requestTransaction(function * () {
@@ -172,19 +216,13 @@ export default function extendConnector (Y/* :any */) {
} }
} }
} }
send (uid, buffer) { send (uid, message) {
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) { this.log('Send \'%s\' to %s', message.type, uid)
throw new Error('Expected Message to be an ArrayBuffer or Uint8Array - please don\'t use this method to send custom messages') this.logMessage('Message: %j', message)
}
this.log('%s: Send \'%y\' to %s', this.userId, buffer, uid)
this.logMessage('Message: %Y', buffer)
} }
broadcast (buffer) { broadcast (message) {
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) { this.log('Broadcast \'%s\'', message.type)
throw new Error('Expected Message to be an ArrayBuffer or Uint8Array - please don\'t use this method to send custom messages') this.logMessage('Message: %j', message)
}
this.log('%s: Broadcast \'%y\'', this.userId, buffer)
this.logMessage('Message: %Y', buffer)
} }
/* /*
Buffer operations, and broadcast them when ready. Buffer operations, and broadcast them when ready.
@@ -196,18 +234,11 @@ export default function extendConnector (Y/* :any */) {
var self = this var self = this
function broadcastOperations () { function broadcastOperations () {
if (self.broadcastOpBuffer.length > 0) { if (self.broadcastOpBuffer.length > 0) {
let encoder = new BinaryEncoder() self.broadcast({
encoder.writeVarString(self.opts.room) type: 'update',
encoder.writeVarString('update') ops: self.broadcastOpBuffer
let ops = self.broadcastOpBuffer })
self.broadcastOpBuffer = [] self.broadcastOpBuffer = []
let length = ops.length
encoder.writeUint32(length)
for (var i = 0; i < length; i++) {
let op = ops[i]
Y.Struct[op.struct].binaryEncode(encoder, op)
}
self.broadcast(encoder.createBuffer())
} }
} }
if (this.broadcastOpBuffer.length === 0) { if (this.broadcastOpBuffer.length === 0) {
@@ -220,67 +251,156 @@ export default function extendConnector (Y/* :any */) {
/* /*
You received a raw message, and you know that it is intended for Yjs. Then call this function. You received a raw message, and you know that it is intended for Yjs. Then call this function.
*/ */
receiveMessage (sender, buffer) { receiveMessage (sender/* :UserId */, message/* :Message */) {
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
throw new Error('Expected Message to be an ArrayBuffer or Uint8Array!')
}
if (sender === this.userId) { if (sender === this.userId) {
return return Promise.resolve()
} }
let decoder = new BinaryDecoder(buffer) this.log('Receive \'%s\' from %s', message.type, sender)
let encoder = new BinaryEncoder() this.logMessage('Message: %j', message)
let roomname = decoder.readVarString() // read room name if (message.protocolVersion != null && message.protocolVersion !== this.protocolVersion) {
encoder.writeVarString(roomname) this.log(
let messageType = decoder.readVarString() `You tried to sync with a yjs instance that has a different protocol version
let senderConn = this.connections.get(sender) (You: ${this.protocolVersion}, Client: ${message.protocolVersion}).
The sync was stopped. You need to upgrade your dependencies (especially Yjs & the Connector)!
this.log('%s: Receive \'%s\' from %s', this.userId, messageType, sender) `)
this.logMessage('Message: %Y', buffer) this.send(sender, {
type: 'sync stop',
if (senderConn == null) { protocolVersion: this.protocolVersion
throw new Error('Received message from unknown peer!') })
return Promise.reject(new Error('Incompatible protocol version'))
} }
if ((message.type === 'sync step 1' || message.type === 'sync step 2') && this.connections[sender] != null && this.connections[sender].auth == null) {
if (messageType === 'sync step 1' || messageType === 'sync step 2') { // authenticate using auth in message
let auth = decoder.readVarUint() var auth = this.checkAuth(message.auth, this.y)
if (senderConn.auth == null) { this.connections[sender].auth = auth
senderConn.processAfterAuth.push([sender, buffer]) auth.then(auth => {
// in case operations were received before sender was received
// check auth // we apply the messages after authentication
return this.checkAuth(auth, this.y, sender).then(authPermissions => { this.connections[sender].syncStep2.promise.then(() => {
senderConn.auth = authPermissions // we do it after sync step 1
this.y.emit('userAuthenticated', { this.connections[sender].waitingMessages.forEach(msg => {
user: senderConn.uid, this.receiveMessage(sender, msg)
auth: authPermissions
}) })
return senderConn.syncStep2.promise this.connections[sender].waitingMessages = null
}).then(() => {
if (senderConn.processAfterAuth == null) {
return Promise.resolve()
}
let messages = senderConn.processAfterAuth
senderConn.processAfterAuth = null
return Promise.all(messages.map(m =>
this.receiveMessage(m[0], m[1])
))
}) })
} for (var f of this.userEventListeners) {
f({
action: 'userAuthenticated',
user: sender,
auth: auth
})
}
})
} }
if (this.connections[sender] != null && this.connections[sender].auth != null) {
return this.connections[sender].auth.then((auth) => {
if (message.type === 'sync step 1' && canRead(auth)) {
let conn = this
let m = message
let wait // wait for sync step 2 to complete
if (this.role === 'slave') {
wait = Promise.all(Object.keys(this.connections)
.map(uid => this.connections[uid])
.filter(conn => conn.role === 'master')
.map(conn => conn.syncStep2.promise)
)
} else {
wait = Promise.resolve()
}
wait.then(() => {
this.y.db.requestTransaction(function * () {
var currentStateSet = yield * this.getStateSet()
// TODO: remove
// if (canWrite(auth)) {
// yield * this.applyDeleteSet(m.deleteSet)
// }
if (messageType === 'sync step 1' && (senderConn.auth === 'write' || senderConn.auth === 'read')) { var ds = yield * this.getDeleteSet()
// cannot wait for sync step 1 to finish, because we may wait for sync step 2 in sync step 1 (->lock) var answer = {
computeMessageSyncStep1(decoder, encoder, this, senderConn, sender) type: 'sync step 2',
return this.y.db.whenTransactionsFinished() stateSet: currentStateSet,
} else if (messageType === 'sync step 2' && senderConn.auth === 'write') { deleteSet: ds,
return computeMessageSyncStep2(decoder, encoder, this, senderConn, sender) protocolVersion: this.protocolVersion,
} else if (messageType === 'update' && senderConn.auth === 'write') { auth: this.authInfo
return computeMessageUpdate(decoder, encoder, this, senderConn, sender) }
if (message.preferUntransformed === true && Object.keys(m.stateSet).length === 0) {
answer.osUntransformed = yield * this.getOperationsUntransformed()
} else {
answer.os = yield * this.getOperations(m.stateSet)
}
conn.send(sender, answer)
if (this.forwardToSyncingClients) {
conn.syncingClients.push(sender)
setTimeout(function () {
conn.syncingClients = conn.syncingClients.filter(function (cli) {
return cli !== sender
})
conn.send(sender, {
type: 'sync done'
})
}, 5000) // TODO: conn.syncingClientDuration)
} else {
conn.send(sender, {
type: 'sync done'
})
}
})
})
} else if (message.type === 'sync step 2' && canWrite(auth)) {
var db = this.y.db
let defer = this.connections[sender].syncStep2
let m = message
// apply operations first
db.requestTransaction(function * () {
// yield * this.applyDeleteSet(m.deleteSet)
if (m.osUntransformed != null) {
yield * this.applyOperationsUntransformed(m.osUntransformed, m.stateSet)
} else {
this.store.apply(m.os)
}
// defer.resolve()
})
// then apply ds
db.whenTransactionsFinished().then(() => {
db.requestTransaction(function * () {
yield * this.applyDeleteSet(m.deleteSet)
})
defer.resolve()
})
return defer.promise
} else if (message.type === 'sync done') {
var self = this
this.connections[sender].syncStep2.promise.then(function () {
self._setSyncedWith(sender)
})
} else if (message.type === 'update' && canWrite(auth)) {
if (this.forwardToSyncingClients) {
for (var client of this.syncingClients) {
this.send(client, message)
}
}
if (this.y.db.forwardAppliedOperations) {
var delops = message.ops.filter(function (o) {
return o.struct === 'Delete'
})
if (delops.length > 0) {
this.broadcastOps(delops)
}
}
this.y.db.apply(message.ops)
}
})
} else if (this.connections[sender] != null) {
// wait for authentication
let senderConn = this.connections[sender]
senderConn.waitingMessages = senderConn.waitingMessages || []
senderConn.waitingMessages.push(message)
} else { } else {
Promise.reject(new Error('Unable to receive message')) return Promise.reject(new Error('Unknown user - Unable to deliver message'))
} }
} }
_setSyncedWith (user) { _setSyncedWith (user) {
var conn = this.connections.get(user) var conn = this.connections[user]
if (conn != null) { if (conn != null) {
conn.isSynced = true conn.isSynced = true
} }

178
src/Connectors/Test.js Normal file
View File

@@ -0,0 +1,178 @@
/* global getRandom, async */
'use strict'
module.exports = function (Y) {
var globalRoom = {
users: {},
buffers: {},
removeUser: function (user) {
for (var i in this.users) {
this.users[i].userLeft(user)
}
delete this.users[user]
delete this.buffers[user]
},
addUser: function (connector) {
this.users[connector.userId] = connector
this.buffers[connector.userId] = {}
for (var uname in this.users) {
if (uname !== connector.userId) {
var u = this.users[uname]
u.userJoined(connector.userId, 'master')
connector.userJoined(u.userId, 'master')
}
}
},
whenTransactionsFinished: function () {
var self = this
return new Promise(function (resolve, reject) {
// The connector first has to send the messages to the db.
// Wait for the checkAuth-function to resolve
// The test lib only has a simple checkAuth function: `() => Promise.resolve()`
// Just add a function to the event-queue, in order to wait for the event.
// TODO: this may be buggy in test applications (but it isn't be for real-life apps)
setTimeout(function () {
var ps = []
for (var name in self.users) {
ps.push(self.users[name].y.db.whenTransactionsFinished())
}
Promise.all(ps).then(resolve, reject)
}, 10)
})
},
flushOne: function flushOne () {
var bufs = []
for (var receiver in globalRoom.buffers) {
let buff = globalRoom.buffers[receiver]
var push = false
for (let sender in buff) {
if (buff[sender].length > 0) {
push = true
break
}
}
if (push) {
bufs.push(receiver)
}
}
if (bufs.length > 0) {
var userId = getRandom(bufs)
let buff = globalRoom.buffers[userId]
let sender = getRandom(Object.keys(buff))
var m = buff[sender].shift()
if (buff[sender].length === 0) {
delete buff[sender]
}
var user = globalRoom.users[userId]
return user.receiveMessage(m[0], m[1]).then(function () {
return user.y.db.whenTransactionsFinished()
}, function () {})
} else {
return false
}
},
flushAll: function () {
return new Promise(function (resolve) {
// flushes may result in more created operations,
// flush until there is nothing more to flush
function nextFlush () {
var c = globalRoom.flushOne()
if (c) {
while (c) {
c = globalRoom.flushOne()
}
globalRoom.whenTransactionsFinished().then(nextFlush)
} else {
c = globalRoom.flushOne()
if (c) {
c.then(function () {
globalRoom.whenTransactionsFinished().then(nextFlush)
})
} else {
resolve()
}
}
}
globalRoom.whenTransactionsFinished().then(nextFlush)
})
}
}
Y.utils.globalRoom = globalRoom
var userIdCounter = 0
class Test extends Y.AbstractConnector {
constructor (y, options) {
if (options === undefined) {
throw new Error('Options must not be undefined!')
}
options.role = 'master'
options.forwardToSyncingClients = false
super(y, options)
this.setUserId((userIdCounter++) + '').then(() => {
globalRoom.addUser(this)
})
this.globalRoom = globalRoom
this.syncingClientDuration = 0
}
receiveMessage (sender, m) {
return super.receiveMessage(sender, JSON.parse(JSON.stringify(m)))
}
send (userId, message) {
var buffer = globalRoom.buffers[userId]
if (buffer != null) {
if (buffer[this.userId] == null) {
buffer[this.userId] = []
}
buffer[this.userId].push(JSON.parse(JSON.stringify([this.userId, message])))
}
}
broadcast (message) {
for (var key in globalRoom.buffers) {
var buff = globalRoom.buffers[key]
if (buff[this.userId] == null) {
buff[this.userId] = []
}
buff[this.userId].push(JSON.parse(JSON.stringify([this.userId, message])))
}
}
isDisconnected () {
return globalRoom.users[this.userId] == null
}
reconnect () {
if (this.isDisconnected()) {
globalRoom.addUser(this)
super.reconnect()
}
return Y.utils.globalRoom.flushAll()
}
disconnect () {
var waitForMe = Promise.resolve()
if (!this.isDisconnected()) {
globalRoom.removeUser(this.userId)
waitForMe = super.disconnect()
}
var self = this
return waitForMe.then(function () {
return self.y.db.whenTransactionsFinished()
})
}
flush () {
var self = this
return async(function * () {
var buff = globalRoom.buffers[self.userId]
while (Object.keys(buff).length > 0) {
var sender = getRandom(Object.keys(buff))
var m = buff[sender].shift()
if (buff[sender].length === 0) {
delete buff[sender]
}
yield this.receiveMessage(m[0], m[1])
}
yield self.whenTransactionsFinished()
})
}
}
Y.Test = Test
}

View File

@@ -306,12 +306,10 @@ export default function extendDatabase (Y /* :any */) {
* check if it is an expected op (otherwise wait for it) * check if it is an expected op (otherwise wait for it)
* check if was deleted, apply a delete operation after op was applied * check if was deleted, apply a delete operation after op was applied
*/ */
applyOperations (decoder) { apply (ops) {
this.opsReceivedTimestamp = new Date() this.opsReceivedTimestamp = new Date()
let length = decoder.readUint32() for (var i = 0; i < ops.length; i++) {
var o = ops[i]
for (var i = 0; i < length; i++) {
let o = Y.Struct.binaryDecodeOperation(decoder)
if (o.id == null || o.id[0] !== this.y.connector.userId) { if (o.id == null || o.id[0] !== this.y.connector.userId) {
var required = Y.Struct[o.struct].requiredOps(o) var required = Y.Struct[o.struct].requiredOps(o)
if (o.requires != null) { if (o.requires != null) {
@@ -592,7 +590,7 @@ export default function extendDatabase (Y /* :any */) {
op.type = typedefinition[0].name op.type = typedefinition[0].name
this.requestTransaction(function * () { this.requestTransaction(function * () {
if (op.id[0] === 0xFFFFFF) { if (op.id[0] === '_') {
yield * this.setOperation(op) yield * this.setOperation(op)
} else { } else {
yield * this.applyCreatedOperations([op]) yield * this.applyCreatedOperations([op])

View File

@@ -1,133 +0,0 @@
import utf8 from 'utf-8'
const bits7 = 0b1111111
const bits8 = 0b11111111
export class BinaryEncoder {
constructor () {
this.data = []
}
get pos () {
return this.data.length
}
createBuffer () {
return Uint8Array.from(this.data).buffer
}
writeUint8 (num) {
this.data.push(num & bits8)
}
setUint8 (pos, num) {
this.data[pos] = num & bits8
}
writeUint16 (num) {
this.data.push(num & bits8, (num >>> 8) & bits8)
}
setUint16 (pos, num) {
this.data[pos] = num & bits8
this.data[pos + 1] = (num >>> 8) & bits8
}
writeUint32 (num) {
for (let i = 0; i < 4; i++) {
this.data.push(num & bits8)
num >>>= 8
}
}
setUint32 (pos, num) {
for (let i = 0; i < 4; i++) {
this.data[pos + i] = num & bits8
num >>>= 8
}
}
writeVarUint (num) {
while (num >= 0b10000000) {
this.data.push(0b10000000 | (bits7 & num))
num >>>= 7
}
this.data.push(bits7 & num)
}
writeVarString (str) {
let bytes = utf8.setBytesFromString(str)
let len = bytes.length
this.writeVarUint(len)
for (let i = 0; i < len; i++) {
this.data.push(bytes[i])
}
}
writeOpID (id) {
let user = id[0]
this.writeVarUint(user)
if (user !== 0xFFFFFF) {
this.writeVarUint(id[1])
} else {
this.writeVarString(id[1])
}
}
}
export class BinaryDecoder {
constructor (buffer) {
if (buffer instanceof ArrayBuffer) {
this.uint8arr = new Uint8Array(buffer)
} else if (buffer instanceof Uint8Array) {
this.uint8arr = buffer
} else {
throw new Error('Expected an ArrayBuffer or Uint8Array!')
}
this.pos = 0
}
skip8 () {
this.pos++
}
readUint8 () {
return this.uint8arr[this.pos++]
}
readUint32 () {
let uint =
this.uint8arr[this.pos] +
(this.uint8arr[this.pos + 1] << 8) +
(this.uint8arr[this.pos + 2] << 16) +
(this.uint8arr[this.pos + 3] << 24)
this.pos += 4
return uint
}
peekUint8 () {
return this.uint8arr[this.pos]
}
readVarUint () {
let num = 0
let len = 0
while (true) {
let r = this.uint8arr[this.pos++]
num = num | ((r & bits7) << len)
len += 7
if (r < 1 << 7) {
return num >>> 0 // return unsigned number!
}
if (len > 35) {
throw new Error('Integer out of range!')
}
}
}
readVarString () {
let len = this.readVarUint()
let bytes = new Array(len)
for (let i = 0; i < len; i++) {
bytes[i] = this.uint8arr[this.pos++]
}
return utf8.getStringFromBytes(bytes)
}
peekVarString () {
let pos = this.pos
let s = this.readVarString()
this.pos = pos
return s
}
readOpID () {
let user = this.readVarUint()
if (user !== 0xFFFFFF) {
return [user, this.readVarUint()]
} else {
return [user, this.readVarString()]
}
}
}

View File

@@ -1,185 +0,0 @@
import Y from './y.js'
import { BinaryDecoder, BinaryEncoder } from './Encoding.js'
export function formatYjsMessage (buffer) {
let decoder = new BinaryDecoder(buffer)
decoder.readVarString() // read roomname
let type = decoder.readVarString()
let strBuilder = []
strBuilder.push('\n === ' + type + ' ===\n')
if (type === 'update') {
logMessageUpdate(decoder, strBuilder)
} else if (type === 'sync step 1') {
logMessageSyncStep1(decoder, strBuilder)
} else if (type === 'sync step 2') {
logMessageSyncStep2(decoder, strBuilder)
} else {
strBuilder.push('-- Unknown message type - probably an encoding issue!!!')
}
return strBuilder.join('')
}
export function formatYjsMessageType (buffer) {
let decoder = new BinaryDecoder(buffer)
decoder.readVarString() // roomname
return decoder.readVarString()
}
export function logMessageUpdate (decoder, strBuilder) {
let len = decoder.readUint32()
for (let i = 0; i < len; i++) {
strBuilder.push(JSON.stringify(Y.Struct.binaryDecodeOperation(decoder)) + '\n')
}
}
export function computeMessageUpdate (decoder, encoder, conn) {
if (conn.y.db.forwardAppliedOperations) {
let messagePosition = decoder.pos
let len = decoder.readUint32()
let delops = []
for (let i = 0; i < len; i++) {
let op = Y.Struct.binaryDecodeOperation(decoder)
if (op.struct === 'Delete') {
delops.push(op)
}
}
if (delops.length > 0) {
conn.broadcastOps(delops)
}
decoder.pos = messagePosition
}
conn.y.db.applyOperations(decoder)
}
export function sendSyncStep1 (conn, syncUser) {
conn.y.db.requestTransaction(function * () {
let encoder = new BinaryEncoder()
encoder.writeVarString(conn.opts.room || '')
encoder.writeVarString('sync step 1')
encoder.writeVarString(conn.authInfo || '')
encoder.writeVarUint(conn.protocolVersion)
let preferUntransformed = conn.preferUntransformed && this.os.length === 0 // TODO: length may not be defined
encoder.writeUint8(preferUntransformed ? 1 : 0)
yield * this.writeStateSet(encoder)
conn.send(syncUser, encoder.createBuffer())
})
}
export function logMessageSyncStep1 (decoder, strBuilder) {
let auth = decoder.readVarString()
let protocolVersion = decoder.readVarUint()
let preferUntransformed = decoder.readUint8() === 1
strBuilder.push(`
- auth: "${auth}"
- protocolVersion: ${protocolVersion}
- preferUntransformed: ${preferUntransformed}
`)
logSS(decoder, strBuilder)
}
export function computeMessageSyncStep1 (decoder, encoder, conn, senderConn, sender) {
let protocolVersion = decoder.readVarUint()
let preferUntransformed = decoder.readUint8() === 1
// check protocol version
if (protocolVersion !== conn.protocolVersion) {
console.warn(
`You tried to sync with a yjs instance that has a different protocol version
(You: ${protocolVersion}, Client: ${protocolVersion}).
The sync was stopped. You need to upgrade your dependencies (especially Yjs & the Connector)!
`)
conn.y.destroy()
}
// send sync step 2
conn.y.db.requestTransaction(function * () {
encoder.writeVarString('sync step 2')
encoder.writeVarString(conn.authInfo || '')
if (preferUntransformed) {
encoder.writeUint8(1)
yield * this.writeOperationsUntransformed(encoder)
} else {
encoder.writeUint8(0)
yield * this.writeOperations(encoder, decoder)
}
yield * this.writeDeleteSet(encoder)
conn.send(senderConn.uid, encoder.createBuffer())
senderConn.receivedSyncStep2 = true
})
if (conn.role === 'slave') {
sendSyncStep1(conn, sender)
}
return conn.y.db.whenTransactionsFinished()
}
export function logSS (decoder, strBuilder) {
strBuilder.push(' == SS: \n')
let len = decoder.readUint32()
for (let i = 0; i < len; i++) {
let user = decoder.readVarUint()
let clock = decoder.readVarUint()
strBuilder.push(` ${user}: ${clock}\n`)
}
}
export function logOS (decoder, strBuilder) {
strBuilder.push(' == OS: \n')
let len = decoder.readUint32()
for (let i = 0; i < len; i++) {
let op = Y.Struct.binaryDecodeOperation(decoder)
strBuilder.push(JSON.stringify(op) + '\n')
}
}
export function logDS (decoder, strBuilder) {
strBuilder.push(' == DS: \n')
let len = decoder.readUint32()
for (let i = 0; i < len; i++) {
let user = decoder.readVarUint()
strBuilder.push(` User: ${user}: `)
let len2 = decoder.readVarUint()
for (let j = 0; j < len2; j++) {
let from = decoder.readVarUint()
let to = decoder.readVarUint()
let gc = decoder.readUint8() === 1
strBuilder.push(`[${from}, ${to}, ${gc}]`)
}
}
}
export function logMessageSyncStep2 (decoder, strBuilder) {
strBuilder.push(' - auth: ' + decoder.readVarString() + '\n')
let osTransformed = decoder.readUint8() === 1
strBuilder.push(' - osUntransformed: ' + osTransformed + '\n')
logOS(decoder, strBuilder)
if (osTransformed) {
logSS(decoder, strBuilder)
}
logDS(decoder, strBuilder)
}
export function computeMessageSyncStep2 (decoder, encoder, conn, senderConn, sender) {
var db = conn.y.db
let defer = senderConn.syncStep2
// apply operations first
db.requestTransaction(function * () {
let osUntransformed = decoder.readUint8()
if (osUntransformed === 1) {
yield * this.applyOperationsUntransformed(decoder)
} else {
this.store.applyOperations(decoder)
}
})
// then apply ds
db.requestTransaction(function * () {
yield * this.applyDeleteSet(decoder)
})
return db.whenTransactionsFinished().then(() => {
conn._setSyncedWith(sender)
defer.resolve()
})
}

404
src/SpecHelper.js Normal file
View File

@@ -0,0 +1,404 @@
/* eslint-env browser, jasmine */
/*
This is just a compilation of functions that help to test this library!
*/
// When testing, you store everything on the global object. We call it g
var Y = require('./y.js')
require('../../y-memory/src/Memory.js')(Y)
require('../../y-array/src/Array.js')(Y)
require('../../y-map/src/Map.js')(Y)
require('../../y-indexeddb/src/IndexedDB.js')(Y)
module.exports = Y
var g
if (typeof global !== 'undefined') {
g = global
} else if (typeof window !== 'undefined') {
g = window
} else {
throw new Error('No global object?')
}
g.g = g
// Helper methods for the random number generator
Math.seedrandom = require('seedrandom')
g.generateRandomSeed = function generateRandomSeed () {
var seed
if (typeof window !== 'undefined' && window.location.hash.length > 1) {
seed = window.location.hash.slice(1) // first character is the hash!
console.warn('Using random seed that was specified in the url!')
} else {
seed = JSON.stringify(Math.random())
}
console.info('Using random seed: ' + seed)
g.setRandomSeed(seed)
}
g.setRandomSeed = function setRandomSeed (seed) {
Math.seedrandom.currentSeed = seed
Math.seedrandom(Math.seedrandom.currentSeed, { global: true })
}
g.generateRandomSeed()
g.YConcurrencyTestingMode = true
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000
g.describeManyTimes = function describeManyTimes (times, name, f) {
for (var i = 0; i < times; i++) {
describe(name, f)
}
}
/*
Wait for a specified amount of time (in ms). defaults to 5ms
*/
function wait (t) {
if (t == null) {
t = 0
}
return new Promise(function (resolve) {
setTimeout(function () {
resolve()
}, t)
})
}
g.wait = wait
g.databases = ['memory']
if (typeof window !== 'undefined') {
g.databases.push('indexeddb')
} else {
g.databases.push('leveldb')
}
/*
returns a random element of o.
works on Object, and Array
*/
function getRandom (o) {
if (o instanceof Array) {
return o[Math.floor(Math.random() * o.length)]
} else if (o.constructor === Object) {
return o[getRandom(Object.keys(o))]
}
}
g.getRandom = getRandom
function getRandomNumber (n) {
if (n == null) {
n = 9999
}
return Math.floor(Math.random() * n)
}
g.getRandomNumber = getRandomNumber
function getRandomString () {
var chars = 'abcdefghijklmnopqrstuvwxyzäüöABCDEFGHIJKLMNOPQRSTUVWXYZÄÜÖ'
var char = chars[getRandomNumber(chars.length)] // ü\n\n\n\n\n\n\n'
var length = getRandomNumber(7)
var string = ''
for (var i = 0; i < length; i++) {
string += char
}
return string
}
g.getRandomString = getRandomString
function * applyTransactions (relAmount, numberOfTransactions, objects, users, transactions, noReconnect) {
g.generateRandomSeed() // create a new seed, so we can re-create the behavior
for (var i = 0; i < numberOfTransactions * relAmount + 1; i++) {
var r = Math.random()
if (r > 0.95) {
// 10% chance of toggling concurrent user interactions.
// There will be an artificial delay until ops can be executed by the type,
// therefore, operations of the database will be (pre)transformed until user operations arrive
yield (function simulateConcurrentUserInteractions (type) {
if (!(type instanceof Y.utils.CustomType) && type.y instanceof Y.utils.CustomType) {
// usually we expect type to be a custom type. But in YXml we share an object {y: YXml, dom: Dom} instead
type = type.y
}
if (type.eventHandler.awaiting === 0 && type.eventHandler._debuggingAwaiting !== true) {
type.eventHandler.awaiting = 1
type.eventHandler._debuggingAwaiting = true
} else {
// fixAwaitingInType will handle _debuggingAwaiting
return fixAwaitingInType(type)
}
})(getRandom(objects))
} else if (r >= 0.5) {
// 40% chance to flush
yield Y.utils.globalRoom.flushOne() // flushes for some user.. (not necessarily 0)
} else if (noReconnect || r >= 0.05) {
// 45% chance to create operation
var done = getRandom(transactions)(getRandom(objects))
if (done != null) {
yield done
} else {
yield wait()
}
yield Y.utils.globalRoom.whenTransactionsFinished()
} else {
// 5% chance to disconnect/reconnect
var u = getRandom(users)
yield Promise.all(objects.map(fixAwaitingInType))
if (u.connector.isDisconnected()) {
yield u.reconnect()
} else {
yield u.disconnect()
}
yield Promise.all(objects.map(fixAwaitingInType))
}
}
}
function fixAwaitingInType (type) {
if (!(type instanceof Y.utils.CustomType) && type.y instanceof Y.utils.CustomType) {
// usually we expect type to be a custom type. But in YXml we share an object {y: YXml, dom: Dom} instead
type = type.y
}
return new Promise(function (resolve) {
type.os.whenTransactionsFinished().then(function () {
// _debuggingAwaiting artificially increases the awaiting property. We need to make sure that we only do that once / reverse the effect once
type.os.requestTransaction(function * () {
if (type.eventHandler.awaiting > 0 && type.eventHandler._debuggingAwaiting === true) {
type.eventHandler._debuggingAwaiting = false
yield * type.eventHandler.awaitOps(this, function * () { /* mock function */ })
}
wait(50).then(type.os.whenTransactionsFinished()).then(wait(50)).then(resolve)
})
})
})
}
g.fixAwaitingInType = fixAwaitingInType
g.applyRandomTransactionsNoGCNoDisconnect = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
yield * applyTransactions(1, numberOfTransactions, objects, users, transactions, true)
yield Y.utils.globalRoom.flushAll()
yield Promise.all(objects.map(fixAwaitingInType))
})
g.applyRandomTransactionsAllRejoinNoGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
yield * applyTransactions(1, numberOfTransactions, objects, users, transactions)
yield Promise.all(objects.map(fixAwaitingInType))
yield Y.utils.globalRoom.flushAll()
yield Promise.all(objects.map(fixAwaitingInType))
for (var u in users) {
yield Promise.all(objects.map(fixAwaitingInType))
yield users[u].reconnect()
yield Promise.all(objects.map(fixAwaitingInType))
}
yield Promise.all(objects.map(fixAwaitingInType))
yield Y.utils.globalRoom.flushAll()
yield Promise.all(objects.map(fixAwaitingInType))
yield g.garbageCollectAllUsers(users)
})
g.applyRandomTransactionsWithGC = async(function * applyRandomTransactions (users, objects, transactions, numberOfTransactions) {
yield * applyTransactions(1, numberOfTransactions, objects, users.slice(1), transactions)
yield Y.utils.globalRoom.flushAll()
yield Promise.all(objects.map(fixAwaitingInType))
for (var u in users) {
// TODO: here, we enforce that two users never sync at the same time with u[0]
// enforce that in the connector itself!
yield users[u].reconnect()
}
yield Y.utils.globalRoom.flushAll()
yield Promise.all(objects.map(fixAwaitingInType))
yield g.garbageCollectAllUsers(users)
})
g.garbageCollectAllUsers = async(function * garbageCollectAllUsers (users) {
yield Y.utils.globalRoom.flushAll()
for (var i in users) {
yield users[i].db.emptyGarbageCollector()
}
})
g.compareAllUsers = async(function * compareAllUsers (users) {
var s1, s2 // state sets
var ds1, ds2 // delete sets
var allDels1, allDels2 // all deletions
var db1 = [] // operation store of user1
yield Y.utils.globalRoom.flushAll()
yield g.garbageCollectAllUsers(users)
yield Y.utils.globalRoom.flushAll()
// disconnect, then reconnect all users
// We do this to make sure that the gc is updated by everyone
for (var i = 0; i < users.length; i++) {
yield users[i].disconnect()
yield wait()
yield users[i].reconnect()
}
yield wait()
yield Y.utils.globalRoom.flushAll()
// t1 and t2 basically do the same. They define t[1,2], ds[1,2], and allDels[1,2]
function * t1 () {
s1 = yield * this.getStateSet()
ds1 = yield * this.getDeleteSet()
allDels1 = []
yield * this.ds.iterate(this, null, null, function * (d) {
allDels1.push(d)
})
}
function * t2 () {
s2 = yield * this.getStateSet()
ds2 = yield * this.getDeleteSet()
allDels2 = []
yield * this.ds.iterate(this, null, null, function * (d) {
allDels2.push(d)
})
}
var buffer = Y.utils.globalRoom.buffers
for (var name in buffer) {
if (buffer[name].length > 0) {
// not all ops were transmitted..
debugger // eslint-disable-line
}
}
for (var uid = 0; uid < users.length; uid++) {
var u = users[uid]
u.db.requestTransaction(function * () {
var sv = yield * this.getStateVector()
for (var s of sv) {
yield * this.updateState(s.user)
}
// compare deleted ops against deleteStore
yield * this.os.iterate(this, null, null, function * (o) {
if (o.deleted === true) {
expect(yield * this.isDeleted(o.id)).toBeTruthy()
}
})
// compare deleteStore against deleted ops
var ds = []
yield * this.ds.iterate(this, null, null, function * (d) {
ds.push(d)
})
for (var j in ds) {
var d = ds[j]
for (var i = 0; i < d.len; i++) {
var o = yield * this.getInsertion([d.id[0], d.id[1] + i])
// gc'd or deleted
if (d.gc) {
expect(o).toBeFalsy()
} else {
expect(o.deleted).toBeTruthy()
}
}
}
})
// compare allDels tree
if (s1 == null) {
u.db.requestTransaction(function * () {
yield * t1.call(this)
yield * this.os.iterate(this, null, null, function * (o) {
o = Y.utils.copyObject(o)
delete o.origin
delete o.originOf
db1.push(o)
})
})
} else {
u.db.requestTransaction(function * () {
yield * t2.call(this)
var db2 = []
yield * this.os.iterate(this, null, null, function * (o) {
o = Y.utils.copyObject(o)
delete o.origin
delete o.originOf
db2.push(o)
})
expect(s1).toEqual(s2)
expect(allDels1).toEqual(allDels2) // inner structure
expect(ds1).toEqual(ds2) // exported structure
db2.forEach((o, i) => {
expect(db1[i]).toEqual(o)
})
})
}
yield u.db.whenTransactionsFinished()
}
})
g.createUsers = async(function * createUsers (self, numberOfUsers, database, initType) {
if (Y.utils.globalRoom.users[0] != null) {
yield Y.utils.globalRoom.flushAll()
}
// destroy old users
for (var u in Y.utils.globalRoom.users) {
Y.utils.globalRoom.users[u].y.destroy()
}
self.users = null
var promises = []
for (var i = 0; i < numberOfUsers; i++) {
promises.push(Y({
db: {
name: database,
namespace: 'User ' + i,
cleanStart: true,
gcTimeout: -1,
gc: true,
repairCheckInterval: -1
},
connector: {
name: 'Test',
debug: false
},
share: {
root: initType || 'Map'
}
}))
}
self.users = yield Promise.all(promises)
self.types = self.users.map(function (u) { return u.share.root })
return self.users
})
/*
Until async/await arrives in js, we use this function to wait for promises
by yielding them.
*/
function async (makeGenerator) {
return function (arg) {
var generator = makeGenerator.apply(this, arguments)
function handle (result) {
if (result.done) return Promise.resolve(result.value)
return Promise.resolve(result.value).then(function (res) {
return handle(generator.next(res))
}, function (err) {
return handle(generator.throw(err))
})
}
try {
return handle(generator.next())
} catch (ex) {
generator.throw(ex)
// return Promise.reject(ex)
}
}
}
g.async = async
function logUsers (self) {
if (self.constructor === Array) {
self = {users: self}
}
self.users[0].db.logTable()
self.users[1].db.logTable()
self.users[2].db.logTable()
}
g.logUsers = logUsers

View File

@@ -1,7 +1,5 @@
const CDELETE = 0 /* @flow */
const CINSERT = 1 'use strict'
const CLIST = 2
const CMAP = 3
/* /*
An operation also defines the structure of a type. This is why operation and An operation also defines the structure of a type. This is why operation and
@@ -23,20 +21,6 @@ const CMAP = 3
*/ */
export default function extendStruct (Y) { export default function extendStruct (Y) {
var Struct = { var Struct = {
binaryDecodeOperation: function (decoder) {
let code = decoder.peekUint8()
if (code === CDELETE) {
return Y.Struct.Delete.binaryDecode(decoder)
} else if (code === CINSERT) {
return Y.Struct.Insert.binaryDecode(decoder)
} else if (code === CLIST) {
return Y.Struct.List.binaryDecode(decoder)
} else if (code === CMAP) {
return Y.Struct.Map.binaryDecode(decoder)
} else {
throw new Error('Unable to decode operation!')
}
},
/* This is the only operation that is actually not a structure, because /* This is the only operation that is actually not a structure, because
it is not stored in the OS. This is why it _does not_ have an id it is not stored in the OS. This is why it _does not_ have an id
@@ -52,19 +36,6 @@ export default function extendStruct (Y) {
struct: 'Delete' struct: 'Delete'
} }
}, },
binaryEncode: function (encoder, op) {
encoder.writeUint8(CDELETE)
encoder.writeOpID(op.target)
encoder.writeVarUint(op.length || 0)
},
binaryDecode: function (decoder) {
decoder.skip8()
return {
target: decoder.readOpID(),
length: decoder.readVarUint(),
struct: 'Delete'
}
},
requiredOps: function (op) { requiredOps: function (op) {
return [] // [op.target] return [] // [op.target]
}, },
@@ -106,91 +77,6 @@ export default function extendStruct (Y) {
return e return e
}, },
binaryEncode: function (encoder, op) {
encoder.writeUint8(CINSERT)
// compute info property
let contentIsText = op.content != null && op.content.every(c => typeof c === 'string' && c.length === 1)
let originIsLeft = Y.utils.compareIds(op.left, op.origin)
let info =
(op.parentSub != null ? 1 : 0) |
(op.opContent != null ? 2 : 0) |
(contentIsText ? 4 : 0) |
(originIsLeft ? 8 : 0) |
(op.left != null ? 16 : 0) |
(op.right != null ? 32 : 0) |
(op.origin != null ? 64 : 0)
encoder.writeUint8(info)
encoder.writeOpID(op.id)
encoder.writeOpID(op.parent)
if (info & 16) {
encoder.writeOpID(op.left)
}
if (info & 32) {
encoder.writeOpID(op.right)
}
if (!originIsLeft && info & 64) {
encoder.writeOpID(op.origin)
}
if (info & 1) {
// write parentSub
encoder.writeVarString(op.parentSub)
}
if (info & 2) {
// write opContent
encoder.writeOpID(op.opContent)
} else if (info & 4) {
// write text
encoder.writeVarString(op.content.join(''))
} else {
// convert to JSON and write
encoder.writeVarString(JSON.stringify(op.content))
}
},
binaryDecode: function (decoder) {
let op = {
struct: 'Insert'
}
decoder.skip8()
// get info property
let info = decoder.readUint8()
op.id = decoder.readOpID()
op.parent = decoder.readOpID()
if (info & 16) {
op.left = decoder.readOpID()
} else {
op.left = null
}
if (info & 32) {
op.right = decoder.readOpID()
} else {
op.right = null
}
if (info & 8) {
// origin is left
op.origin = op.left
} else if (info & 64) {
op.origin = decoder.readOpID()
} else {
op.origin = null
}
if (info & 1) {
// has parentSub
op.parentSub = decoder.readVarString()
}
if (info & 2) {
// has opContent
op.opContent = decoder.readOpID()
} else if (info & 4) {
// has pure text content
op.content = decoder.readVarString().split('')
} else {
// has mixed content
let s = decoder.readVarString()
op.content = JSON.parse(s)
}
return op
},
requiredOps: function (op) { requiredOps: function (op) {
var ids = [] var ids = []
if (op.left != null) { if (op.left != null) {
@@ -413,31 +299,14 @@ export default function extendStruct (Y) {
id: op.id, id: op.id,
type: op.type type: op.type
} }
if (op.requires != null) {
e.requires = op.requires
}
if (op.info != null) { if (op.info != null) {
e.info = op.info e.info = op.info
} }
return e return e
}, },
binaryEncode: function (encoder, op) {
encoder.writeUint8(CLIST)
encoder.writeOpID(op.id)
encoder.writeVarString(op.type)
let info = op.info != null ? JSON.stringify(op.info) : ''
encoder.writeVarString(info)
},
binaryDecode: function (decoder) {
decoder.skip8()
let op = {
id: decoder.readOpID(),
type: decoder.readVarString(),
struct: 'List'
}
let info = decoder.readVarString()
if (info.length > 0) {
op.info = JSON.parse(info)
}
return op
},
requiredOps: function () { requiredOps: function () {
/* /*
var ids = [] var ids = []
@@ -512,36 +381,13 @@ export default function extendStruct (Y) {
map: {} // overwrite map!! map: {} // overwrite map!!
} }
if (op.requires != null) { if (op.requires != null) {
e.requires = op.require e.requires = op.requires
// TODO: !!
console.warn('requires is used! see same note above for List')
} }
if (op.info != null) { if (op.info != null) {
e.info = op.info e.info = op.info
} }
return e return e
}, },
binaryEncode: function (encoder, op) {
encoder.writeUint8(CMAP)
encoder.writeOpID(op.id)
encoder.writeVarString(op.type)
let info = op.info != null ? JSON.stringify(op.info) : ''
encoder.writeVarString(info)
},
binaryDecode: function (decoder) {
decoder.skip8()
let op = {
id: decoder.readOpID(),
type: decoder.readVarString(),
struct: 'Map',
map: {}
}
let info = decoder.readVarString()
if (info.length > 0) {
op.info = JSON.parse(info)
}
return op
},
requiredOps: function () { requiredOps: function () {
return [] return []
}, },

View File

@@ -1,3 +1,5 @@
/* @flow */
'use strict'
/* /*
Partial definition of a transaction Partial definition of a transaction
@@ -586,20 +588,11 @@ export default function extendTransaction (Y) {
apply a delete set in order to get apply a delete set in order to get
the state of the supplied ds the state of the supplied ds
*/ */
* applyDeleteSet (decoder) { * applyDeleteSet (ds) {
var deletions = [] var deletions = []
let dsLength = decoder.readUint32() for (var user in ds) {
for (let i = 0; i < dsLength; i++) { var dv = ds[user]
let user = decoder.readVarUint()
let dv = []
let dvLength = decoder.readVarUint()
for (let j = 0; j < dvLength; j++) {
let from = decoder.readVarUint()
let len = decoder.readVarUint()
let gc = decoder.readUint8() === 1
dv.push([from, len, gc])
}
var pos = 0 var pos = 0
var d = dv[pos] var d = dv[pos]
yield * this.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function * (n) { yield * this.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function * (n) {
@@ -693,34 +686,21 @@ export default function extendTransaction (Y) {
/* /*
A DeleteSet (ds) describes all the deleted ops in the OS A DeleteSet (ds) describes all the deleted ops in the OS
*/ */
* writeDeleteSet (encoder) { * getDeleteSet () {
var ds = new Map() var ds = {}
yield * this.ds.iterate(this, null, null, function * (n) { yield * this.ds.iterate(this, null, null, function * (n) {
var user = n.id[0] var user = n.id[0]
var counter = n.id[1] var counter = n.id[1]
var len = n.len var len = n.len
var gc = n.gc var gc = n.gc
var dv = ds.get(user) var dv = ds[user]
if (dv === void 0) { if (dv === void 0) {
dv = [] dv = []
ds.set(user, dv) ds[user] = dv
} }
dv.push([counter, len, gc]) dv.push([counter, len, gc])
}) })
let keys = Array.from(ds.keys()) return ds
encoder.writeUint32(keys.length)
for (var i = 0; i < keys.length; i++) {
let user = keys[i]
let deletions = ds.get(user)
encoder.writeVarUint(user)
encoder.writeVarUint(deletions.length)
for (var j = 0; j < deletions.length; j++) {
let del = deletions[j]
encoder.writeVarUint(del[0])
encoder.writeVarUint(del[1])
encoder.writeUint8(del[2] ? 1 : 0)
}
}
} }
* isDeleted (id) { * isDeleted (id) {
var n = yield * this.ds.findWithUpperBound(id) var n = yield * this.ds.findWithUpperBound(id)
@@ -732,8 +712,7 @@ export default function extendTransaction (Y) {
} }
* addOperation (op) { * addOperation (op) {
yield * this.os.put(op) yield * this.os.put(op)
// case op is created by this user, op is already broadcasted in applyCreatedOperations if (this.store.forwardAppliedOperations && typeof op.id[1] !== 'string') {
if (op.id[0] !== this.store.userId && this.store.forwardAppliedOperations && typeof op.id[1] !== 'string') {
// is connected, and this is not going to be send in addOperation // is connected, and this is not going to be send in addOperation
this.store.y.connector.broadcastOps([op]) this.store.y.connector.broadcastOps([op])
} }
@@ -842,7 +821,7 @@ export default function extendTransaction (Y) {
} }
* getOperation (id/* :any */)/* :Transaction<any> */ { * getOperation (id/* :any */)/* :Transaction<any> */ {
var o = yield * this.os.find(id) var o = yield * this.os.find(id)
if (id[0] !== 0xFFFFFF || o != null) { if (id[0] !== '_' || o != null) {
return o return o
} else { // type is string } else { // type is string
// generate this operation? // generate this operation?
@@ -899,18 +878,6 @@ export default function extendTransaction (Y) {
}) })
return ss return ss
} }
* writeStateSet (encoder) {
let lenPosition = encoder.pos
let len = 0
encoder.writeUint32(0)
yield * this.ss.iterate(this, null, null, function * (n) {
encoder.writeVarUint(n.id[0])
encoder.writeVarUint(n.clock)
len++
})
encoder.setUint32(lenPosition, len)
return len === 0
}
/* /*
Here, we make all missing operations executable for the receiving user. Here, we make all missing operations executable for the receiving user.
@@ -960,17 +927,17 @@ export default function extendTransaction (Y) {
* getOperations (startSS) { * getOperations (startSS) {
// TODO: use bounds here! // TODO: use bounds here!
if (startSS == null) { if (startSS == null) {
startSS = new Map() startSS = {}
} }
var send = [] var send = []
var endSV = yield * this.getStateVector() var endSV = yield * this.getStateVector()
for (let endState of endSV) { for (let endState of endSV) {
let user = endState.user let user = endState.user
if (user === 0xFFFFFF) { if (user === '_') {
continue continue
} }
let startPos = startSS.get(user) || 0 let startPos = startSS[user] || 0
if (startPos > 0) { if (startPos > 0) {
// There is a change that [user, startPos] is in a composed Insertion (with a smaller counter) // There is a change that [user, startPos] is in a composed Insertion (with a smaller counter)
// find out if that is the case // find out if that is the case
@@ -980,19 +947,19 @@ export default function extendTransaction (Y) {
startPos = firstMissing.id[1] startPos = firstMissing.id[1]
} }
} }
startSS.set(user, startPos) startSS[user] = startPos
} }
for (let endState of endSV) { for (let endState of endSV) {
let user = endState.user let user = endState.user
let startPos = startSS.get(user) let startPos = startSS[user]
if (user === 0xFFFFFF) { if (user === '_') {
continue continue
} }
yield * this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) { yield * this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
op = Y.Struct[op.struct].encode(op) op = Y.Struct[op.struct].encode(op)
if (op.struct !== 'Insert') { if (op.struct !== 'Insert') {
send.push(op) send.push(op)
} else if (op.right == null || op.right[1] < (startSS.get(op.right[0]) || 0)) { } else if (op.right == null || op.right[1] < (startSS[op.right[0]] || 0)) {
// case 1. op.right is known // case 1. op.right is known
// this case is only reached if op.right is known. // this case is only reached if op.right is known.
// => this is not called for op.left, as op.right is unknown // => this is not called for op.left, as op.right is unknown
@@ -1010,7 +977,7 @@ export default function extendTransaction (Y) {
op.left = null op.left = null
send.push(op) send.push(op)
/* not necessary, as o is already sent.. /* not necessary, as o is already sent..
if (!Y.utils.compareIds(o.id, op.id) && o.id[1] >= (startSS.get(o.id[0]) || 0)) { if (!Y.utils.compareIds(o.id, op.id) && o.id[1] >= (startSS[o.id[0]] || 0)) {
// o is not op && o is unknown // o is not op && o is unknown
o = Y.Struct[op.struct].encode(o) o = Y.Struct[op.struct].encode(o)
o.right = missingOrigins[missingOrigins.length - 1].id o.right = missingOrigins[missingOrigins.length - 1].id
@@ -1024,7 +991,7 @@ export default function extendTransaction (Y) {
while (missingOrigins.length > 0 && Y.utils.matchesId(o, missingOrigins[missingOrigins.length - 1].origin)) { while (missingOrigins.length > 0 && Y.utils.matchesId(o, missingOrigins[missingOrigins.length - 1].origin)) {
missingOrigins.pop() missingOrigins.pop()
} }
if (o.id[1] < (startSS.get(o.id[0]) || 0)) { if (o.id[1] < (startSS[o.id[0]] || 0)) {
// case 2. o is known // case 2. o is known
op.left = Y.utils.getLastId(o) op.left = Y.utils.getLastId(o)
send.push(op) send.push(op)
@@ -1056,48 +1023,28 @@ export default function extendTransaction (Y) {
} }
return send.reverse() return send.reverse()
} }
* writeOperations (encoder, decoder) {
let ss = new Map()
let ssLength = decoder.readUint32()
for (let i = 0; i < ssLength; i++) {
let user = decoder.readVarUint()
let clock = decoder.readVarUint()
ss.set(user, clock)
}
let ops = yield * this.getOperations(ss)
encoder.writeUint32(ops.length)
for (let i = 0; i < ops.length; i++) {
let op = ops[i]
Y.Struct[op.struct].binaryEncode(encoder, Y.Struct[op.struct].encode(op))
}
}
/* /*
* Get the plain untransformed operations from the database. * Get the plain untransformed operations from the database.
* You can apply these operations using .applyOperationsUntransformed(ops) * You can apply these operations using .applyOperationsUntransformed(ops)
* *
*/ */
* writeOperationsUntransformed (encoder) { * getOperationsUntransformed () {
let lenPosition = encoder.pos var ops = []
let len = 0
encoder.writeUint32(0) // placeholder
yield * this.os.iterate(this, null, null, function * (op) { yield * this.os.iterate(this, null, null, function * (op) {
if (op.id[0] !== 0xFFFFFF) { if (op.id[0] !== '_') {
len++ ops.push(op)
Y.Struct[op.struct].binaryEncode(encoder, Y.Struct[op.struct].encode(op))
} }
}) })
encoder.setUint32(lenPosition, len) return {
yield * this.writeStateSet(encoder) untransformed: ops
}
* applyOperationsUntransformed (decoder) {
let len = decoder.readUint32()
for (let i = 0; i < len; i++) {
let op = Y.Struct.binaryDecodeOperation(decoder)
yield * this.os.put(op)
} }
yield * this.os.iterate(this, null, null, function * (op) { }
if (op.parent != null) { * applyOperationsUntransformed (m, stateSet) {
var ops = m.untransformed
for (var i = 0; i < ops.length; i++) {
var op = ops[i]
// create, and modify parent, if it is created implicitly
if (op.parent != null && op.parent[0] === '_') {
if (op.struct === 'Insert') { if (op.struct === 'Insert') {
// update parents .map/start/end properties // update parents .map/start/end properties
if (op.parentSub != null && op.left == null) { if (op.parentSub != null && op.left == null) {
@@ -1117,14 +1064,12 @@ export default function extendTransaction (Y) {
} }
} }
} }
}) yield * this.os.put(op)
let stateSetLength = decoder.readUint32() }
for (let i = 0; i < stateSetLength; i++) { for (var user in stateSet) {
let user = decoder.readVarUint()
let clock = decoder.readVarUint()
yield * this.ss.put({ yield * this.ss.put({
id: [user], id: [user],
clock: clock clock: stateSet[user]
}) })
} }
} }

View File

@@ -1,7 +1,3 @@
/* globals crypto */
import { BinaryDecoder, BinaryEncoder } from './Encoding.js'
/* /*
EventHandler is an helper class for constructing custom types. EventHandler is an helper class for constructing custom types.
@@ -26,10 +22,7 @@ import { BinaryDecoder, BinaryEncoder } from './Encoding.js'
*/ */
export default function Utils (Y) { export default function Utils (Y) {
Y.utils = { Y.utils = {}
BinaryDecoder: BinaryDecoder,
BinaryEncoder: BinaryEncoder
}
Y.utils.bubbleEvent = function (type, event) { Y.utils.bubbleEvent = function (type, event) {
type.eventHandler.callEventListeners(event) type.eventHandler.callEventListeners(event)
@@ -825,19 +818,8 @@ export default function Utils (Y) {
} }
Y.utils.createSmallLookupBuffer = createSmallLookupBuffer Y.utils.createSmallLookupBuffer = createSmallLookupBuffer
function generateUserId () { // Generates a unique id, for use as a user id.
if (typeof crypto !== 'undefined' && crypto.getRandomValue != null) { // Thx to @jed for this script https://gist.github.com/jed/982883
// browser function generateGuid(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,generateGuid)} // eslint-disable-line
let arr = new Uint32Array(1) Y.utils.generateGuid = generateGuid
crypto.getRandomValues(arr)
return arr[0]
} else if (typeof crypto !== 'undefined' && crypto.randomBytes != null) {
// node
let buf = crypto.randomBytes(4)
return new Uint32Array(buf.buffer)[0]
} else {
return Math.ceil(Math.random() * 0xFFFFFFFF)
}
}
Y.utils.generateUserId = generateUserId
} }

View File

@@ -1,10 +1,9 @@
import debug from 'debug'
import extendConnector from './Connector.js' import extendConnector from './Connector.js'
import extendDatabase from './Database.js' import extendDatabase from './Database.js'
import extendTransaction from './Transaction.js' import extendTransaction from './Transaction.js'
import extendStruct from './Struct.js' import extendStruct from './Struct.js'
import extendUtils from './Utils.js' import extendUtils from './Utils.js'
import debug from 'debug'
import { formatYjsMessage, formatYjsMessageType } from './MessageHandler.js'
extendConnector(Y) extendConnector(Y)
extendDatabase(Y) extendDatabase(Y)
@@ -13,8 +12,6 @@ extendStruct(Y)
extendUtils(Y) extendUtils(Y)
Y.debug = debug Y.debug = debug
debug.formatters.Y = formatYjsMessage
debug.formatters.y = formatYjsMessageType
var requiringModules = {} var requiringModules = {}
@@ -172,7 +169,7 @@ class YConfig extends Y.utils.NamedEventHandler {
var typeName = typeConstructor.splice(0, 1) var typeName = typeConstructor.splice(0, 1)
var type = Y[typeName] var type = Y[typeName]
var typedef = type.typeDefinition var typedef = type.typeDefinition
var id = [0xFFFFFF, typedef.struct + '_' + typeName + '_' + propertyname + '_' + typeConstructor] var id = ['_', typedef.struct + '_' + typeName + '_' + propertyname + '_' + typeConstructor]
var args = [] var args = []
if (typeConstructor.length === 1) { if (typeConstructor.length === 1) {
try { try {

View File

@@ -1,229 +0,0 @@
import { test } from 'cutest'
import Chance from 'chance'
import Y from '../src/y.js'
import { BinaryEncoder, BinaryDecoder } from '../src/Encoding.js'
function testEncoding (t, write, read, val) {
let encoder = new BinaryEncoder()
write(encoder, val)
let reader = new BinaryDecoder(encoder.createBuffer())
let result = read(reader)
t.log(`string encode: ${JSON.stringify(val).length} bytes / binary encode: ${encoder.data.length} bytes`)
t.compare(val, result, 'Compare results')
}
const writeVarUint = (encoder, val) => encoder.writeVarUint(val)
const readVarUint = decoder => decoder.readVarUint()
test('varUint 1 byte', async function varUint1 (t) {
testEncoding(t, writeVarUint, readVarUint, 42)
})
test('varUint 2 bytes', async function varUint2 (t) {
testEncoding(t, writeVarUint, readVarUint, 1 << 9 | 3)
testEncoding(t, writeVarUint, readVarUint, 1 << 9 | 3)
})
test('varUint 3 bytes', async function varUint3 (t) {
testEncoding(t, writeVarUint, readVarUint, 1 << 17 | 1 << 9 | 3)
})
test('varUint 4 bytes', async function varUint4 (t) {
testEncoding(t, writeVarUint, readVarUint, 1 << 25 | 1 << 17 | 1 << 9 | 3)
})
test('varUint of 2839012934', async function varUint2839012934 (t) {
testEncoding(t, writeVarUint, readVarUint, 2839012934)
})
test('varUint random', async function varUintRandom (t) {
const chance = new Chance(t.getSeed() * Math.pow(Number.MAX_SAFE_INTEGER))
testEncoding(t, writeVarUint, readVarUint, chance.integer({min: 0, max: (1 << 28) - 1}))
})
test('varUint random user id', async function varUintRandomUserId (t) {
t.getSeed() // enforces that this test is repeated
testEncoding(t, writeVarUint, readVarUint, Y.utils.generateUserId())
})
const writeVarString = (encoder, val) => encoder.writeVarString(val)
const readVarString = decoder => decoder.readVarString()
test('varString', async function varString (t) {
testEncoding(t, writeVarString, readVarString, 'hello')
testEncoding(t, writeVarString, readVarString, 'test!')
testEncoding(t, writeVarString, readVarString, '☺☺☺')
testEncoding(t, writeVarString, readVarString, '1234')
})
test('varString random', async function varStringRandom (t) {
const chance = new Chance(t.getSeed() * 1000000000)
testEncoding(t, writeVarString, readVarString, chance.string())
})
const writeDelete = Y.Struct.Delete.binaryEncode
const readDelete = Y.Struct.Delete.binaryDecode
test('encode/decode Delete operation', async function binDelete (t) {
let op = {
target: [10, 3000],
length: 40000,
struct: 'Delete'
}
testEncoding(t, writeDelete, readDelete, op)
})
const writeInsert = Y.Struct.Insert.binaryEncode
const readInsert = Y.Struct.Insert.binaryDecode
test('encode/decode Insert operations', async function binInsert (t) {
testEncoding(t, writeInsert, readInsert, {
id: [1, 2],
right: [5, 6],
left: [3, 4],
origin: [7, 8],
parent: [9, 10],
struct: 'Insert',
content: ['a']
})
t.log('left === origin')
testEncoding(t, writeInsert, readInsert, {
id: [1, 2],
right: [5, 6],
left: [3, 4],
origin: [3, 4],
parent: [9, 10],
struct: 'Insert',
content: ['a']
})
t.log('parentsub')
testEncoding(t, writeInsert, readInsert, {
id: [1, 2],
right: [5, 6],
left: [3, 4],
origin: [3, 4],
parent: [9, 10],
parentSub: 'sub',
struct: 'Insert',
content: ['a']
})
t.log('opContent')
testEncoding(t, writeInsert, readInsert, {
id: [1, 2],
right: [5, 6],
left: [3, 4],
origin: [3, 4],
parent: [9, 10],
struct: 'Insert',
opContent: [1000, 10000]
})
t.log('mixed content')
testEncoding(t, writeInsert, readInsert, {
id: [1, 2],
right: [5, 6],
left: [3, 4],
origin: [3, 4],
parent: [9, 10],
struct: 'Insert',
content: ['a', 1]
})
t.log('origin is null')
testEncoding(t, writeInsert, readInsert, {
id: [1, 2],
right: [5, 6],
left: [3, 4],
origin: null,
parent: [9, 10],
struct: 'Insert',
content: ['a']
})
t.log('left = origin = right = null')
testEncoding(t, writeInsert, readInsert, {
id: [1, 2],
right: null,
left: null,
origin: null,
parent: [9, 10],
struct: 'Insert',
content: ['a']
})
})
const writeList = Y.Struct.List.binaryEncode
const readList = Y.Struct.List.binaryDecode
test('encode/decode List operations', async function binList (t) {
testEncoding(t, writeList, readList, {
struct: 'List',
id: [100, 33],
type: 'Array'
})
t.log('info is an object')
testEncoding(t, writeList, readList, {
struct: 'List',
id: [100, 33],
type: 'Array',
info: { prop: 'yay' }
})
t.log('info is a string')
testEncoding(t, writeList, readList, {
struct: 'List',
id: [100, 33],
type: 'Array',
info: 'hi'
})
t.log('info is a number')
testEncoding(t, writeList, readList, {
struct: 'List',
id: [100, 33],
type: 'Array',
info: 400
})
})
const writeMap = Y.Struct.Map.binaryEncode
const readMap = Y.Struct.Map.binaryDecode
test('encode/decode Map operations', async function binMap (t) {
testEncoding(t, writeMap, readMap, {
struct: 'Map',
id: [100, 33],
type: 'Map',
map: {}
})
t.log('info is an object')
testEncoding(t, writeMap, readMap, {
struct: 'Map',
id: [100, 33],
type: 'Map',
info: { prop: 'yay' },
map: {}
})
t.log('info is a string')
testEncoding(t, writeMap, readMap, {
struct: 'Map',
id: [100, 33],
type: 'Map',
map: {},
info: 'hi'
})
t.log('info is a number')
testEncoding(t, writeMap, readMap, {
struct: 'Map',
id: [100, 33],
type: 'Map',
map: {},
info: 400
})
})

View File

@@ -12,33 +12,6 @@ export let Y = _Y
Y.extend(yMemory, yArray, yMap, yTest) Y.extend(yMemory, yArray, yMap, yTest)
function * getStateSet () {
var ss = {}
yield * this.ss.iterate(this, null, null, function * (n) {
var user = n.id[0]
var clock = n.clock
ss[user] = clock
})
return ss
}
function * getDeleteSet () {
var ds = {}
yield * this.ds.iterate(this, null, null, function * (n) {
var user = n.id[0]
var counter = n.id[1]
var len = n.len
var gc = n.gc
var dv = ds[user]
if (dv === void 0) {
dv = []
ds[user] = dv
}
dv.push([counter, len, gc])
})
return ds
}
export async function garbageCollectUsers (t, users) { export async function garbageCollectUsers (t, users) {
await flushAll(t, users) await flushAll(t, users)
await Promise.all(users.map(u => u.db.emptyGarbageCollector())) await Promise.all(users.map(u => u.db.emptyGarbageCollector()))
@@ -87,14 +60,10 @@ export async function compareUsers (t, users) {
var data = await Promise.all(users.map(async (u) => { var data = await Promise.all(users.map(async (u) => {
var data = {} var data = {}
u.db.requestTransaction(function * () { u.db.requestTransaction(function * () {
let ops = [] var os = yield * this.getOperationsUntransformed()
yield * this.os.iterate(this, null, null, function * (op) {
ops.push(Y.Struct[op.struct].encode(op))
})
data.os = {} data.os = {}
for (let i = 0; i < ops.length; i++) { for (let i = 0; i < os.untransformed.length; i++) {
let op = ops[i] let op = os.untransformed[i]
op = Y.Struct[op.struct].encode(op) op = Y.Struct[op.struct].encode(op)
delete op.origin delete op.origin
/* /*
@@ -110,8 +79,8 @@ export async function compareUsers (t, users) {
data.os[JSON.stringify(op.id)] = op data.os[JSON.stringify(op.id)] = op
} }
} }
data.ds = yield * getDeleteSet.apply(this) data.ds = yield * this.getDeleteSet()
data.ss = yield * getStateSet.apply(this) data.ss = yield * this.getStateSet()
}) })
await u.db.whenTransactionsFinished() await u.db.whenTransactionsFinished()
return data return data
@@ -133,9 +102,9 @@ export async function initArrays (t, opts) {
var result = { var result = {
users: [] users: []
} }
var share = Object.assign({ flushHelper: 'Map', array: 'Array', map: 'Map' }, opts.share) var share = Object.assign({ flushHelper: 'Map', array: 'Array' }, opts.share)
var chance = opts.chance || new Chance(t.getSeed() * 1000000000) var chance = opts.chance || new Chance(t.getSeed() * 1000000000)
var connector = Object.assign({ room: 'debugging_' + t.name, generateUserId: false, testContext: t, chance }, opts.connector) var connector = Object.assign({ room: 'debugging_' + t.name, testContext: t, chance }, opts.connector)
for (let i = 0; i < opts.users; i++) { for (let i = 0; i < opts.users; i++) {
let dbOpts let dbOpts
let connOpts let connOpts

View File

@@ -1,53 +1,54 @@
/* global Y */ /* global Y */
import { wait } from './helper.js' import { wait } from './helper.js'
import { formatYjsMessage } from '../src/MessageHandler.js'
var rooms = {} var rooms = {}
export class TestRoom { export class TestRoom {
constructor (roomname) { constructor (roomname) {
this.room = roomname this.room = roomname
this.users = new Map() this.users = {}
this.nextUserId = 0 this.nextUserId = 0
} }
join (connector) { join (connector) {
if (connector.userId == null) { if (connector.userId == null) {
connector.setUserId(this.nextUserId++) connector.setUserId('' + (this.nextUserId++))
} }
this.users.forEach((user, uid) => { Object.keys(this.users).forEach(uid => {
let user = this.users[uid]
if (user.role === 'master' || connector.role === 'master') { if (user.role === 'master' || connector.role === 'master') {
this.users.get(uid).userJoined(connector.userId, connector.role) this.users[uid].userJoined(connector.userId, connector.role)
connector.userJoined(uid, this.users.get(uid).role) connector.userJoined(uid, this.users[uid].role)
} }
}) })
this.users.set(connector.userId, connector) this.users[connector.userId] = connector
} }
leave (connector) { leave (connector) {
this.users.delete(connector.userId) delete this.users[connector.userId]
this.users.forEach(user => { Object.keys(this.users).forEach(uid => {
user.userLeft(connector.userId) this.users[uid].userLeft(connector.userId)
}) })
} }
send (sender, receiver, m) { send (sender, receiver, m) {
var user = this.users.get(receiver) m = JSON.parse(JSON.stringify(m))
var user = this.users[receiver]
if (user != null) { if (user != null) {
user.receiveMessage(sender, m) user.receiveMessage(sender, m)
} }
} }
broadcast (sender, m) { broadcast (sender, m) {
this.users.forEach((user, receiver) => { Object.keys(this.users).forEach(receiver => {
this.send(sender, receiver, m) this.send(sender, receiver, m)
}) })
} }
async flushAll (users) { async flushAll (users) {
let flushing = true let flushing = true
let allUserIds = Array.from(this.users.keys()) let allUserIds = Object.keys(this.users)
if (users == null) { if (users == null) {
users = allUserIds.map(id => this.users.get(id).y) users = allUserIds.map(id => this.users[id].y)
} }
while (flushing) { while (flushing) {
await wait(10) await wait(10)
let res = await Promise.all(allUserIds.map(id => this.users.get(id)._flushAll(users))) let res = await Promise.all(allUserIds.map(id => this.users[id]._flushAll(users)))
flushing = res.some(status => status === 'flushing') flushing = res.some(status => status === 'flushing')
} }
} }
@@ -81,25 +82,14 @@ export default function extendTestConnector (Y) {
this.testRoom.leave(this) this.testRoom.leave(this)
return super.disconnect() return super.disconnect()
} }
logBufferParsed () {
console.log(' === Logging buffer of user ' + this.userId + ' === ')
for (let [user, conn] of this.connections) {
console.log(` ${user}:`)
for (let i = 0; i < conn.buffer.length; i++) {
console.log(formatYjsMessage(conn.buffer[i]))
}
}
}
reconnect () { reconnect () {
this.testRoom.join(this) this.testRoom.join(this)
return super.reconnect() return super.reconnect()
} }
send (uid, message) { send (uid, message) {
super.send(uid, message)
this.testRoom.send(this.userId, uid, message) this.testRoom.send(this.userId, uid, message)
} }
broadcast (message) { broadcast (message) {
super.broadcast(message)
this.testRoom.broadcast(this.userId, message) this.testRoom.broadcast(this.userId, message)
} }
async whenSynced (f) { async whenSynced (f) {
@@ -119,10 +109,10 @@ export default function extendTestConnector (Y) {
}) })
} }
receiveMessage (sender, m) { receiveMessage (sender, m) {
if (this.userId !== sender && this.connections.has(sender)) { if (this.userId !== sender && this.connections[sender] != null) {
var buffer = this.connections.get(sender).buffer var buffer = this.connections[sender].buffer
if (buffer == null) { if (buffer == null) {
buffer = this.connections.get(sender).buffer = [] buffer = this.connections[sender].buffer = []
} }
buffer.push(m) buffer.push(m)
if (this.chance.bool({likelihood: 30})) { if (this.chance.bool({likelihood: 30})) {
@@ -137,13 +127,13 @@ export default function extendTestConnector (Y) {
async _flushAll (flushUsers) { async _flushAll (flushUsers) {
if (flushUsers.some(u => u.connector.userId === this.userId)) { if (flushUsers.some(u => u.connector.userId === this.userId)) {
// this one needs to sync with every other user // this one needs to sync with every other user
flushUsers = Array.from(this.connections.keys()).map(uid => this.testRoom.users.get(uid).y) flushUsers = Object.keys(this.connections).map(id => this.testRoom.users[id].y)
} }
var finished = [] var finished = []
for (let i = 0; i < flushUsers.length; i++) { for (let i = 0; i < flushUsers.length; i++) {
let userId = flushUsers[i].connector.userId let userId = flushUsers[i].connector.userId
if (userId !== this.userId && this.connections.has(userId)) { if (userId !== this.userId && this.connections[userId] != null) {
let buffer = this.connections.get(userId).buffer let buffer = this.connections[userId].buffer
if (buffer != null) { if (buffer != null) {
var messages = buffer.splice(0) var messages = buffer.splice(0)
for (let j = 0; j < messages.length; j++) { for (let j = 0; j < messages.length; j++) {

9
y.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2283
y.node.js

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

606
y.test.js
View File

@@ -13126,41 +13126,25 @@ class BinaryEncoder {
constructor () { constructor () {
this.data = []; this.data = [];
} }
get pos () {
return this.data.length
}
createBuffer () { createBuffer () {
return Uint8Array.from(this.data).buffer return Uint8Array.from(this.data).buffer
} }
writeUint8 (num) { writeUint8 (num) {
this.data.push(num & bits8); this.data.push(num & bits8);
} }
setUint8 (pos, num) {
this.data[pos] = num & bits8;
}
writeUint16 (num) { writeUint16 (num) {
this.data.push(num & bits8, (num >>> 8) & bits8); this.data.push(num & bits8, (num >> 8) & bits8);
}
setUint16 (pos, num) {
this.data[pos] = num & bits8;
this.data[pos + 1] = (num >>> 8) & bits8;
} }
writeUint32 (num) { writeUint32 (num) {
for (let i = 0; i < 4; i++) { for (let i = 0; i < 4; i++) {
this.data.push(num & bits8); this.data.push(num & bits8);
num >>>= 8; num >>= 8;
}
}
setUint32 (pos, num) {
for (let i = 0; i < 4; i++) {
this.data[pos + i] = num & bits8;
num >>>= 8;
} }
} }
writeVarUint (num) { writeVarUint (num) {
while (num >= 0b10000000) { while (num >= 0b10000000) {
this.data.push(0b10000000 | (bits7 & num)); this.data.push(0b10000000 | (bits7 & num));
num >>>= 7; num >>= 7;
} }
this.data.push(bits7 & num); this.data.push(bits7 & num);
} }
@@ -13173,13 +13157,8 @@ class BinaryEncoder {
} }
} }
writeOpID (id) { writeOpID (id) {
let user = id[0]; this.writeVarUint(id[0]);
this.writeVarUint(user); this.writeVarUint(id[1]);
if (user !== 0xFFFFFF) {
this.writeVarUint(id[1]);
} else {
this.writeVarString(id[1]);
}
} }
} }
@@ -13194,15 +13173,6 @@ class BinaryDecoder {
readUint8 () { readUint8 () {
return this.uint8arr[this.pos++] return this.uint8arr[this.pos++]
} }
readUint32 () {
let uint =
this.uint8arr[this.pos] +
(this.uint8arr[this.pos + 1] << 8) +
(this.uint8arr[this.pos + 2] << 16) +
(this.uint8arr[this.pos + 3] << 24);
this.pos += 4;
return uint
}
peekUint8 () { peekUint8 () {
return this.uint8arr[this.pos] return this.uint8arr[this.pos]
} }
@@ -13214,10 +13184,7 @@ class BinaryDecoder {
num = num | ((r & bits7) << len); num = num | ((r & bits7) << len);
len += 7; len += 7;
if (r < 1 << 7) { if (r < 1 << 7) {
return num >>> 0 // return unsigned number! return num
}
if (len > 35) {
throw new Error('Integer out of range!')
} }
} }
} }
@@ -13236,197 +13203,11 @@ class BinaryDecoder {
return s return s
} }
readOpID () { readOpID () {
let user = this.readVarUint(); return [this.readVarUint(), this.readVarUint()]
if (user !== 0xFFFFFF) {
return [user, this.readVarUint()]
} else {
return [user, this.readVarString()]
}
} }
} }
function formatYjsMessage (buffer) { function canWrite (auth) { return auth === 'write' }
let decoder = new BinaryDecoder(buffer);
decoder.readVarString(); // read roomname
let type = decoder.readVarString();
let strBuilder = [];
strBuilder.push('\n === ' + type + ' ===\n');
if (type === 'update') {
logMessageUpdate(decoder, strBuilder);
} else if (type === 'sync step 1') {
logMessageSyncStep1(decoder, strBuilder);
} else if (type === 'sync step 2') {
logMessageSyncStep2(decoder, strBuilder);
} else {
strBuilder.push('-- Unknown message type - probably an encoding issue!!!');
}
return strBuilder.join('')
}
function formatYjsMessageType (buffer) {
let decoder = new BinaryDecoder(buffer);
decoder.readVarString(); // roomname
return decoder.readVarString()
}
async function logMessageUpdate (decoder, strBuilder) {
let len = decoder.readUint32();
for (let i = 0; i < len; i++) {
strBuilder.push(JSON.stringify(Y.Struct.binaryDecodeOperation(decoder)) + '\n');
}
}
async function computeMessageUpdate (decoder, encoder, conn) {
if (conn.y.db.forwardAppliedOperations) {
let messagePosition = decoder.pos;
let len = decoder.readUint32();
let delops = [];
for (let i = 0; i < len; i++) {
let op = Y.Struct.binaryDecodeOperation(decoder);
if (op.struct === 'Delete') {
delops.push(op);
}
}
if (delops.length > 0) {
conn.broadcastOps(delops);
}
decoder.pos = messagePosition;
}
conn.y.db.applyOperations(decoder);
}
function sendSyncStep1 (conn, syncUser) {
conn.y.db.requestTransaction(function * () {
let encoder = new BinaryEncoder();
encoder.writeVarString(conn.opts.room || '');
encoder.writeVarString('sync step 1');
encoder.writeVarString(conn.authInfo || '');
encoder.writeVarUint(conn.protocolVersion);
let preferUntransformed = conn.preferUntransformed && this.os.length === 0; // TODO: length may not be defined
encoder.writeUint8(preferUntransformed ? 1 : 0);
yield * this.writeStateSet(encoder);
conn.send(syncUser, encoder.createBuffer());
});
}
function logMessageSyncStep1 (decoder, strBuilder) {
let auth = decoder.readVarString();
let protocolVersion = decoder.readVarUint();
let preferUntransformed = decoder.readUint8() === 1;
strBuilder.push(`
- auth: "${auth}"
- protocolVersion: ${protocolVersion}
- preferUntransformed: ${preferUntransformed}
`);
logSS(decoder, strBuilder);
}
async function computeMessageSyncStep1 (decoder, encoder, conn, senderConn, sender) {
let protocolVersion = decoder.readVarUint();
let preferUntransformed = decoder.readUint8() === 1;
// check protocol version
if (protocolVersion !== conn.protocolVersion) {
console.warn(
`You tried to sync with a yjs instance that has a different protocol version
(You: ${protocolVersion}, Client: ${protocolVersion}).
The sync was stopped. You need to upgrade your dependencies (especially Yjs & the Connector)!
`);
conn.y.destroy();
}
// send sync step 2
conn.y.db.requestTransaction(function * () {
encoder.writeVarString('sync step 2');
encoder.writeVarString(conn.authInfo || '');
let emptyStateSet = this.ds.length === 0; // TODO: length may not always be available
if (preferUntransformed && emptyStateSet) {
encoder.writeUint8(1);
yield * this.writeOperationsUntransformed(encoder);
} else {
encoder.writeUint8(0);
yield * this.writeOperations(encoder, decoder);
}
yield * this.writeDeleteSet(encoder);
conn.send(senderConn.uid, encoder.createBuffer());
senderConn.receivedSyncStep2 = true;
});
if (conn.role === 'slave') {
sendSyncStep1(conn, sender);
}
await conn.y.db.whenTransactionsFinished();
}
function logSS (decoder, strBuilder) {
strBuilder.push(' == SS: \n');
let len = decoder.readUint32();
for (let i = 0; i < len; i++) {
let user = decoder.readVarUint();
let clock = decoder.readVarUint();
strBuilder.push(` ${user}: ${clock}\n`);
}
}
function logOS (decoder, strBuilder) {
strBuilder.push(' == OS: \n');
let len = decoder.readUint32();
for (let i = 0; i < len; i++) {
let op = Y.Struct.binaryDecodeOperation(decoder);
strBuilder.push(JSON.stringify(op) + '\n');
}
}
function logDS (decoder, strBuilder) {
strBuilder.push(' == DS: \n');
let len = decoder.readUint32();
for (let i = 0; i < len; i++) {
let user = decoder.readVarUint();
strBuilder.push(` User: ${user}: `);
let len2 = decoder.readVarUint();
for (let j = 0; j < len2; j++) {
let from = decoder.readVarUint();
let to = decoder.readVarUint();
let gc = decoder.readUint8() === 1;
strBuilder.push(`[${from}, ${to}, ${gc}]`);
}
}
}
function logMessageSyncStep2 (decoder, strBuilder) {
strBuilder.push(' - auth: ' + decoder.readVarString() + '\n');
let osTransformed = decoder.readUint8() === 1;
strBuilder.push(' - osUntransformed: ' + osTransformed + '\n');
logOS(decoder, strBuilder);
if (osTransformed) {
logSS(decoder, strBuilder);
}
logDS(decoder, strBuilder);
}
async function computeMessageSyncStep2 (decoder, encoder, conn, senderConn, sender) {
var db = conn.y.db;
let defer = senderConn.syncStep2;
// apply operations first
db.requestTransaction(function * () {
let osUntransformed = decoder.readUint8();
if (osUntransformed === 1) {
yield * this.applyOperationsUntransformed(decoder);
} else {
this.store.applyOperations(decoder);
}
});
// then apply ds
await db.whenTransactionsFinished();
db.requestTransaction(function * () {
yield * this.applyDeleteSet(decoder);
});
await db.whenTransactionsFinished();
conn._setSyncedWith(sender);
defer.resolve();
}
function extendConnector (Y/* :any */) { function extendConnector (Y/* :any */) {
class AbstractConnector { class AbstractConnector {
@@ -13439,7 +13220,6 @@ function extendConnector (Y/* :any */) {
if (opts == null) { if (opts == null) {
opts = {}; opts = {};
} }
this.opts = opts;
// Prefer to receive untransformed operations. This does only work if // Prefer to receive untransformed operations. This does only work if
// this client receives operations from only one other client. // this client receives operations from only one other client.
// In particular, this does not work with y-webrtc. // In particular, this does not work with y-webrtc.
@@ -13470,6 +13250,15 @@ function extendConnector (Y/* :any */) {
this.setUserId(Y.utils.generateUserId()); this.setUserId(Y.utils.generateUserId());
} }
} }
resetAuth (auth) {
if (this.authInfo !== auth) {
this.authInfo = auth;
this.broadcast({
type: 'auth',
auth: this.authInfo
});
}
}
reconnect () { reconnect () {
this.log('reconnecting..'); this.log('reconnecting..');
return this.y.db.startGarbageCollector() return this.y.db.startGarbageCollector()
@@ -13537,9 +13326,7 @@ function extendConnector (Y/* :any */) {
this.connections.set(user, { this.connections.set(user, {
uid: user, uid: user,
isSynced: false, isSynced: false,
role: role, role: role
processAfterAuth: [],
receivedSyncStep2: false
}); });
let defer = {}; let defer = {};
defer.promise = new Promise(function (resolve) { defer.resolve = resolve; }); defer.promise = new Promise(function (resolve) { defer.resolve = resolve; });
@@ -13565,8 +13352,8 @@ function extendConnector (Y/* :any */) {
} }
} }
findNextSyncTarget () { findNextSyncTarget () {
if (this.currentSyncTarget != null || this.role === 'slave') { if (this.currentSyncTarget != null) {
return // "The current sync has not finished or this is controlled by a master!" return // "The current sync has not finished!"
} }
var syncUser = null; var syncUser = null;
@@ -13579,7 +13366,19 @@ function extendConnector (Y/* :any */) {
var conn = this; var conn = this;
if (syncUser != null) { if (syncUser != null) {
this.currentSyncTarget = syncUser; this.currentSyncTarget = syncUser;
sendSyncStep1(this, syncUser); this.y.db.requestTransaction(function * () {
var stateSet = yield * this.getStateSet();
var answer = {
type: 'sync step 1',
stateSet: stateSet,
protocolVersion: conn.protocolVersion,
auth: conn.authInfo
};
if (conn.preferUntransformed && Object.keys(stateSet).length === 0) {
answer.preferUntransformed = true;
}
conn.send(syncUser, answer);
});
} else { } else {
if (!conn.isSynced) { if (!conn.isSynced) {
this.y.db.requestTransaction(function * () { this.y.db.requestTransaction(function * () {
@@ -13598,13 +13397,13 @@ function extendConnector (Y/* :any */) {
} }
} }
} }
send (uid, buffer) { send (uid, message) {
this.log('%s: Send \'%y\' to %s', this.userId, buffer, uid); this.log('%s: Send \'%s\' to %s', this.userId, message.type, uid);
this.logMessage('Message: %Y', buffer); this.logMessage('Message: %j', message);
} }
broadcast (buffer) { broadcast (message) {
this.log('%s: Broadcast \'%y\'', this.userId, buffer); this.log('%s: Broadcast \'%s\'', this.userId, message.type);
this.logMessage('Message: %Y', buffer); this.logMessage('Message: %j', message);
} }
/* /*
Buffer operations, and broadcast them when ready. Buffer operations, and broadcast them when ready.
@@ -13616,18 +13415,11 @@ function extendConnector (Y/* :any */) {
var self = this; var self = this;
function broadcastOperations () { function broadcastOperations () {
if (self.broadcastOpBuffer.length > 0) { if (self.broadcastOpBuffer.length > 0) {
let encoder = new BinaryEncoder(); self.broadcast({
encoder.writeVarString(self.opts.room); type: 'update',
encoder.writeVarString('update'); ops: self.broadcastOpBuffer
let ops = self.broadcastOpBuffer; });
self.broadcastOpBuffer = []; self.broadcastOpBuffer = [];
let length = ops.length;
encoder.writeUint32(length);
for (var i = 0; i < length; i++) {
let op = ops[i];
Y.Struct[op.struct].binaryEncode(encoder, op);
}
self.broadcast(encoder.createBuffer());
} }
} }
if (this.broadcastOpBuffer.length === 0) { if (this.broadcastOpBuffer.length === 0) {
@@ -13646,56 +13438,24 @@ function extendConnector (Y/* :any */) {
} }
let decoder = new BinaryDecoder(buffer); let decoder = new BinaryDecoder(buffer);
let encoder = new BinaryEncoder(); let encoder = new BinaryEncoder();
let roomname = decoder.readVarString(); // read room name
encoder.writeVarString(roomname);
let messageType = decoder.readVarString(); let messageType = decoder.readVarString();
let senderConn = this.connections.get(sender); let senderConn = this.connections.get(sender);
this.log('%s: Receive \'%s\' from %s', this.userId, messageType, sender); this.log('%s: Receive \'%s\' from %s', this.userId, messageType, sender);
this.logMessage('Message: %Y', buffer); this.logMessage('Message: %yjsMessage', buffer);
if (senderConn == null) { if (!this.connections.has(sender)) {
throw new Error('Received message from unknown peer!') throw new Error('Received message from unknown peer!')
} }
if (messageType === 'sync step 1') {
if (messageType === 'sync step 1' || messageType === 'sync step 2') { // read is handled in computeMessageSyncStep1
let auth = decoder.readVarUint(); return await computeMessageSyncStep1(decoder, encoder, this, senderConn)
if (senderConn.auth == null) {
// check auth
let authPermissions = await this.checkAuth(auth, this.y, sender);
senderConn.auth = authPermissions;
this.y.emit('userAuthenticated', {
user: senderConn.uid,
auth: authPermissions
});
senderConn.syncStep2.promise.then(() => {
if (senderConn.processAfterAuth == null) {
return
}
for (let i = 0; i < senderConn.processAfterAuth.length; i++) {
let m = senderConn.processAfterAuth[i];
this.receiveMessage(m[0], m[1]);
}
senderConn.processAfterAuth = null;
});
}
} }
if (senderConn.auth == null) { if (messageType === 'sync step 2' && canWrite(auth)) {
senderConn.processAfterAuth.push([sender, buffer]); return await computeMessageSyncStep2(decoder, encoder, this, senderConn)
return } else if (message.type === 'update' && canWrite(auth)) {
} return await computeMessageUpdate(decoder, encoder, this, senderConn)
if (messageType === 'sync step 1' && (senderConn.auth === 'write' || senderConn.auth === 'read')) {
// cannot wait for sync step 1 to finish, because we may wait for sync step 2 in sync step 1 (->lock)
computeMessageSyncStep1(decoder, encoder, this, senderConn, sender);
return this.y.db.whenTransactionsFinished()
} else if (messageType === 'sync step 2' && senderConn.auth === 'write') {
return computeMessageSyncStep2(decoder, encoder, this, senderConn, sender)
} else if (messageType === 'update' && senderConn.auth === 'write') {
return computeMessageUpdate(decoder, encoder, this, senderConn, sender)
} else {
console.error('Unable to receive message');
} }
} }
_setSyncedWith (user) { _setSyncedWith (user) {
@@ -14108,12 +13868,10 @@ function extendDatabase (Y /* :any */) {
* check if it is an expected op (otherwise wait for it) * check if it is an expected op (otherwise wait for it)
* check if was deleted, apply a delete operation after op was applied * check if was deleted, apply a delete operation after op was applied
*/ */
applyOperations (decoder) { apply (ops) {
this.opsReceivedTimestamp = new Date(); this.opsReceivedTimestamp = new Date();
let length = decoder.readUint32(); for (var i = 0; i < ops.length; i++) {
var o = ops[i];
for (var i = 0; i < length; i++) {
let o = Y.Struct.binaryDecodeOperation(decoder);
if (o.id == null || o.id[0] !== this.y.connector.userId) { if (o.id == null || o.id[0] !== this.y.connector.userId) {
var required = Y.Struct[o.struct].requiredOps(o); var required = Y.Struct[o.struct].requiredOps(o);
if (o.requires != null) { if (o.requires != null) {
@@ -14394,7 +14152,7 @@ function extendDatabase (Y /* :any */) {
op.type = typedefinition[0].name; op.type = typedefinition[0].name;
this.requestTransaction(function * () { this.requestTransaction(function * () {
if (op.id[0] === 0xFFFFFF) { if (op.id[0] === -1) {
yield * this.setOperation(op); yield * this.setOperation(op);
} else { } else {
yield * this.applyCreatedOperations([op]); yield * this.applyCreatedOperations([op]);
@@ -14408,79 +14166,7 @@ function extendDatabase (Y /* :any */) {
Y.AbstractDatabase = AbstractDatabase; Y.AbstractDatabase = AbstractDatabase;
} }
/* /* @flow */
Partial definition of a transaction
A transaction provides all the the async functionality on a database.
By convention, a transaction has the following properties:
* ss for StateSet
* os for OperationStore
* ds for DeleteStore
A transaction must also define the following methods:
* checkDeleteStoreForState(state)
- When increasing the state of a user, an operation with an higher id
may already be garbage collected, and therefore it will never be received.
update the state to reflect this knowledge. This won't call a method to save the state!
* getDeleteSet(id)
- Get the delete set in a readable format:
{
"userX": [
[5,1], // starting from position 5, one operations is deleted
[9,4] // starting from position 9, four operations are deleted
],
"userY": ...
}
* getOpsFromDeleteSet(ds) -- TODO: just call this.deleteOperation(id) here
- get a set of deletions that need to be applied in order to get to
achieve the state of the supplied ds
* setOperation(op)
- write `op` to the database.
Note: this is allowed to return an in-memory object.
E.g. the Memory adapter returns the object that it has in-memory.
Changing values on this object will be stored directly in the database
without calling this function. Therefore,
setOperation may have no functionality in some adapters. This also has
implications on the way we use operations that were served from the database.
We try not to call copyObject, if not necessary.
* addOperation(op)
- add an operation to the database.
This may only be called once for every op.id
Must return a function that returns the next operation in the database (ordered by id)
* getOperation(id)
* removeOperation(id)
- remove an operation from the database. This is called when an operation
is garbage collected.
* setState(state)
- `state` is of the form
{
user: "1",
clock: 4
} <- meaning that we have four operations from user "1"
(with these id's respectively: 0, 1, 2, and 3)
* getState(user)
* getStateVector()
- Get the state of the OS in the form
[{
user: "userX",
clock: 11
},
..
]
* getStateSet()
- Get the state of the OS in the form
{
"userX": 11,
"userY": 22
}
* getOperations(startSS)
- Get the all the operations that are necessary in order to achive the
stateSet of this user, starting from a stateSet supplied by another user
* makeOperationReady(ss, op)
- this is called only by `getOperations(startSS)`. It makes an operation
applyable on a given SS.
*/
function extendTransaction (Y) { function extendTransaction (Y) {
class TransactionInterface { class TransactionInterface {
/* :: /* ::
@@ -14503,7 +14189,7 @@ function extendTransaction (Y) {
send.push(Y.Struct[op.struct].encode(op)); send.push(Y.Struct[op.struct].encode(op));
} }
} }
if (send.length > 0) { // TODO: && !this.store.forwardAppliedOperations (but then i don't send delete ops) if (this.store.y.connector.isSynced && send.length > 0) { // TODO: && !this.store.forwardAppliedOperations (but then i don't send delete ops)
// is connected, and this is not going to be send in addOperation // is connected, and this is not going to be send in addOperation
this.store.y.connector.broadcastOps(send); this.store.y.connector.broadcastOps(send);
} }
@@ -14995,20 +14681,12 @@ function extendTransaction (Y) {
apply a delete set in order to get apply a delete set in order to get
the state of the supplied ds the state of the supplied ds
*/ */
* applyDeleteSet (decoder) { * applyDeleteSet (ds) {
var deletions = []; var deletions = [];
let dsLength = decoder.readUint32(); for (var user in ds) {
for (let i = 0; i < dsLength; i++) { var dv = ds[user];
let user = decoder.readVarUint(); user = Number.parseInt(user, 10);
let dv = [];
let dvLength = decoder.readVarUint();
for (let j = 0; j < dvLength; j++) {
let from = decoder.readVarUint();
let len = decoder.readVarUint();
let gc = decoder.readUint8() === 1;
dv.push([from, len, gc]);
}
var pos = 0; var pos = 0;
var d = dv[pos]; var d = dv[pos];
yield * this.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function * (n) { yield * this.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function * (n) {
@@ -15102,34 +14780,21 @@ function extendTransaction (Y) {
/* /*
A DeleteSet (ds) describes all the deleted ops in the OS A DeleteSet (ds) describes all the deleted ops in the OS
*/ */
* writeDeleteSet (encoder) { * getDeleteSet () {
var ds = new Map(); var ds = {};
yield * this.ds.iterate(this, null, null, function * (n) { yield * this.ds.iterate(this, null, null, function * (n) {
var user = n.id[0]; var user = n.id[0];
var counter = n.id[1]; var counter = n.id[1];
var len = n.len; var len = n.len;
var gc = n.gc; var gc = n.gc;
var dv = ds.get(user); var dv = ds[user];
if (dv === void 0) { if (dv === void 0) {
dv = []; dv = [];
ds.set(user, dv); ds[user] = dv;
} }
dv.push([counter, len, gc]); dv.push([counter, len, gc]);
}); });
let keys = Array.from(ds.keys()); return ds
encoder.writeUint32(keys.length);
for (var i = 0; i < keys.length; i++) {
let user = keys[i];
let deletions = ds.get(user);
encoder.writeVarUint(user);
encoder.writeVarUint(deletions.length);
for (var j = 0; j < deletions.length; j++) {
let del = deletions[j];
encoder.writeVarUint(del[0]);
encoder.writeVarUint(del[1]);
encoder.writeUint8(del[2] ? 1 : 0);
}
}
} }
* isDeleted (id) { * isDeleted (id) {
var n = yield * this.ds.findWithUpperBound(id); var n = yield * this.ds.findWithUpperBound(id);
@@ -15141,8 +14806,7 @@ function extendTransaction (Y) {
} }
* addOperation (op) { * addOperation (op) {
yield * this.os.put(op); yield * this.os.put(op);
// case op is created by this user, op is already broadcasted in applyCreatedOperations if (this.store.y.connector.isSynced && this.store.forwardAppliedOperations && typeof op.id[1] !== 'string') {
if (op.id[0] !== this.store.userId && this.store.forwardAppliedOperations && typeof op.id[1] !== 'string') {
// is connected, and this is not going to be send in addOperation // is connected, and this is not going to be send in addOperation
this.store.y.connector.broadcastOps([op]); this.store.y.connector.broadcastOps([op]);
} }
@@ -15251,11 +14915,11 @@ function extendTransaction (Y) {
} }
* getOperation (id/* :any */)/* :Transaction<any> */ { * getOperation (id/* :any */)/* :Transaction<any> */ {
var o = yield * this.os.find(id); var o = yield * this.os.find(id);
if (id[0] !== 0xFFFFFF || o != null) { if (id[0] !== -1 || o != null) {
return o return o
} else { // type is string } else { // type is string
// generate this operation? // generate this operation?
var comp = id[1].split('_'); var comp = id[1].split(-1);
if (comp.length > 1) { if (comp.length > 1) {
var struct = comp[0]; var struct = comp[0];
var op = Y.Struct[struct].create(id); var op = Y.Struct[struct].create(id);
@@ -15308,18 +14972,6 @@ function extendTransaction (Y) {
}); });
return ss return ss
} }
* writeStateSet (encoder) {
let lenPosition = encoder.pos;
let len = 0;
encoder.writeUint32(0);
yield * this.ss.iterate(this, null, null, function * (n) {
encoder.writeVarUint(n.id[0]);
encoder.writeVarUint(n.clock);
len++;
});
encoder.setUint32(lenPosition, len);
return len === 0
}
/* /*
Here, we make all missing operations executable for the receiving user. Here, we make all missing operations executable for the receiving user.
@@ -15369,17 +15021,17 @@ function extendTransaction (Y) {
* getOperations (startSS) { * getOperations (startSS) {
// TODO: use bounds here! // TODO: use bounds here!
if (startSS == null) { if (startSS == null) {
startSS = new Map(); startSS = {};
} }
var send = []; var send = [];
var endSV = yield * this.getStateVector(); var endSV = yield * this.getStateVector();
for (let endState of endSV) { for (let endState of endSV) {
let user = endState.user; let user = endState.user;
if (user === 0xFFFFFF) { if (user === -1) {
continue continue
} }
let startPos = startSS.get(user) || 0; let startPos = startSS[user] || 0;
if (startPos > 0) { if (startPos > 0) {
// There is a change that [user, startPos] is in a composed Insertion (with a smaller counter) // There is a change that [user, startPos] is in a composed Insertion (with a smaller counter)
// find out if that is the case // find out if that is the case
@@ -15389,19 +15041,19 @@ function extendTransaction (Y) {
startPos = firstMissing.id[1]; startPos = firstMissing.id[1];
} }
} }
startSS.set(user, startPos); startSS[user] = startPos;
} }
for (let endState of endSV) { for (let endState of endSV) {
let user = endState.user; let user = endState.user;
let startPos = startSS.get(user); let startPos = startSS[user];
if (user === 0xFFFFFF) { if (user === -1) {
continue continue
} }
yield * this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) { yield * this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
op = Y.Struct[op.struct].encode(op); op = Y.Struct[op.struct].encode(op);
if (op.struct !== 'Insert') { if (op.struct !== 'Insert') {
send.push(op); send.push(op);
} else if (op.right == null || op.right[1] < (startSS.get(op.right[0]) || 0)) { } else if (op.right == null || op.right[1] < (startSS[op.right[0]] || 0)) {
// case 1. op.right is known // case 1. op.right is known
// this case is only reached if op.right is known. // this case is only reached if op.right is known.
// => this is not called for op.left, as op.right is unknown // => this is not called for op.left, as op.right is unknown
@@ -15419,7 +15071,7 @@ function extendTransaction (Y) {
op.left = null; op.left = null;
send.push(op); send.push(op);
/* not necessary, as o is already sent.. /* not necessary, as o is already sent..
if (!Y.utils.compareIds(o.id, op.id) && o.id[1] >= (startSS.get(o.id[0]) || 0)) { if (!Y.utils.compareIds(o.id, op.id) && o.id[1] >= (startSS[o.id[0]] || 0)) {
// o is not op && o is unknown // o is not op && o is unknown
o = Y.Struct[op.struct].encode(o) o = Y.Struct[op.struct].encode(o)
o.right = missingOrigins[missingOrigins.length - 1].id o.right = missingOrigins[missingOrigins.length - 1].id
@@ -15433,7 +15085,7 @@ function extendTransaction (Y) {
while (missingOrigins.length > 0 && Y.utils.matchesId(o, missingOrigins[missingOrigins.length - 1].origin)) { while (missingOrigins.length > 0 && Y.utils.matchesId(o, missingOrigins[missingOrigins.length - 1].origin)) {
missingOrigins.pop(); missingOrigins.pop();
} }
if (o.id[1] < (startSS.get(o.id[0]) || 0)) { if (o.id[1] < (startSS[o.id[0]] || 0)) {
// case 2. o is known // case 2. o is known
op.left = Y.utils.getLastId(o); op.left = Y.utils.getLastId(o);
send.push(op); send.push(op);
@@ -15465,22 +15117,6 @@ function extendTransaction (Y) {
} }
return send.reverse() return send.reverse()
} }
* writeOperations (encoder, decoder) {
let ss = new Map();
let ssLength = decoder.readUint32();
for (let i = 0; i < ssLength; i++) {
let user = decoder.readVarUint();
let clock = decoder.readVarUint();
ss.set(user, clock);
}
let ops = yield * this.getOperations(ss);
encoder.writeUint32(ops.length);
for (let i = 0; i < ops.length; i++) {
let op = ops[i];
Y.Struct[op.struct].binaryEncode(encoder, Y.Struct[op.struct].encode(op));
}
}
/* /*
* Get the plain untransformed operations from the database. * Get the plain untransformed operations from the database.
* You can apply these operations using .applyOperationsUntransformed(ops) * You can apply these operations using .applyOperationsUntransformed(ops)
@@ -15491,22 +15127,21 @@ function extendTransaction (Y) {
let len = 0; let len = 0;
encoder.writeUint32(0); // placeholder encoder.writeUint32(0); // placeholder
yield * this.os.iterate(this, null, null, function * (op) { yield * this.os.iterate(this, null, null, function * (op) {
if (op.id[0] !== 0xFFFFFF) { if (op.id[0] !== -1) {
len++; len++;
Y.Struct[op.struct].binaryEncode(encoder, Y.Struct[op.struct].encode(op)); Y.Struct[op.struct].binaryEncode(encoder, Y.Struct[op.struct].encode(op));
} }
}); });
encoder.setUint32(lenPosition, len); encoder.dataview.writeUint32(lenPosition, len);
yield * this.writeStateSet(encoder);
} }
* applyOperationsUntransformed (decoder) { * applyOperationsUntransformed (decoder) {
let len = decoder.readUint32(); let len = decoder.readUint32();
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
let op = Y.Struct.binaryDecodeOperation(decoder); let op = decoder.readOperation();
yield * this.os.put(op); yield * this.os.put(op);
} }
yield * this.os.iterate(this, null, null, function * (op) { yield * this.os.iterate(this, null, null, function * (op) {
if (op.parent != null && op.parent[0] === 0xFFFFFF) { if (op.parent != null && op.parent[0] === -1) {
if (op.struct === 'Insert') { if (op.struct === 'Insert') {
// update parents .map/start/end properties // update parents .map/start/end properties
if (op.parentSub != null && op.left == null) { if (op.parentSub != null && op.left == null) {
@@ -15596,20 +15231,6 @@ const CMAP = 3;
*/ */
function extendStruct (Y) { function extendStruct (Y) {
var Struct = { var Struct = {
binaryDecodeOperation: function (decoder) {
let code = decoder.peekUint8();
if (code === CDELETE) {
return Y.Struct.Delete.binaryDecode(decoder)
} else if (code === CINSERT) {
return Y.Struct.Insert.binaryDecode(decoder)
} else if (code === CLIST) {
return Y.Struct.List.binaryDecode(decoder)
} else if (code === CMAP) {
return Y.Struct.Map.binaryDecode(decoder)
} else {
throw new Error('Unable to decode operation!')
}
},
/* This is the only operation that is actually not a structure, because /* This is the only operation that is actually not a structure, because
it is not stored in the OS. This is why it _does not_ have an id it is not stored in the OS. This is why it _does not_ have an id
@@ -16145,11 +15766,33 @@ function extendStruct (Y) {
Y.Struct = Struct; Y.Struct = Struct;
} }
/* global crypto */
/*
EventHandler is an helper class for constructing custom types.
Why: When constructing custom types, you sometimes want your types to work
synchronous: E.g.
``` Synchronous
mytype.setSomething("yay")
mytype.getSomething() === "yay"
```
versus
``` Asynchronous
mytype.setSomething("yay")
mytype.getSomething() === undefined
mytype.waitForSomething().then(function(){
mytype.getSomething() === "yay"
})
```
The structures usually work asynchronously (you have to wait for the
database request to finish). EventHandler helps you to make your type
synchronous.
*/
function Utils (Y) { function Utils (Y) {
Y.utils = { Y.utils = {};
BinaryDecoder: BinaryDecoder,
BinaryEncoder: BinaryEncoder
};
Y.utils.bubbleEvent = function (type, event) { Y.utils.bubbleEvent = function (type, event) {
type.eventHandler.callEventListeners(event); type.eventHandler.callEventListeners(event);
@@ -16946,18 +16589,9 @@ function Utils (Y) {
Y.utils.createSmallLookupBuffer = createSmallLookupBuffer; Y.utils.createSmallLookupBuffer = createSmallLookupBuffer;
function generateUserId () { function generateUserId () {
if (typeof crypto !== 'undefined' && crypto.getRandomValue != null) { let arr = new Uint32Array(1);
// browser crypto.getRandomValues(arr);
let arr = new Uint32Array(1); return arr[0]
crypto.getRandomValues(arr);
return arr[0]
} else if (typeof crypto !== 'undefined' && crypto.randomBytes != null) {
// node
let buf = crypto.randomBytes(4);
return new Uint32Array(buf.buffer)[0]
} else {
return Math.ceil(Math.random() * 0xFFFFFFFF)
}
} }
Y.utils.generateUserId = generateUserId; Y.utils.generateUserId = generateUserId;
} }
@@ -17514,8 +17148,6 @@ extendStruct(Y);
Utils(Y); Utils(Y);
Y.debug = browser; Y.debug = browser;
browser.formatters.Y = formatYjsMessage;
browser.formatters.y = formatYjsMessageType;
var requiringModules = {}; var requiringModules = {};
@@ -17673,7 +17305,7 @@ class YConfig extends Y.utils.NamedEventHandler {
var typeName = typeConstructor.splice(0, 1); var typeName = typeConstructor.splice(0, 1);
var type = Y[typeName]; var type = Y[typeName];
var typedef = type.typeDefinition; var typedef = type.typeDefinition;
var id = [0xFFFFFF, typedef.struct + '_' + typeName + '_' + propertyname + '_' + typeConstructor]; var id = [-1, typedef.struct + -1 + typeName + -1 + propertyname + -1 + typeConstructor];
var args = []; var args = [];
if (typeConstructor.length === 1) { if (typeConstructor.length === 1) {
try { try {
@@ -17762,7 +17394,6 @@ test('varUint 1 byte', async function varUint1 (t) {
test('varUint 2 bytes', async function varUint2 (t) { test('varUint 2 bytes', async function varUint2 (t) {
testEncoding(t, writeVarUint, readVarUint, 1 << 9 | 3); testEncoding(t, writeVarUint, readVarUint, 1 << 9 | 3);
testEncoding(t, writeVarUint, readVarUint, 1 << 9 | 3);
}); });
test('varUint 3 bytes', async function varUint3 (t) { test('varUint 3 bytes', async function varUint3 (t) {
testEncoding(t, writeVarUint, readVarUint, 1 << 17 | 1 << 9 | 3); testEncoding(t, writeVarUint, readVarUint, 1 << 17 | 1 << 9 | 3);
@@ -17772,20 +17403,11 @@ test('varUint 4 bytes', async function varUint4 (t) {
testEncoding(t, writeVarUint, readVarUint, 1 << 25 | 1 << 17 | 1 << 9 | 3); testEncoding(t, writeVarUint, readVarUint, 1 << 25 | 1 << 17 | 1 << 9 | 3);
}); });
test('varUint of 2839012934', async function varUint2839012934 (t) {
testEncoding(t, writeVarUint, readVarUint, 2839012934);
});
test('varUint random', async function varUintRandom (t) { test('varUint random', async function varUintRandom (t) {
const chance = new chance_1(t.getSeed() * Math.pow(Number.MAX_SAFE_INTEGER)); const chance = new chance_1(t.getSeed() * 1000000000);
testEncoding(t, writeVarUint, readVarUint, chance.integer({min: 0, max: (1 << 28) - 1})); testEncoding(t, writeVarUint, readVarUint, chance.integer({min: 0, max: (1 << 28) - 1}));
}); });
test('varUint random user id', async function varUintRandomUserId (t) {
t.getSeed(); // enforces that this test is repeated
testEncoding(t, writeVarUint, readVarUint, Y.utils.generateUserId());
});
const writeVarString = (encoder, val) => encoder.writeVarString(val); const writeVarString = (encoder, val) => encoder.writeVarString(val);
const readVarString = decoder => decoder.readVarString(); const readVarString = decoder => decoder.readVarString();

File diff suppressed because one or more lines are too long