ugly-app 0.1.304 → 0.1.305

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1681 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for CollabServer.ts</title>
7
+ <meta charset="utf-8" />
8
+ <link rel="stylesheet" href="prettify.css" />
9
+ <link rel="stylesheet" href="base.css" />
10
+ <link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12
+ <style type='text/css'>
13
+ .coverage-summary .sorter {
14
+ background-image: url(sort-arrow-sprite.png);
15
+ }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <div class='wrapper'>
21
+ <div class='pad1'>
22
+ <h1><a href="index.html">All files</a> CollabServer.ts</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">99.5% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>200/201</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">92.92% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>92/99</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">100% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>29/29</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">100% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>185/185</span>
50
+ </div>
51
+
52
+
53
+ </div>
54
+ <p class="quiet">
55
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
+ </p>
57
+ <template id="filterTemplate">
58
+ <div class="quiet">
59
+ Filter:
60
+ <input type="search" id="fileSearch">
61
+ </div>
62
+ </template>
63
+ </div>
64
+ <div class='status-line high'></div>
65
+ <pre><table class="coverage">
66
+ <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
+ <a name='L2'></a><a href='#L2'>2</a>
68
+ <a name='L3'></a><a href='#L3'>3</a>
69
+ <a name='L4'></a><a href='#L4'>4</a>
70
+ <a name='L5'></a><a href='#L5'>5</a>
71
+ <a name='L6'></a><a href='#L6'>6</a>
72
+ <a name='L7'></a><a href='#L7'>7</a>
73
+ <a name='L8'></a><a href='#L8'>8</a>
74
+ <a name='L9'></a><a href='#L9'>9</a>
75
+ <a name='L10'></a><a href='#L10'>10</a>
76
+ <a name='L11'></a><a href='#L11'>11</a>
77
+ <a name='L12'></a><a href='#L12'>12</a>
78
+ <a name='L13'></a><a href='#L13'>13</a>
79
+ <a name='L14'></a><a href='#L14'>14</a>
80
+ <a name='L15'></a><a href='#L15'>15</a>
81
+ <a name='L16'></a><a href='#L16'>16</a>
82
+ <a name='L17'></a><a href='#L17'>17</a>
83
+ <a name='L18'></a><a href='#L18'>18</a>
84
+ <a name='L19'></a><a href='#L19'>19</a>
85
+ <a name='L20'></a><a href='#L20'>20</a>
86
+ <a name='L21'></a><a href='#L21'>21</a>
87
+ <a name='L22'></a><a href='#L22'>22</a>
88
+ <a name='L23'></a><a href='#L23'>23</a>
89
+ <a name='L24'></a><a href='#L24'>24</a>
90
+ <a name='L25'></a><a href='#L25'>25</a>
91
+ <a name='L26'></a><a href='#L26'>26</a>
92
+ <a name='L27'></a><a href='#L27'>27</a>
93
+ <a name='L28'></a><a href='#L28'>28</a>
94
+ <a name='L29'></a><a href='#L29'>29</a>
95
+ <a name='L30'></a><a href='#L30'>30</a>
96
+ <a name='L31'></a><a href='#L31'>31</a>
97
+ <a name='L32'></a><a href='#L32'>32</a>
98
+ <a name='L33'></a><a href='#L33'>33</a>
99
+ <a name='L34'></a><a href='#L34'>34</a>
100
+ <a name='L35'></a><a href='#L35'>35</a>
101
+ <a name='L36'></a><a href='#L36'>36</a>
102
+ <a name='L37'></a><a href='#L37'>37</a>
103
+ <a name='L38'></a><a href='#L38'>38</a>
104
+ <a name='L39'></a><a href='#L39'>39</a>
105
+ <a name='L40'></a><a href='#L40'>40</a>
106
+ <a name='L41'></a><a href='#L41'>41</a>
107
+ <a name='L42'></a><a href='#L42'>42</a>
108
+ <a name='L43'></a><a href='#L43'>43</a>
109
+ <a name='L44'></a><a href='#L44'>44</a>
110
+ <a name='L45'></a><a href='#L45'>45</a>
111
+ <a name='L46'></a><a href='#L46'>46</a>
112
+ <a name='L47'></a><a href='#L47'>47</a>
113
+ <a name='L48'></a><a href='#L48'>48</a>
114
+ <a name='L49'></a><a href='#L49'>49</a>
115
+ <a name='L50'></a><a href='#L50'>50</a>
116
+ <a name='L51'></a><a href='#L51'>51</a>
117
+ <a name='L52'></a><a href='#L52'>52</a>
118
+ <a name='L53'></a><a href='#L53'>53</a>
119
+ <a name='L54'></a><a href='#L54'>54</a>
120
+ <a name='L55'></a><a href='#L55'>55</a>
121
+ <a name='L56'></a><a href='#L56'>56</a>
122
+ <a name='L57'></a><a href='#L57'>57</a>
123
+ <a name='L58'></a><a href='#L58'>58</a>
124
+ <a name='L59'></a><a href='#L59'>59</a>
125
+ <a name='L60'></a><a href='#L60'>60</a>
126
+ <a name='L61'></a><a href='#L61'>61</a>
127
+ <a name='L62'></a><a href='#L62'>62</a>
128
+ <a name='L63'></a><a href='#L63'>63</a>
129
+ <a name='L64'></a><a href='#L64'>64</a>
130
+ <a name='L65'></a><a href='#L65'>65</a>
131
+ <a name='L66'></a><a href='#L66'>66</a>
132
+ <a name='L67'></a><a href='#L67'>67</a>
133
+ <a name='L68'></a><a href='#L68'>68</a>
134
+ <a name='L69'></a><a href='#L69'>69</a>
135
+ <a name='L70'></a><a href='#L70'>70</a>
136
+ <a name='L71'></a><a href='#L71'>71</a>
137
+ <a name='L72'></a><a href='#L72'>72</a>
138
+ <a name='L73'></a><a href='#L73'>73</a>
139
+ <a name='L74'></a><a href='#L74'>74</a>
140
+ <a name='L75'></a><a href='#L75'>75</a>
141
+ <a name='L76'></a><a href='#L76'>76</a>
142
+ <a name='L77'></a><a href='#L77'>77</a>
143
+ <a name='L78'></a><a href='#L78'>78</a>
144
+ <a name='L79'></a><a href='#L79'>79</a>
145
+ <a name='L80'></a><a href='#L80'>80</a>
146
+ <a name='L81'></a><a href='#L81'>81</a>
147
+ <a name='L82'></a><a href='#L82'>82</a>
148
+ <a name='L83'></a><a href='#L83'>83</a>
149
+ <a name='L84'></a><a href='#L84'>84</a>
150
+ <a name='L85'></a><a href='#L85'>85</a>
151
+ <a name='L86'></a><a href='#L86'>86</a>
152
+ <a name='L87'></a><a href='#L87'>87</a>
153
+ <a name='L88'></a><a href='#L88'>88</a>
154
+ <a name='L89'></a><a href='#L89'>89</a>
155
+ <a name='L90'></a><a href='#L90'>90</a>
156
+ <a name='L91'></a><a href='#L91'>91</a>
157
+ <a name='L92'></a><a href='#L92'>92</a>
158
+ <a name='L93'></a><a href='#L93'>93</a>
159
+ <a name='L94'></a><a href='#L94'>94</a>
160
+ <a name='L95'></a><a href='#L95'>95</a>
161
+ <a name='L96'></a><a href='#L96'>96</a>
162
+ <a name='L97'></a><a href='#L97'>97</a>
163
+ <a name='L98'></a><a href='#L98'>98</a>
164
+ <a name='L99'></a><a href='#L99'>99</a>
165
+ <a name='L100'></a><a href='#L100'>100</a>
166
+ <a name='L101'></a><a href='#L101'>101</a>
167
+ <a name='L102'></a><a href='#L102'>102</a>
168
+ <a name='L103'></a><a href='#L103'>103</a>
169
+ <a name='L104'></a><a href='#L104'>104</a>
170
+ <a name='L105'></a><a href='#L105'>105</a>
171
+ <a name='L106'></a><a href='#L106'>106</a>
172
+ <a name='L107'></a><a href='#L107'>107</a>
173
+ <a name='L108'></a><a href='#L108'>108</a>
174
+ <a name='L109'></a><a href='#L109'>109</a>
175
+ <a name='L110'></a><a href='#L110'>110</a>
176
+ <a name='L111'></a><a href='#L111'>111</a>
177
+ <a name='L112'></a><a href='#L112'>112</a>
178
+ <a name='L113'></a><a href='#L113'>113</a>
179
+ <a name='L114'></a><a href='#L114'>114</a>
180
+ <a name='L115'></a><a href='#L115'>115</a>
181
+ <a name='L116'></a><a href='#L116'>116</a>
182
+ <a name='L117'></a><a href='#L117'>117</a>
183
+ <a name='L118'></a><a href='#L118'>118</a>
184
+ <a name='L119'></a><a href='#L119'>119</a>
185
+ <a name='L120'></a><a href='#L120'>120</a>
186
+ <a name='L121'></a><a href='#L121'>121</a>
187
+ <a name='L122'></a><a href='#L122'>122</a>
188
+ <a name='L123'></a><a href='#L123'>123</a>
189
+ <a name='L124'></a><a href='#L124'>124</a>
190
+ <a name='L125'></a><a href='#L125'>125</a>
191
+ <a name='L126'></a><a href='#L126'>126</a>
192
+ <a name='L127'></a><a href='#L127'>127</a>
193
+ <a name='L128'></a><a href='#L128'>128</a>
194
+ <a name='L129'></a><a href='#L129'>129</a>
195
+ <a name='L130'></a><a href='#L130'>130</a>
196
+ <a name='L131'></a><a href='#L131'>131</a>
197
+ <a name='L132'></a><a href='#L132'>132</a>
198
+ <a name='L133'></a><a href='#L133'>133</a>
199
+ <a name='L134'></a><a href='#L134'>134</a>
200
+ <a name='L135'></a><a href='#L135'>135</a>
201
+ <a name='L136'></a><a href='#L136'>136</a>
202
+ <a name='L137'></a><a href='#L137'>137</a>
203
+ <a name='L138'></a><a href='#L138'>138</a>
204
+ <a name='L139'></a><a href='#L139'>139</a>
205
+ <a name='L140'></a><a href='#L140'>140</a>
206
+ <a name='L141'></a><a href='#L141'>141</a>
207
+ <a name='L142'></a><a href='#L142'>142</a>
208
+ <a name='L143'></a><a href='#L143'>143</a>
209
+ <a name='L144'></a><a href='#L144'>144</a>
210
+ <a name='L145'></a><a href='#L145'>145</a>
211
+ <a name='L146'></a><a href='#L146'>146</a>
212
+ <a name='L147'></a><a href='#L147'>147</a>
213
+ <a name='L148'></a><a href='#L148'>148</a>
214
+ <a name='L149'></a><a href='#L149'>149</a>
215
+ <a name='L150'></a><a href='#L150'>150</a>
216
+ <a name='L151'></a><a href='#L151'>151</a>
217
+ <a name='L152'></a><a href='#L152'>152</a>
218
+ <a name='L153'></a><a href='#L153'>153</a>
219
+ <a name='L154'></a><a href='#L154'>154</a>
220
+ <a name='L155'></a><a href='#L155'>155</a>
221
+ <a name='L156'></a><a href='#L156'>156</a>
222
+ <a name='L157'></a><a href='#L157'>157</a>
223
+ <a name='L158'></a><a href='#L158'>158</a>
224
+ <a name='L159'></a><a href='#L159'>159</a>
225
+ <a name='L160'></a><a href='#L160'>160</a>
226
+ <a name='L161'></a><a href='#L161'>161</a>
227
+ <a name='L162'></a><a href='#L162'>162</a>
228
+ <a name='L163'></a><a href='#L163'>163</a>
229
+ <a name='L164'></a><a href='#L164'>164</a>
230
+ <a name='L165'></a><a href='#L165'>165</a>
231
+ <a name='L166'></a><a href='#L166'>166</a>
232
+ <a name='L167'></a><a href='#L167'>167</a>
233
+ <a name='L168'></a><a href='#L168'>168</a>
234
+ <a name='L169'></a><a href='#L169'>169</a>
235
+ <a name='L170'></a><a href='#L170'>170</a>
236
+ <a name='L171'></a><a href='#L171'>171</a>
237
+ <a name='L172'></a><a href='#L172'>172</a>
238
+ <a name='L173'></a><a href='#L173'>173</a>
239
+ <a name='L174'></a><a href='#L174'>174</a>
240
+ <a name='L175'></a><a href='#L175'>175</a>
241
+ <a name='L176'></a><a href='#L176'>176</a>
242
+ <a name='L177'></a><a href='#L177'>177</a>
243
+ <a name='L178'></a><a href='#L178'>178</a>
244
+ <a name='L179'></a><a href='#L179'>179</a>
245
+ <a name='L180'></a><a href='#L180'>180</a>
246
+ <a name='L181'></a><a href='#L181'>181</a>
247
+ <a name='L182'></a><a href='#L182'>182</a>
248
+ <a name='L183'></a><a href='#L183'>183</a>
249
+ <a name='L184'></a><a href='#L184'>184</a>
250
+ <a name='L185'></a><a href='#L185'>185</a>
251
+ <a name='L186'></a><a href='#L186'>186</a>
252
+ <a name='L187'></a><a href='#L187'>187</a>
253
+ <a name='L188'></a><a href='#L188'>188</a>
254
+ <a name='L189'></a><a href='#L189'>189</a>
255
+ <a name='L190'></a><a href='#L190'>190</a>
256
+ <a name='L191'></a><a href='#L191'>191</a>
257
+ <a name='L192'></a><a href='#L192'>192</a>
258
+ <a name='L193'></a><a href='#L193'>193</a>
259
+ <a name='L194'></a><a href='#L194'>194</a>
260
+ <a name='L195'></a><a href='#L195'>195</a>
261
+ <a name='L196'></a><a href='#L196'>196</a>
262
+ <a name='L197'></a><a href='#L197'>197</a>
263
+ <a name='L198'></a><a href='#L198'>198</a>
264
+ <a name='L199'></a><a href='#L199'>199</a>
265
+ <a name='L200'></a><a href='#L200'>200</a>
266
+ <a name='L201'></a><a href='#L201'>201</a>
267
+ <a name='L202'></a><a href='#L202'>202</a>
268
+ <a name='L203'></a><a href='#L203'>203</a>
269
+ <a name='L204'></a><a href='#L204'>204</a>
270
+ <a name='L205'></a><a href='#L205'>205</a>
271
+ <a name='L206'></a><a href='#L206'>206</a>
272
+ <a name='L207'></a><a href='#L207'>207</a>
273
+ <a name='L208'></a><a href='#L208'>208</a>
274
+ <a name='L209'></a><a href='#L209'>209</a>
275
+ <a name='L210'></a><a href='#L210'>210</a>
276
+ <a name='L211'></a><a href='#L211'>211</a>
277
+ <a name='L212'></a><a href='#L212'>212</a>
278
+ <a name='L213'></a><a href='#L213'>213</a>
279
+ <a name='L214'></a><a href='#L214'>214</a>
280
+ <a name='L215'></a><a href='#L215'>215</a>
281
+ <a name='L216'></a><a href='#L216'>216</a>
282
+ <a name='L217'></a><a href='#L217'>217</a>
283
+ <a name='L218'></a><a href='#L218'>218</a>
284
+ <a name='L219'></a><a href='#L219'>219</a>
285
+ <a name='L220'></a><a href='#L220'>220</a>
286
+ <a name='L221'></a><a href='#L221'>221</a>
287
+ <a name='L222'></a><a href='#L222'>222</a>
288
+ <a name='L223'></a><a href='#L223'>223</a>
289
+ <a name='L224'></a><a href='#L224'>224</a>
290
+ <a name='L225'></a><a href='#L225'>225</a>
291
+ <a name='L226'></a><a href='#L226'>226</a>
292
+ <a name='L227'></a><a href='#L227'>227</a>
293
+ <a name='L228'></a><a href='#L228'>228</a>
294
+ <a name='L229'></a><a href='#L229'>229</a>
295
+ <a name='L230'></a><a href='#L230'>230</a>
296
+ <a name='L231'></a><a href='#L231'>231</a>
297
+ <a name='L232'></a><a href='#L232'>232</a>
298
+ <a name='L233'></a><a href='#L233'>233</a>
299
+ <a name='L234'></a><a href='#L234'>234</a>
300
+ <a name='L235'></a><a href='#L235'>235</a>
301
+ <a name='L236'></a><a href='#L236'>236</a>
302
+ <a name='L237'></a><a href='#L237'>237</a>
303
+ <a name='L238'></a><a href='#L238'>238</a>
304
+ <a name='L239'></a><a href='#L239'>239</a>
305
+ <a name='L240'></a><a href='#L240'>240</a>
306
+ <a name='L241'></a><a href='#L241'>241</a>
307
+ <a name='L242'></a><a href='#L242'>242</a>
308
+ <a name='L243'></a><a href='#L243'>243</a>
309
+ <a name='L244'></a><a href='#L244'>244</a>
310
+ <a name='L245'></a><a href='#L245'>245</a>
311
+ <a name='L246'></a><a href='#L246'>246</a>
312
+ <a name='L247'></a><a href='#L247'>247</a>
313
+ <a name='L248'></a><a href='#L248'>248</a>
314
+ <a name='L249'></a><a href='#L249'>249</a>
315
+ <a name='L250'></a><a href='#L250'>250</a>
316
+ <a name='L251'></a><a href='#L251'>251</a>
317
+ <a name='L252'></a><a href='#L252'>252</a>
318
+ <a name='L253'></a><a href='#L253'>253</a>
319
+ <a name='L254'></a><a href='#L254'>254</a>
320
+ <a name='L255'></a><a href='#L255'>255</a>
321
+ <a name='L256'></a><a href='#L256'>256</a>
322
+ <a name='L257'></a><a href='#L257'>257</a>
323
+ <a name='L258'></a><a href='#L258'>258</a>
324
+ <a name='L259'></a><a href='#L259'>259</a>
325
+ <a name='L260'></a><a href='#L260'>260</a>
326
+ <a name='L261'></a><a href='#L261'>261</a>
327
+ <a name='L262'></a><a href='#L262'>262</a>
328
+ <a name='L263'></a><a href='#L263'>263</a>
329
+ <a name='L264'></a><a href='#L264'>264</a>
330
+ <a name='L265'></a><a href='#L265'>265</a>
331
+ <a name='L266'></a><a href='#L266'>266</a>
332
+ <a name='L267'></a><a href='#L267'>267</a>
333
+ <a name='L268'></a><a href='#L268'>268</a>
334
+ <a name='L269'></a><a href='#L269'>269</a>
335
+ <a name='L270'></a><a href='#L270'>270</a>
336
+ <a name='L271'></a><a href='#L271'>271</a>
337
+ <a name='L272'></a><a href='#L272'>272</a>
338
+ <a name='L273'></a><a href='#L273'>273</a>
339
+ <a name='L274'></a><a href='#L274'>274</a>
340
+ <a name='L275'></a><a href='#L275'>275</a>
341
+ <a name='L276'></a><a href='#L276'>276</a>
342
+ <a name='L277'></a><a href='#L277'>277</a>
343
+ <a name='L278'></a><a href='#L278'>278</a>
344
+ <a name='L279'></a><a href='#L279'>279</a>
345
+ <a name='L280'></a><a href='#L280'>280</a>
346
+ <a name='L281'></a><a href='#L281'>281</a>
347
+ <a name='L282'></a><a href='#L282'>282</a>
348
+ <a name='L283'></a><a href='#L283'>283</a>
349
+ <a name='L284'></a><a href='#L284'>284</a>
350
+ <a name='L285'></a><a href='#L285'>285</a>
351
+ <a name='L286'></a><a href='#L286'>286</a>
352
+ <a name='L287'></a><a href='#L287'>287</a>
353
+ <a name='L288'></a><a href='#L288'>288</a>
354
+ <a name='L289'></a><a href='#L289'>289</a>
355
+ <a name='L290'></a><a href='#L290'>290</a>
356
+ <a name='L291'></a><a href='#L291'>291</a>
357
+ <a name='L292'></a><a href='#L292'>292</a>
358
+ <a name='L293'></a><a href='#L293'>293</a>
359
+ <a name='L294'></a><a href='#L294'>294</a>
360
+ <a name='L295'></a><a href='#L295'>295</a>
361
+ <a name='L296'></a><a href='#L296'>296</a>
362
+ <a name='L297'></a><a href='#L297'>297</a>
363
+ <a name='L298'></a><a href='#L298'>298</a>
364
+ <a name='L299'></a><a href='#L299'>299</a>
365
+ <a name='L300'></a><a href='#L300'>300</a>
366
+ <a name='L301'></a><a href='#L301'>301</a>
367
+ <a name='L302'></a><a href='#L302'>302</a>
368
+ <a name='L303'></a><a href='#L303'>303</a>
369
+ <a name='L304'></a><a href='#L304'>304</a>
370
+ <a name='L305'></a><a href='#L305'>305</a>
371
+ <a name='L306'></a><a href='#L306'>306</a>
372
+ <a name='L307'></a><a href='#L307'>307</a>
373
+ <a name='L308'></a><a href='#L308'>308</a>
374
+ <a name='L309'></a><a href='#L309'>309</a>
375
+ <a name='L310'></a><a href='#L310'>310</a>
376
+ <a name='L311'></a><a href='#L311'>311</a>
377
+ <a name='L312'></a><a href='#L312'>312</a>
378
+ <a name='L313'></a><a href='#L313'>313</a>
379
+ <a name='L314'></a><a href='#L314'>314</a>
380
+ <a name='L315'></a><a href='#L315'>315</a>
381
+ <a name='L316'></a><a href='#L316'>316</a>
382
+ <a name='L317'></a><a href='#L317'>317</a>
383
+ <a name='L318'></a><a href='#L318'>318</a>
384
+ <a name='L319'></a><a href='#L319'>319</a>
385
+ <a name='L320'></a><a href='#L320'>320</a>
386
+ <a name='L321'></a><a href='#L321'>321</a>
387
+ <a name='L322'></a><a href='#L322'>322</a>
388
+ <a name='L323'></a><a href='#L323'>323</a>
389
+ <a name='L324'></a><a href='#L324'>324</a>
390
+ <a name='L325'></a><a href='#L325'>325</a>
391
+ <a name='L326'></a><a href='#L326'>326</a>
392
+ <a name='L327'></a><a href='#L327'>327</a>
393
+ <a name='L328'></a><a href='#L328'>328</a>
394
+ <a name='L329'></a><a href='#L329'>329</a>
395
+ <a name='L330'></a><a href='#L330'>330</a>
396
+ <a name='L331'></a><a href='#L331'>331</a>
397
+ <a name='L332'></a><a href='#L332'>332</a>
398
+ <a name='L333'></a><a href='#L333'>333</a>
399
+ <a name='L334'></a><a href='#L334'>334</a>
400
+ <a name='L335'></a><a href='#L335'>335</a>
401
+ <a name='L336'></a><a href='#L336'>336</a>
402
+ <a name='L337'></a><a href='#L337'>337</a>
403
+ <a name='L338'></a><a href='#L338'>338</a>
404
+ <a name='L339'></a><a href='#L339'>339</a>
405
+ <a name='L340'></a><a href='#L340'>340</a>
406
+ <a name='L341'></a><a href='#L341'>341</a>
407
+ <a name='L342'></a><a href='#L342'>342</a>
408
+ <a name='L343'></a><a href='#L343'>343</a>
409
+ <a name='L344'></a><a href='#L344'>344</a>
410
+ <a name='L345'></a><a href='#L345'>345</a>
411
+ <a name='L346'></a><a href='#L346'>346</a>
412
+ <a name='L347'></a><a href='#L347'>347</a>
413
+ <a name='L348'></a><a href='#L348'>348</a>
414
+ <a name='L349'></a><a href='#L349'>349</a>
415
+ <a name='L350'></a><a href='#L350'>350</a>
416
+ <a name='L351'></a><a href='#L351'>351</a>
417
+ <a name='L352'></a><a href='#L352'>352</a>
418
+ <a name='L353'></a><a href='#L353'>353</a>
419
+ <a name='L354'></a><a href='#L354'>354</a>
420
+ <a name='L355'></a><a href='#L355'>355</a>
421
+ <a name='L356'></a><a href='#L356'>356</a>
422
+ <a name='L357'></a><a href='#L357'>357</a>
423
+ <a name='L358'></a><a href='#L358'>358</a>
424
+ <a name='L359'></a><a href='#L359'>359</a>
425
+ <a name='L360'></a><a href='#L360'>360</a>
426
+ <a name='L361'></a><a href='#L361'>361</a>
427
+ <a name='L362'></a><a href='#L362'>362</a>
428
+ <a name='L363'></a><a href='#L363'>363</a>
429
+ <a name='L364'></a><a href='#L364'>364</a>
430
+ <a name='L365'></a><a href='#L365'>365</a>
431
+ <a name='L366'></a><a href='#L366'>366</a>
432
+ <a name='L367'></a><a href='#L367'>367</a>
433
+ <a name='L368'></a><a href='#L368'>368</a>
434
+ <a name='L369'></a><a href='#L369'>369</a>
435
+ <a name='L370'></a><a href='#L370'>370</a>
436
+ <a name='L371'></a><a href='#L371'>371</a>
437
+ <a name='L372'></a><a href='#L372'>372</a>
438
+ <a name='L373'></a><a href='#L373'>373</a>
439
+ <a name='L374'></a><a href='#L374'>374</a>
440
+ <a name='L375'></a><a href='#L375'>375</a>
441
+ <a name='L376'></a><a href='#L376'>376</a>
442
+ <a name='L377'></a><a href='#L377'>377</a>
443
+ <a name='L378'></a><a href='#L378'>378</a>
444
+ <a name='L379'></a><a href='#L379'>379</a>
445
+ <a name='L380'></a><a href='#L380'>380</a>
446
+ <a name='L381'></a><a href='#L381'>381</a>
447
+ <a name='L382'></a><a href='#L382'>382</a>
448
+ <a name='L383'></a><a href='#L383'>383</a>
449
+ <a name='L384'></a><a href='#L384'>384</a>
450
+ <a name='L385'></a><a href='#L385'>385</a>
451
+ <a name='L386'></a><a href='#L386'>386</a>
452
+ <a name='L387'></a><a href='#L387'>387</a>
453
+ <a name='L388'></a><a href='#L388'>388</a>
454
+ <a name='L389'></a><a href='#L389'>389</a>
455
+ <a name='L390'></a><a href='#L390'>390</a>
456
+ <a name='L391'></a><a href='#L391'>391</a>
457
+ <a name='L392'></a><a href='#L392'>392</a>
458
+ <a name='L393'></a><a href='#L393'>393</a>
459
+ <a name='L394'></a><a href='#L394'>394</a>
460
+ <a name='L395'></a><a href='#L395'>395</a>
461
+ <a name='L396'></a><a href='#L396'>396</a>
462
+ <a name='L397'></a><a href='#L397'>397</a>
463
+ <a name='L398'></a><a href='#L398'>398</a>
464
+ <a name='L399'></a><a href='#L399'>399</a>
465
+ <a name='L400'></a><a href='#L400'>400</a>
466
+ <a name='L401'></a><a href='#L401'>401</a>
467
+ <a name='L402'></a><a href='#L402'>402</a>
468
+ <a name='L403'></a><a href='#L403'>403</a>
469
+ <a name='L404'></a><a href='#L404'>404</a>
470
+ <a name='L405'></a><a href='#L405'>405</a>
471
+ <a name='L406'></a><a href='#L406'>406</a>
472
+ <a name='L407'></a><a href='#L407'>407</a>
473
+ <a name='L408'></a><a href='#L408'>408</a>
474
+ <a name='L409'></a><a href='#L409'>409</a>
475
+ <a name='L410'></a><a href='#L410'>410</a>
476
+ <a name='L411'></a><a href='#L411'>411</a>
477
+ <a name='L412'></a><a href='#L412'>412</a>
478
+ <a name='L413'></a><a href='#L413'>413</a>
479
+ <a name='L414'></a><a href='#L414'>414</a>
480
+ <a name='L415'></a><a href='#L415'>415</a>
481
+ <a name='L416'></a><a href='#L416'>416</a>
482
+ <a name='L417'></a><a href='#L417'>417</a>
483
+ <a name='L418'></a><a href='#L418'>418</a>
484
+ <a name='L419'></a><a href='#L419'>419</a>
485
+ <a name='L420'></a><a href='#L420'>420</a>
486
+ <a name='L421'></a><a href='#L421'>421</a>
487
+ <a name='L422'></a><a href='#L422'>422</a>
488
+ <a name='L423'></a><a href='#L423'>423</a>
489
+ <a name='L424'></a><a href='#L424'>424</a>
490
+ <a name='L425'></a><a href='#L425'>425</a>
491
+ <a name='L426'></a><a href='#L426'>426</a>
492
+ <a name='L427'></a><a href='#L427'>427</a>
493
+ <a name='L428'></a><a href='#L428'>428</a>
494
+ <a name='L429'></a><a href='#L429'>429</a>
495
+ <a name='L430'></a><a href='#L430'>430</a>
496
+ <a name='L431'></a><a href='#L431'>431</a>
497
+ <a name='L432'></a><a href='#L432'>432</a>
498
+ <a name='L433'></a><a href='#L433'>433</a>
499
+ <a name='L434'></a><a href='#L434'>434</a>
500
+ <a name='L435'></a><a href='#L435'>435</a>
501
+ <a name='L436'></a><a href='#L436'>436</a>
502
+ <a name='L437'></a><a href='#L437'>437</a>
503
+ <a name='L438'></a><a href='#L438'>438</a>
504
+ <a name='L439'></a><a href='#L439'>439</a>
505
+ <a name='L440'></a><a href='#L440'>440</a>
506
+ <a name='L441'></a><a href='#L441'>441</a>
507
+ <a name='L442'></a><a href='#L442'>442</a>
508
+ <a name='L443'></a><a href='#L443'>443</a>
509
+ <a name='L444'></a><a href='#L444'>444</a>
510
+ <a name='L445'></a><a href='#L445'>445</a>
511
+ <a name='L446'></a><a href='#L446'>446</a>
512
+ <a name='L447'></a><a href='#L447'>447</a>
513
+ <a name='L448'></a><a href='#L448'>448</a>
514
+ <a name='L449'></a><a href='#L449'>449</a>
515
+ <a name='L450'></a><a href='#L450'>450</a>
516
+ <a name='L451'></a><a href='#L451'>451</a>
517
+ <a name='L452'></a><a href='#L452'>452</a>
518
+ <a name='L453'></a><a href='#L453'>453</a>
519
+ <a name='L454'></a><a href='#L454'>454</a>
520
+ <a name='L455'></a><a href='#L455'>455</a>
521
+ <a name='L456'></a><a href='#L456'>456</a>
522
+ <a name='L457'></a><a href='#L457'>457</a>
523
+ <a name='L458'></a><a href='#L458'>458</a>
524
+ <a name='L459'></a><a href='#L459'>459</a>
525
+ <a name='L460'></a><a href='#L460'>460</a>
526
+ <a name='L461'></a><a href='#L461'>461</a>
527
+ <a name='L462'></a><a href='#L462'>462</a>
528
+ <a name='L463'></a><a href='#L463'>463</a>
529
+ <a name='L464'></a><a href='#L464'>464</a>
530
+ <a name='L465'></a><a href='#L465'>465</a>
531
+ <a name='L466'></a><a href='#L466'>466</a>
532
+ <a name='L467'></a><a href='#L467'>467</a>
533
+ <a name='L468'></a><a href='#L468'>468</a>
534
+ <a name='L469'></a><a href='#L469'>469</a>
535
+ <a name='L470'></a><a href='#L470'>470</a>
536
+ <a name='L471'></a><a href='#L471'>471</a>
537
+ <a name='L472'></a><a href='#L472'>472</a>
538
+ <a name='L473'></a><a href='#L473'>473</a>
539
+ <a name='L474'></a><a href='#L474'>474</a>
540
+ <a name='L475'></a><a href='#L475'>475</a>
541
+ <a name='L476'></a><a href='#L476'>476</a>
542
+ <a name='L477'></a><a href='#L477'>477</a>
543
+ <a name='L478'></a><a href='#L478'>478</a>
544
+ <a name='L479'></a><a href='#L479'>479</a>
545
+ <a name='L480'></a><a href='#L480'>480</a>
546
+ <a name='L481'></a><a href='#L481'>481</a>
547
+ <a name='L482'></a><a href='#L482'>482</a>
548
+ <a name='L483'></a><a href='#L483'>483</a>
549
+ <a name='L484'></a><a href='#L484'>484</a>
550
+ <a name='L485'></a><a href='#L485'>485</a>
551
+ <a name='L486'></a><a href='#L486'>486</a>
552
+ <a name='L487'></a><a href='#L487'>487</a>
553
+ <a name='L488'></a><a href='#L488'>488</a>
554
+ <a name='L489'></a><a href='#L489'>489</a>
555
+ <a name='L490'></a><a href='#L490'>490</a>
556
+ <a name='L491'></a><a href='#L491'>491</a>
557
+ <a name='L492'></a><a href='#L492'>492</a>
558
+ <a name='L493'></a><a href='#L493'>493</a>
559
+ <a name='L494'></a><a href='#L494'>494</a>
560
+ <a name='L495'></a><a href='#L495'>495</a>
561
+ <a name='L496'></a><a href='#L496'>496</a>
562
+ <a name='L497'></a><a href='#L497'>497</a>
563
+ <a name='L498'></a><a href='#L498'>498</a>
564
+ <a name='L499'></a><a href='#L499'>499</a>
565
+ <a name='L500'></a><a href='#L500'>500</a>
566
+ <a name='L501'></a><a href='#L501'>501</a>
567
+ <a name='L502'></a><a href='#L502'>502</a>
568
+ <a name='L503'></a><a href='#L503'>503</a>
569
+ <a name='L504'></a><a href='#L504'>504</a>
570
+ <a name='L505'></a><a href='#L505'>505</a>
571
+ <a name='L506'></a><a href='#L506'>506</a>
572
+ <a name='L507'></a><a href='#L507'>507</a>
573
+ <a name='L508'></a><a href='#L508'>508</a>
574
+ <a name='L509'></a><a href='#L509'>509</a>
575
+ <a name='L510'></a><a href='#L510'>510</a>
576
+ <a name='L511'></a><a href='#L511'>511</a>
577
+ <a name='L512'></a><a href='#L512'>512</a>
578
+ <a name='L513'></a><a href='#L513'>513</a>
579
+ <a name='L514'></a><a href='#L514'>514</a>
580
+ <a name='L515'></a><a href='#L515'>515</a>
581
+ <a name='L516'></a><a href='#L516'>516</a>
582
+ <a name='L517'></a><a href='#L517'>517</a>
583
+ <a name='L518'></a><a href='#L518'>518</a>
584
+ <a name='L519'></a><a href='#L519'>519</a>
585
+ <a name='L520'></a><a href='#L520'>520</a>
586
+ <a name='L521'></a><a href='#L521'>521</a>
587
+ <a name='L522'></a><a href='#L522'>522</a>
588
+ <a name='L523'></a><a href='#L523'>523</a>
589
+ <a name='L524'></a><a href='#L524'>524</a>
590
+ <a name='L525'></a><a href='#L525'>525</a>
591
+ <a name='L526'></a><a href='#L526'>526</a>
592
+ <a name='L527'></a><a href='#L527'>527</a>
593
+ <a name='L528'></a><a href='#L528'>528</a>
594
+ <a name='L529'></a><a href='#L529'>529</a>
595
+ <a name='L530'></a><a href='#L530'>530</a>
596
+ <a name='L531'></a><a href='#L531'>531</a>
597
+ <a name='L532'></a><a href='#L532'>532</a>
598
+ <a name='L533'></a><a href='#L533'>533</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
599
+ <span class="cline-any cline-neutral">&nbsp;</span>
600
+ <span class="cline-any cline-neutral">&nbsp;</span>
601
+ <span class="cline-any cline-neutral">&nbsp;</span>
602
+ <span class="cline-any cline-neutral">&nbsp;</span>
603
+ <span class="cline-any cline-neutral">&nbsp;</span>
604
+ <span class="cline-any cline-neutral">&nbsp;</span>
605
+ <span class="cline-any cline-neutral">&nbsp;</span>
606
+ <span class="cline-any cline-neutral">&nbsp;</span>
607
+ <span class="cline-any cline-neutral">&nbsp;</span>
608
+ <span class="cline-any cline-neutral">&nbsp;</span>
609
+ <span class="cline-any cline-neutral">&nbsp;</span>
610
+ <span class="cline-any cline-neutral">&nbsp;</span>
611
+ <span class="cline-any cline-neutral">&nbsp;</span>
612
+ <span class="cline-any cline-neutral">&nbsp;</span>
613
+ <span class="cline-any cline-neutral">&nbsp;</span>
614
+ <span class="cline-any cline-neutral">&nbsp;</span>
615
+ <span class="cline-any cline-neutral">&nbsp;</span>
616
+ <span class="cline-any cline-neutral">&nbsp;</span>
617
+ <span class="cline-any cline-neutral">&nbsp;</span>
618
+ <span class="cline-any cline-neutral">&nbsp;</span>
619
+ <span class="cline-any cline-neutral">&nbsp;</span>
620
+ <span class="cline-any cline-neutral">&nbsp;</span>
621
+ <span class="cline-any cline-neutral">&nbsp;</span>
622
+ <span class="cline-any cline-neutral">&nbsp;</span>
623
+ <span class="cline-any cline-neutral">&nbsp;</span>
624
+ <span class="cline-any cline-neutral">&nbsp;</span>
625
+ <span class="cline-any cline-neutral">&nbsp;</span>
626
+ <span class="cline-any cline-neutral">&nbsp;</span>
627
+ <span class="cline-any cline-neutral">&nbsp;</span>
628
+ <span class="cline-any cline-neutral">&nbsp;</span>
629
+ <span class="cline-any cline-neutral">&nbsp;</span>
630
+ <span class="cline-any cline-neutral">&nbsp;</span>
631
+ <span class="cline-any cline-neutral">&nbsp;</span>
632
+ <span class="cline-any cline-neutral">&nbsp;</span>
633
+ <span class="cline-any cline-neutral">&nbsp;</span>
634
+ <span class="cline-any cline-neutral">&nbsp;</span>
635
+ <span class="cline-any cline-neutral">&nbsp;</span>
636
+ <span class="cline-any cline-neutral">&nbsp;</span>
637
+ <span class="cline-any cline-neutral">&nbsp;</span>
638
+ <span class="cline-any cline-neutral">&nbsp;</span>
639
+ <span class="cline-any cline-neutral">&nbsp;</span>
640
+ <span class="cline-any cline-neutral">&nbsp;</span>
641
+ <span class="cline-any cline-neutral">&nbsp;</span>
642
+ <span class="cline-any cline-neutral">&nbsp;</span>
643
+ <span class="cline-any cline-neutral">&nbsp;</span>
644
+ <span class="cline-any cline-neutral">&nbsp;</span>
645
+ <span class="cline-any cline-neutral">&nbsp;</span>
646
+ <span class="cline-any cline-neutral">&nbsp;</span>
647
+ <span class="cline-any cline-neutral">&nbsp;</span>
648
+ <span class="cline-any cline-neutral">&nbsp;</span>
649
+ <span class="cline-any cline-neutral">&nbsp;</span>
650
+ <span class="cline-any cline-neutral">&nbsp;</span>
651
+ <span class="cline-any cline-neutral">&nbsp;</span>
652
+ <span class="cline-any cline-neutral">&nbsp;</span>
653
+ <span class="cline-any cline-neutral">&nbsp;</span>
654
+ <span class="cline-any cline-neutral">&nbsp;</span>
655
+ <span class="cline-any cline-neutral">&nbsp;</span>
656
+ <span class="cline-any cline-neutral">&nbsp;</span>
657
+ <span class="cline-any cline-neutral">&nbsp;</span>
658
+ <span class="cline-any cline-yes">1x</span>
659
+ <span class="cline-any cline-neutral">&nbsp;</span>
660
+ <span class="cline-any cline-neutral">&nbsp;</span>
661
+ <span class="cline-any cline-neutral">&nbsp;</span>
662
+ <span class="cline-any cline-yes">74x</span>
663
+ <span class="cline-any cline-neutral">&nbsp;</span>
664
+ <span class="cline-any cline-neutral">&nbsp;</span>
665
+ <span class="cline-any cline-neutral">&nbsp;</span>
666
+ <span class="cline-any cline-yes">80x</span>
667
+ <span class="cline-any cline-neutral">&nbsp;</span>
668
+ <span class="cline-any cline-neutral">&nbsp;</span>
669
+ <span class="cline-any cline-neutral">&nbsp;</span>
670
+ <span class="cline-any cline-neutral">&nbsp;</span>
671
+ <span class="cline-any cline-neutral">&nbsp;</span>
672
+ <span class="cline-any cline-neutral">&nbsp;</span>
673
+ <span class="cline-any cline-neutral">&nbsp;</span>
674
+ <span class="cline-any cline-neutral">&nbsp;</span>
675
+ <span class="cline-any cline-neutral">&nbsp;</span>
676
+ <span class="cline-any cline-neutral">&nbsp;</span>
677
+ <span class="cline-any cline-neutral">&nbsp;</span>
678
+ <span class="cline-any cline-neutral">&nbsp;</span>
679
+ <span class="cline-any cline-neutral">&nbsp;</span>
680
+ <span class="cline-any cline-neutral">&nbsp;</span>
681
+ <span class="cline-any cline-neutral">&nbsp;</span>
682
+ <span class="cline-any cline-neutral">&nbsp;</span>
683
+ <span class="cline-any cline-yes">78x</span>
684
+ <span class="cline-any cline-yes">78x</span>
685
+ <span class="cline-any cline-neutral">&nbsp;</span>
686
+ <span class="cline-any cline-neutral">&nbsp;</span>
687
+ <span class="cline-any cline-neutral">&nbsp;</span>
688
+ <span class="cline-any cline-neutral">&nbsp;</span>
689
+ <span class="cline-any cline-neutral">&nbsp;</span>
690
+ <span class="cline-any cline-neutral">&nbsp;</span>
691
+ <span class="cline-any cline-neutral">&nbsp;</span>
692
+ <span class="cline-any cline-neutral">&nbsp;</span>
693
+ <span class="cline-any cline-yes">61x</span>
694
+ <span class="cline-any cline-yes">60x</span>
695
+ <span class="cline-any cline-neutral">&nbsp;</span>
696
+ <span class="cline-any cline-neutral">&nbsp;</span>
697
+ <span class="cline-any cline-neutral">&nbsp;</span>
698
+ <span class="cline-any cline-neutral">&nbsp;</span>
699
+ <span class="cline-any cline-neutral">&nbsp;</span>
700
+ <span class="cline-any cline-yes">67x</span>
701
+ <span class="cline-any cline-neutral">&nbsp;</span>
702
+ <span class="cline-any cline-neutral">&nbsp;</span>
703
+ <span class="cline-any cline-neutral">&nbsp;</span>
704
+ <span class="cline-any cline-yes">52x</span>
705
+ <span class="cline-any cline-neutral">&nbsp;</span>
706
+ <span class="cline-any cline-neutral">&nbsp;</span>
707
+ <span class="cline-any cline-neutral">&nbsp;</span>
708
+ <span class="cline-any cline-yes">120x</span>
709
+ <span class="cline-any cline-neutral">&nbsp;</span>
710
+ <span class="cline-any cline-neutral">&nbsp;</span>
711
+ <span class="cline-any cline-neutral">&nbsp;</span>
712
+ <span class="cline-any cline-neutral">&nbsp;</span>
713
+ <span class="cline-any cline-neutral">&nbsp;</span>
714
+ <span class="cline-any cline-neutral">&nbsp;</span>
715
+ <span class="cline-any cline-neutral">&nbsp;</span>
716
+ <span class="cline-any cline-neutral">&nbsp;</span>
717
+ <span class="cline-any cline-neutral">&nbsp;</span>
718
+ <span class="cline-any cline-neutral">&nbsp;</span>
719
+ <span class="cline-any cline-neutral">&nbsp;</span>
720
+ <span class="cline-any cline-neutral">&nbsp;</span>
721
+ <span class="cline-any cline-yes">56x</span>
722
+ <span class="cline-any cline-yes">56x</span>
723
+ <span class="cline-any cline-neutral">&nbsp;</span>
724
+ <span class="cline-any cline-neutral">&nbsp;</span>
725
+ <span class="cline-any cline-yes">56x</span>
726
+ <span class="cline-any cline-neutral">&nbsp;</span>
727
+ <span class="cline-any cline-neutral">&nbsp;</span>
728
+ <span class="cline-any cline-neutral">&nbsp;</span>
729
+ <span class="cline-any cline-neutral">&nbsp;</span>
730
+ <span class="cline-any cline-neutral">&nbsp;</span>
731
+ <span class="cline-any cline-neutral">&nbsp;</span>
732
+ <span class="cline-any cline-neutral">&nbsp;</span>
733
+ <span class="cline-any cline-neutral">&nbsp;</span>
734
+ <span class="cline-any cline-yes">6x</span>
735
+ <span class="cline-any cline-yes">4x</span>
736
+ <span class="cline-any cline-neutral">&nbsp;</span>
737
+ <span class="cline-any cline-yes">2x</span>
738
+ <span class="cline-any cline-neutral">&nbsp;</span>
739
+ <span class="cline-any cline-neutral">&nbsp;</span>
740
+ <span class="cline-any cline-neutral">&nbsp;</span>
741
+ <span class="cline-any cline-neutral">&nbsp;</span>
742
+ <span class="cline-any cline-neutral">&nbsp;</span>
743
+ <span class="cline-any cline-neutral">&nbsp;</span>
744
+ <span class="cline-any cline-yes">61x</span>
745
+ <span class="cline-any cline-yes">61x</span>
746
+ <span class="cline-any cline-yes">10x</span>
747
+ <span class="cline-any cline-yes">1x</span>
748
+ <span class="cline-any cline-yes">1x</span>
749
+ <span class="cline-any cline-neutral">&nbsp;</span>
750
+ <span class="cline-any cline-yes">10x</span>
751
+ <span class="cline-any cline-neutral">&nbsp;</span>
752
+ <span class="cline-any cline-neutral">&nbsp;</span>
753
+ <span class="cline-any cline-yes">51x</span>
754
+ <span class="cline-any cline-neutral">&nbsp;</span>
755
+ <span class="cline-any cline-neutral">&nbsp;</span>
756
+ <span class="cline-any cline-yes">51x</span>
757
+ <span class="cline-any cline-yes">50x</span>
758
+ <span class="cline-any cline-yes">2x</span>
759
+ <span class="cline-any cline-yes">2x</span>
760
+ <span class="cline-any cline-yes">2x</span>
761
+ <span class="cline-any cline-neutral">&nbsp;</span>
762
+ <span class="cline-any cline-yes">1x</span>
763
+ <span class="cline-any cline-yes">1x</span>
764
+ <span class="cline-any cline-yes">1x</span>
765
+ <span class="cline-any cline-neutral">&nbsp;</span>
766
+ <span class="cline-any cline-neutral">&nbsp;</span>
767
+ <span class="cline-any cline-yes">48x</span>
768
+ <span class="cline-any cline-yes">1x</span>
769
+ <span class="cline-any cline-neutral">&nbsp;</span>
770
+ <span class="cline-any cline-neutral">&nbsp;</span>
771
+ <span class="cline-any cline-neutral">&nbsp;</span>
772
+ <span class="cline-any cline-yes">50x</span>
773
+ <span class="cline-any cline-neutral">&nbsp;</span>
774
+ <span class="cline-any cline-yes">50x</span>
775
+ <span class="cline-any cline-neutral">&nbsp;</span>
776
+ <span class="cline-any cline-neutral">&nbsp;</span>
777
+ <span class="cline-any cline-yes">19x</span>
778
+ <span class="cline-any cline-yes">19x</span>
779
+ <span class="cline-any cline-yes">2x</span>
780
+ <span class="cline-any cline-yes">2x</span>
781
+ <span class="cline-any cline-yes">2x</span>
782
+ <span class="cline-any cline-neutral">&nbsp;</span>
783
+ <span class="cline-any cline-yes">1x</span>
784
+ <span class="cline-any cline-neutral">&nbsp;</span>
785
+ <span class="cline-any cline-yes">2x</span>
786
+ <span class="cline-any cline-yes">2x</span>
787
+ <span class="cline-any cline-yes">2x</span>
788
+ <span class="cline-any cline-neutral">&nbsp;</span>
789
+ <span class="cline-any cline-neutral">&nbsp;</span>
790
+ <span class="cline-any cline-neutral">&nbsp;</span>
791
+ <span class="cline-any cline-neutral">&nbsp;</span>
792
+ <span class="cline-any cline-neutral">&nbsp;</span>
793
+ <span class="cline-any cline-neutral">&nbsp;</span>
794
+ <span class="cline-any cline-neutral">&nbsp;</span>
795
+ <span class="cline-any cline-yes">49x</span>
796
+ <span class="cline-any cline-neutral">&nbsp;</span>
797
+ <span class="cline-any cline-yes">49x</span>
798
+ <span class="cline-any cline-neutral">&nbsp;</span>
799
+ <span class="cline-any cline-neutral">&nbsp;</span>
800
+ <span class="cline-any cline-yes">4x</span>
801
+ <span class="cline-any cline-yes">4x</span>
802
+ <span class="cline-any cline-yes">1x</span>
803
+ <span class="cline-any cline-yes">1x</span>
804
+ <span class="cline-any cline-yes">1x</span>
805
+ <span class="cline-any cline-neutral">&nbsp;</span>
806
+ <span class="cline-any cline-neutral">&nbsp;</span>
807
+ <span class="cline-any cline-neutral">&nbsp;</span>
808
+ <span class="cline-any cline-neutral">&nbsp;</span>
809
+ <span class="cline-any cline-neutral">&nbsp;</span>
810
+ <span class="cline-any cline-neutral">&nbsp;</span>
811
+ <span class="cline-any cline-yes">49x</span>
812
+ <span class="cline-any cline-neutral">&nbsp;</span>
813
+ <span class="cline-any cline-yes">49x</span>
814
+ <span class="cline-any cline-neutral">&nbsp;</span>
815
+ <span class="cline-any cline-neutral">&nbsp;</span>
816
+ <span class="cline-any cline-yes">72x</span>
817
+ <span class="cline-any cline-yes">72x</span>
818
+ <span class="cline-any cline-yes">1x</span>
819
+ <span class="cline-any cline-yes">1x</span>
820
+ <span class="cline-any cline-yes">1x</span>
821
+ <span class="cline-any cline-neutral">&nbsp;</span>
822
+ <span class="cline-any cline-neutral">&nbsp;</span>
823
+ <span class="cline-any cline-neutral">&nbsp;</span>
824
+ <span class="cline-any cline-yes">49x</span>
825
+ <span class="cline-any cline-neutral">&nbsp;</span>
826
+ <span class="cline-any cline-neutral">&nbsp;</span>
827
+ <span class="cline-any cline-yes">49x</span>
828
+ <span class="cline-any cline-yes">18x</span>
829
+ <span class="cline-any cline-yes">18x</span>
830
+ <span class="cline-any cline-yes">18x</span>
831
+ <span class="cline-any cline-yes">18x</span>
832
+ <span class="cline-any cline-yes">15x</span>
833
+ <span class="cline-any cline-neutral">&nbsp;</span>
834
+ <span class="cline-any cline-yes">18x</span>
835
+ <span class="cline-any cline-neutral">&nbsp;</span>
836
+ <span class="cline-any cline-neutral">&nbsp;</span>
837
+ <span class="cline-any cline-yes">49x</span>
838
+ <span class="cline-any cline-neutral">&nbsp;</span>
839
+ <span class="cline-any cline-neutral">&nbsp;</span>
840
+ <span class="cline-any cline-neutral">&nbsp;</span>
841
+ <span class="cline-any cline-neutral">&nbsp;</span>
842
+ <span class="cline-any cline-neutral">&nbsp;</span>
843
+ <span class="cline-any cline-neutral">&nbsp;</span>
844
+ <span class="cline-any cline-neutral">&nbsp;</span>
845
+ <span class="cline-any cline-neutral">&nbsp;</span>
846
+ <span class="cline-any cline-neutral">&nbsp;</span>
847
+ <span class="cline-any cline-neutral">&nbsp;</span>
848
+ <span class="cline-any cline-yes">49x</span>
849
+ <span class="cline-any cline-yes">49x</span>
850
+ <span class="cline-any cline-neutral">&nbsp;</span>
851
+ <span class="cline-any cline-neutral">&nbsp;</span>
852
+ <span class="cline-any cline-neutral">&nbsp;</span>
853
+ <span class="cline-any cline-yes">11x</span>
854
+ <span class="cline-any cline-yes">11x</span>
855
+ <span class="cline-any cline-neutral">&nbsp;</span>
856
+ <span class="cline-any cline-yes">10x</span>
857
+ <span class="cline-any cline-yes">3x</span>
858
+ <span class="cline-any cline-neutral">&nbsp;</span>
859
+ <span class="cline-any cline-neutral">&nbsp;</span>
860
+ <span class="cline-any cline-yes">10x</span>
861
+ <span class="cline-any cline-yes">10x</span>
862
+ <span class="cline-any cline-yes">10x</span>
863
+ <span class="cline-any cline-yes">30x</span>
864
+ <span class="cline-any cline-neutral">&nbsp;</span>
865
+ <span class="cline-any cline-yes">10x</span>
866
+ <span class="cline-any cline-yes">10x</span>
867
+ <span class="cline-any cline-yes">10x</span>
868
+ <span class="cline-any cline-neutral">&nbsp;</span>
869
+ <span class="cline-any cline-neutral">&nbsp;</span>
870
+ <span class="cline-any cline-neutral">&nbsp;</span>
871
+ <span class="cline-any cline-neutral">&nbsp;</span>
872
+ <span class="cline-any cline-neutral">&nbsp;</span>
873
+ <span class="cline-any cline-yes">19x</span>
874
+ <span class="cline-any cline-yes">19x</span>
875
+ <span class="cline-any cline-neutral">&nbsp;</span>
876
+ <span class="cline-any cline-yes">18x</span>
877
+ <span class="cline-any cline-yes">1x</span>
878
+ <span class="cline-any cline-yes">1x</span>
879
+ <span class="cline-any cline-neutral">&nbsp;</span>
880
+ <span class="cline-any cline-neutral">&nbsp;</span>
881
+ <span class="cline-any cline-yes">17x</span>
882
+ <span class="cline-any cline-yes">17x</span>
883
+ <span class="cline-any cline-yes">11x</span>
884
+ <span class="cline-any cline-neutral">&nbsp;</span>
885
+ <span class="cline-any cline-neutral">&nbsp;</span>
886
+ <span class="cline-any cline-neutral">&nbsp;</span>
887
+ <span class="cline-any cline-neutral">&nbsp;</span>
888
+ <span class="cline-any cline-yes">17x</span>
889
+ <span class="cline-any cline-yes">17x</span>
890
+ <span class="cline-any cline-neutral">&nbsp;</span>
891
+ <span class="cline-any cline-yes">16x</span>
892
+ <span class="cline-any cline-yes">15x</span>
893
+ <span class="cline-any cline-yes">15x</span>
894
+ <span class="cline-any cline-neutral">&nbsp;</span>
895
+ <span class="cline-any cline-neutral">&nbsp;</span>
896
+ <span class="cline-any cline-yes">16x</span>
897
+ <span class="cline-any cline-yes">16x</span>
898
+ <span class="cline-any cline-neutral">&nbsp;</span>
899
+ <span class="cline-any cline-neutral">&nbsp;</span>
900
+ <span class="cline-any cline-yes">16x</span>
901
+ <span class="cline-any cline-neutral">&nbsp;</span>
902
+ <span class="cline-any cline-yes">27x</span>
903
+ <span class="cline-any cline-neutral">&nbsp;</span>
904
+ <span class="cline-any cline-yes">1x</span>
905
+ <span class="cline-any cline-yes">1x</span>
906
+ <span class="cline-any cline-yes">1x</span>
907
+ <span class="cline-any cline-neutral">&nbsp;</span>
908
+ <span class="cline-any cline-neutral">&nbsp;</span>
909
+ <span class="cline-any cline-yes">15x</span>
910
+ <span class="cline-any cline-neutral">&nbsp;</span>
911
+ <span class="cline-any cline-yes">17x</span>
912
+ <span class="cline-any cline-neutral">&nbsp;</span>
913
+ <span class="cline-any cline-neutral">&nbsp;</span>
914
+ <span class="cline-any cline-neutral">&nbsp;</span>
915
+ <span class="cline-any cline-neutral">&nbsp;</span>
916
+ <span class="cline-any cline-neutral">&nbsp;</span>
917
+ <span class="cline-any cline-yes">17x</span>
918
+ <span class="cline-any cline-neutral">&nbsp;</span>
919
+ <span class="cline-any cline-yes">14x</span>
920
+ <span class="cline-any cline-yes">14x</span>
921
+ <span class="cline-any cline-yes">14x</span>
922
+ <span class="cline-any cline-neutral">&nbsp;</span>
923
+ <span class="cline-any cline-yes">1x</span>
924
+ <span class="cline-any cline-neutral">&nbsp;</span>
925
+ <span class="cline-any cline-neutral">&nbsp;</span>
926
+ <span class="cline-any cline-neutral">&nbsp;</span>
927
+ <span class="cline-any cline-neutral">&nbsp;</span>
928
+ <span class="cline-any cline-neutral">&nbsp;</span>
929
+ <span class="cline-any cline-neutral">&nbsp;</span>
930
+ <span class="cline-any cline-neutral">&nbsp;</span>
931
+ <span class="cline-any cline-neutral">&nbsp;</span>
932
+ <span class="cline-any cline-neutral">&nbsp;</span>
933
+ <span class="cline-any cline-neutral">&nbsp;</span>
934
+ <span class="cline-any cline-neutral">&nbsp;</span>
935
+ <span class="cline-any cline-yes">95x</span>
936
+ <span class="cline-any cline-yes">96x</span>
937
+ <span class="cline-any cline-yes">76x</span>
938
+ <span class="cline-any cline-yes">78x</span>
939
+ <span class="cline-any cline-neutral">&nbsp;</span>
940
+ <span class="cline-any cline-neutral">&nbsp;</span>
941
+ <span class="cline-any cline-neutral">&nbsp;</span>
942
+ <span class="cline-any cline-neutral">&nbsp;</span>
943
+ <span class="cline-any cline-neutral">&nbsp;</span>
944
+ <span class="cline-any cline-yes">72x</span>
945
+ <span class="cline-any cline-yes">72x</span>
946
+ <span class="cline-any cline-yes">72x</span>
947
+ <span class="cline-any cline-neutral">&nbsp;</span>
948
+ <span class="cline-any cline-neutral">&nbsp;</span>
949
+ <span class="cline-any cline-neutral">&nbsp;</span>
950
+ <span class="cline-any cline-neutral">&nbsp;</span>
951
+ <span class="cline-any cline-neutral">&nbsp;</span>
952
+ <span class="cline-any cline-neutral">&nbsp;</span>
953
+ <span class="cline-any cline-neutral">&nbsp;</span>
954
+ <span class="cline-any cline-neutral">&nbsp;</span>
955
+ <span class="cline-any cline-neutral">&nbsp;</span>
956
+ <span class="cline-any cline-neutral">&nbsp;</span>
957
+ <span class="cline-any cline-neutral">&nbsp;</span>
958
+ <span class="cline-any cline-yes">61x</span>
959
+ <span class="cline-any cline-yes">61x</span>
960
+ <span class="cline-any cline-neutral">&nbsp;</span>
961
+ <span class="cline-any cline-yes">59x</span>
962
+ <span class="cline-any cline-yes">59x</span>
963
+ <span class="cline-any cline-yes">57x</span>
964
+ <span class="cline-any cline-yes">57x</span>
965
+ <span class="cline-any cline-neutral">&nbsp;</span>
966
+ <span class="cline-any cline-yes">59x</span>
967
+ <span class="cline-any cline-neutral">&nbsp;</span>
968
+ <span class="cline-any cline-yes">59x</span>
969
+ <span class="cline-any cline-yes">59x</span>
970
+ <span class="cline-any cline-yes">58x</span>
971
+ <span class="cline-any cline-yes">58x</span>
972
+ <span class="cline-any cline-neutral">&nbsp;</span>
973
+ <span class="cline-any cline-yes">59x</span>
974
+ <span class="cline-any cline-neutral">&nbsp;</span>
975
+ <span class="cline-any cline-yes">59x</span>
976
+ <span class="cline-any cline-yes">61x</span>
977
+ <span class="cline-any cline-neutral">&nbsp;</span>
978
+ <span class="cline-any cline-neutral">&nbsp;</span>
979
+ <span class="cline-any cline-neutral">&nbsp;</span>
980
+ <span class="cline-any cline-yes">61x</span>
981
+ <span class="cline-any cline-neutral">&nbsp;</span>
982
+ <span class="cline-any cline-neutral">&nbsp;</span>
983
+ <span class="cline-any cline-neutral">&nbsp;</span>
984
+ <span class="cline-any cline-neutral">&nbsp;</span>
985
+ <span class="cline-any cline-neutral">&nbsp;</span>
986
+ <span class="cline-any cline-yes">61x</span>
987
+ <span class="cline-any cline-neutral">&nbsp;</span>
988
+ <span class="cline-any cline-yes">61x</span>
989
+ <span class="cline-any cline-neutral">&nbsp;</span>
990
+ <span class="cline-any cline-neutral">&nbsp;</span>
991
+ <span class="cline-any cline-neutral">&nbsp;</span>
992
+ <span class="cline-any cline-neutral">&nbsp;</span>
993
+ <span class="cline-any cline-neutral">&nbsp;</span>
994
+ <span class="cline-any cline-yes">61x</span>
995
+ <span class="cline-any cline-yes">61x</span>
996
+ <span class="cline-any cline-neutral">&nbsp;</span>
997
+ <span class="cline-any cline-yes">2x</span>
998
+ <span class="cline-any cline-yes">2x</span>
999
+ <span class="cline-any cline-neutral">&nbsp;</span>
1000
+ <span class="cline-any cline-neutral">&nbsp;</span>
1001
+ <span class="cline-any cline-neutral">&nbsp;</span>
1002
+ <span class="cline-any cline-neutral">&nbsp;</span>
1003
+ <span class="cline-any cline-yes">19x</span>
1004
+ <span class="cline-any cline-yes">19x</span>
1005
+ <span class="cline-any cline-neutral">&nbsp;</span>
1006
+ <span class="cline-any cline-yes">18x</span>
1007
+ <span class="cline-any cline-yes">18x</span>
1008
+ <span class="cline-any cline-yes">18x</span>
1009
+ <span class="cline-any cline-neutral">&nbsp;</span>
1010
+ <span class="cline-any cline-yes">18x</span>
1011
+ <span class="cline-any cline-neutral">&nbsp;</span>
1012
+ <span class="cline-any cline-neutral">&nbsp;</span>
1013
+ <span class="cline-any cline-neutral">&nbsp;</span>
1014
+ <span class="cline-any cline-neutral">&nbsp;</span>
1015
+ <span class="cline-any cline-neutral">&nbsp;</span>
1016
+ <span class="cline-any cline-neutral">&nbsp;</span>
1017
+ <span class="cline-any cline-yes">18x</span>
1018
+ <span class="cline-any cline-neutral">&nbsp;</span>
1019
+ <span class="cline-any cline-neutral">&nbsp;</span>
1020
+ <span class="cline-any cline-neutral">&nbsp;</span>
1021
+ <span class="cline-any cline-neutral">&nbsp;</span>
1022
+ <span class="cline-any cline-neutral">&nbsp;</span>
1023
+ <span class="cline-any cline-yes">1x</span>
1024
+ <span class="cline-any cline-neutral">&nbsp;</span>
1025
+ <span class="cline-any cline-neutral">&nbsp;</span>
1026
+ <span class="cline-any cline-neutral">&nbsp;</span>
1027
+ <span class="cline-any cline-neutral">&nbsp;</span>
1028
+ <span class="cline-any cline-yes">4x</span>
1029
+ <span class="cline-any cline-yes">4x</span>
1030
+ <span class="cline-any cline-neutral">&nbsp;</span>
1031
+ <span class="cline-any cline-yes">3x</span>
1032
+ <span class="cline-any cline-neutral">&nbsp;</span>
1033
+ <span class="cline-any cline-neutral">&nbsp;</span>
1034
+ <span class="cline-any cline-neutral">&nbsp;</span>
1035
+ <span class="cline-any cline-neutral">&nbsp;</span>
1036
+ <span class="cline-any cline-neutral">&nbsp;</span>
1037
+ <span class="cline-any cline-yes">3x</span>
1038
+ <span class="cline-any cline-neutral">&nbsp;</span>
1039
+ <span class="cline-any cline-neutral">&nbsp;</span>
1040
+ <span class="cline-any cline-neutral">&nbsp;</span>
1041
+ <span class="cline-any cline-neutral">&nbsp;</span>
1042
+ <span class="cline-any cline-neutral">&nbsp;</span>
1043
+ <span class="cline-any cline-neutral">&nbsp;</span>
1044
+ <span class="cline-any cline-yes">13x</span>
1045
+ <span class="cline-any cline-yes">13x</span>
1046
+ <span class="cline-any cline-neutral">&nbsp;</span>
1047
+ <span class="cline-any cline-yes">12x</span>
1048
+ <span class="cline-any cline-yes">12x</span>
1049
+ <span class="cline-any cline-yes">11x</span>
1050
+ <span class="cline-any cline-yes">11x</span>
1051
+ <span class="cline-any cline-yes">10x</span>
1052
+ <span class="cline-any cline-neutral">&nbsp;</span>
1053
+ <span class="cline-any cline-neutral">&nbsp;</span>
1054
+ <span class="cline-any cline-neutral">&nbsp;</span>
1055
+ <span class="cline-any cline-yes">12x</span>
1056
+ <span class="cline-any cline-yes">12x</span>
1057
+ <span class="cline-any cline-yes">11x</span>
1058
+ <span class="cline-any cline-yes">11x</span>
1059
+ <span class="cline-any cline-yes">10x</span>
1060
+ <span class="cline-any cline-neutral">&nbsp;</span>
1061
+ <span class="cline-any cline-neutral">&nbsp;</span>
1062
+ <span class="cline-any cline-neutral">&nbsp;</span>
1063
+ <span class="cline-any cline-yes">12x</span>
1064
+ <span class="cline-any cline-neutral">&nbsp;</span>
1065
+ <span class="cline-any cline-neutral">&nbsp;</span>
1066
+ <span class="cline-any cline-neutral">&nbsp;</span>
1067
+ <span class="cline-any cline-neutral">&nbsp;</span>
1068
+ <span class="cline-any cline-neutral">&nbsp;</span>
1069
+ <span class="cline-any cline-yes">12x</span>
1070
+ <span class="cline-any cline-yes">12x</span>
1071
+ <span class="cline-any cline-neutral">&nbsp;</span>
1072
+ <span class="cline-any cline-yes">12x</span>
1073
+ <span class="cline-any cline-yes">9x</span>
1074
+ <span class="cline-any cline-yes">8x</span>
1075
+ <span class="cline-any cline-neutral">&nbsp;</span>
1076
+ <span class="cline-any cline-neutral">&nbsp;</span>
1077
+ <span class="cline-any cline-neutral">&nbsp;</span>
1078
+ <span class="cline-any cline-neutral">&nbsp;</span>
1079
+ <span class="cline-any cline-neutral">&nbsp;</span>
1080
+ <span class="cline-any cline-yes">2x</span>
1081
+ <span class="cline-any cline-yes">2x</span>
1082
+ <span class="cline-any cline-neutral">&nbsp;</span>
1083
+ <span class="cline-any cline-yes">1x</span>
1084
+ <span class="cline-any cline-yes">2x</span>
1085
+ <span class="cline-any cline-neutral">&nbsp;</span>
1086
+ <span class="cline-any cline-yes">1x</span>
1087
+ <span class="cline-any cline-neutral">&nbsp;</span>
1088
+ <span class="cline-any cline-neutral">&nbsp;</span>
1089
+ <span class="cline-any cline-neutral">&nbsp;</span>
1090
+ <span class="cline-any cline-neutral">&nbsp;</span>
1091
+ <span class="cline-any cline-neutral">&nbsp;</span>
1092
+ <span class="cline-any cline-neutral">&nbsp;</span>
1093
+ <span class="cline-any cline-neutral">&nbsp;</span>
1094
+ <span class="cline-any cline-yes">8x</span>
1095
+ <span class="cline-any cline-yes">9x</span>
1096
+ <span class="cline-any cline-yes">9x</span>
1097
+ <span class="cline-any cline-neutral">&nbsp;</span>
1098
+ <span class="cline-any cline-yes">6x</span>
1099
+ <span class="cline-any cline-yes">9x</span>
1100
+ <span class="cline-any cline-neutral">&nbsp;</span>
1101
+ <span class="cline-any cline-yes">9x</span>
1102
+ <span class="cline-any cline-neutral">&nbsp;</span>
1103
+ <span class="cline-any cline-yes">2x</span>
1104
+ <span class="cline-any cline-neutral">&nbsp;</span>
1105
+ <span class="cline-any cline-neutral">&nbsp;</span>
1106
+ <span class="cline-any cline-neutral">&nbsp;</span>
1107
+ <span class="cline-any cline-neutral">&nbsp;</span>
1108
+ <span class="cline-any cline-neutral">&nbsp;</span>
1109
+ <span class="cline-any cline-neutral">&nbsp;</span>
1110
+ <span class="cline-any cline-yes">2x</span>
1111
+ <span class="cline-any cline-neutral">&nbsp;</span>
1112
+ <span class="cline-any cline-neutral">&nbsp;</span>
1113
+ <span class="cline-any cline-yes">1x</span>
1114
+ <span class="cline-any cline-yes">1x</span>
1115
+ <span class="cline-any cline-neutral">&nbsp;</span>
1116
+ <span class="cline-any cline-neutral">&nbsp;</span>
1117
+ <span class="cline-any cline-yes">1x</span>
1118
+ <span class="cline-any cline-yes">1x</span>
1119
+ <span class="cline-any cline-neutral">&nbsp;</span>
1120
+ <span class="cline-any cline-neutral">&nbsp;</span>
1121
+ <span class="cline-any cline-yes">1x</span>
1122
+ <span class="cline-any cline-yes">1x</span>
1123
+ <span class="cline-any cline-neutral">&nbsp;</span>
1124
+ <span class="cline-any cline-neutral">&nbsp;</span>
1125
+ <span class="cline-any cline-yes">1x</span>
1126
+ <span class="cline-any cline-neutral">&nbsp;</span>
1127
+ <span class="cline-any cline-neutral">&nbsp;</span>
1128
+ <span class="cline-any cline-neutral">&nbsp;</span>
1129
+ <span class="cline-any cline-neutral">&nbsp;</span>
1130
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import os from 'os';
1131
+ import type WebSocket from 'ws';
1132
+ import * as Y from 'yjs';
1133
+ &nbsp;
1134
+ import { escapeNatsToken } from '../../server/NatsStore.js';
1135
+ import { natsPublish, natsSubscribe } from '../../server/Nats.js';
1136
+ import type {
1137
+ CollabAwarenessMessage,
1138
+ CollabDocState,
1139
+ CollabPeersMessage,
1140
+ CollabSyncOutput,
1141
+ CollabUpdateMessage,
1142
+ } from '../shared/CollabTypes.js';
1143
+ &nbsp;
1144
+ // --- Configuration ---
1145
+ &nbsp;
1146
+ export interface CollabConfig {
1147
+ /**
1148
+ * Load the current Yjs state for a document.
1149
+ * Return the base64-encoded yjsState string, or null if no state exists.
1150
+ */
1151
+ loadState(docId: string): Promise&lt;string | null&gt;;
1152
+ &nbsp;
1153
+ /**
1154
+ * Persist the Yjs state for a document.
1155
+ * Called with the full base64-encoded state and a serialized content string
1156
+ * (if a serialize callback is provided).
1157
+ */
1158
+ saveState(
1159
+ docId: string,
1160
+ collabState: CollabDocState,
1161
+ serialized: string | null,
1162
+ ): Promise&lt;void&gt;;
1163
+ &nbsp;
1164
+ /**
1165
+ * Optional: Initialize a Yjs doc from existing content when no collab state
1166
+ * exists yet. Called with an empty Y.Doc — the implementation should populate it.
1167
+ */
1168
+ initializeDoc?(ydoc: Y.Doc, docId: string): Promise&lt;void&gt;;
1169
+ &nbsp;
1170
+ /**
1171
+ * Optional: Serialize a Yjs doc to a string for persistence alongside the
1172
+ * binary Yjs state. Useful for maintaining a searchable/readable representation.
1173
+ */
1174
+ serialize?(ydoc: Y.Doc): string;
1175
+ &nbsp;
1176
+ /**
1177
+ * Optional: Called when an error occurs. Defaults to console.error.
1178
+ */
1179
+ onError?(message: string, error: unknown, context?: Record&lt;string, unknown&gt;): void;
1180
+ &nbsp;
1181
+ /** Debounce interval for persistence in ms. Default: 3000 */
1182
+ persistDebounceMs?: number;
1183
+ /** Max wait before forced persistence in ms. Default: 30000 */
1184
+ persistMaxWaitMs?: number;
1185
+ /** Idle timeout before unloading a document in ms. Default: 300000 (5 min) */
1186
+ idleCleanupMs?: number;
1187
+ }
1188
+ &nbsp;
1189
+ // --- Server identity ---
1190
+ const serverId = `${os.hostname()}-${process.pid}`;
1191
+ &nbsp;
1192
+ // --- Base64 helpers ---
1193
+ function base64Encode(data: Uint8Array): string {
1194
+ return Buffer.from(data).toString('base64');
1195
+ }
1196
+ &nbsp;
1197
+ function base64Decode(base64: string): Uint8Array {
1198
+ return new Uint8Array(Buffer.from(base64, 'base64'));
1199
+ }
1200
+ &nbsp;
1201
+ // --- Active document state ---
1202
+ interface ActiveDocument {
1203
+ ydoc: Y.Doc;
1204
+ peers: Map&lt;string, Set&lt;WebSocket&gt;&gt;; // userId → sockets
1205
+ natsSubs: (() =&gt; void)[];
1206
+ persistTimer: ReturnType&lt;typeof setTimeout&gt; | null;
1207
+ firstDirtyAt: number | null;
1208
+ lastPersistedState: Uint8Array | null;
1209
+ dirtyFlag: boolean;
1210
+ idleTimer: ReturnType&lt;typeof setTimeout&gt; | null;
1211
+ }
1212
+ &nbsp;
1213
+ // --- Send helpers ---
1214
+ function wsSend(ws: WebSocket, type: string, data: unknown): void {
1215
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (ws.readyState === 1 /* OPEN */) {
1216
+ ws.send(JSON.stringify({ type, data }));
1217
+ }
1218
+ }
1219
+ &nbsp;
1220
+ function wsSendResponse(
1221
+ ws: WebSocket,
1222
+ id: string | undefined,
1223
+ data: unknown,
1224
+ ): void {
1225
+ if (id &amp;&amp; ws.readyState === 1) {
1226
+ ws.send(JSON.stringify({ type: 'response', id, data }));
1227
+ }
1228
+ }
1229
+ &nbsp;
1230
+ // --- NATS subjects ---
1231
+ function updateSubject(docId: string): string {
1232
+ return `collab.update.${escapeNatsToken(docId)}`;
1233
+ }
1234
+ &nbsp;
1235
+ function awarenessSubject(docId: string): string {
1236
+ return `collab.awareness.${escapeNatsToken(docId)}`;
1237
+ }
1238
+ &nbsp;
1239
+ function peersSubject(docId: string): string {
1240
+ return `collab.peers.${escapeNatsToken(docId)}`;
1241
+ }
1242
+ &nbsp;
1243
+ /**
1244
+ * Manages collaborative editing for multiple documents using Yjs CRDTs.
1245
+ * Cross-server sync via NATS, debounced persistence, idle cleanup.
1246
+ */
1247
+ export class CollabServer {
1248
+ private readonly config: Required&lt;
1249
+ Pick&lt;CollabConfig, 'persistDebounceMs' | 'persistMaxWaitMs' | 'idleCleanupMs'&gt;
1250
+ &gt; &amp;
1251
+ CollabConfig;
1252
+ &nbsp;
1253
+ private readonly activeDocuments = new Map&lt;string, ActiveDocument&gt;();
1254
+ private readonly socketDocs = new Map&lt;WebSocket, Set&lt;string&gt;&gt;();
1255
+ &nbsp;
1256
+ constructor(config: CollabConfig) {
1257
+ this.config = {
1258
+ persistDebounceMs: 3000,
1259
+ persistMaxWaitMs: 30000,
1260
+ idleCleanupMs: 300000,
1261
+ ...config,
1262
+ };
1263
+ }
1264
+ &nbsp;
1265
+ private logError(message: string, error: unknown, context?: Record&lt;string, unknown&gt;): void {
1266
+ if (this.config.onError) {
1267
+ this.config.onError(message, error, context);
1268
+ } else {
1269
+ console.error(message, error, context);
1270
+ }
1271
+ }
1272
+ &nbsp;
1273
+ // --- Document lifecycle ---
1274
+ &nbsp;
1275
+ private async loadDocument(docId: string): Promise&lt;ActiveDocument&gt; {
1276
+ const existing = this.activeDocuments.get(docId);
1277
+ if (existing) {
1278
+ if (existing.idleTimer) {
1279
+ clearTimeout(existing.idleTimer);
1280
+ existing.idleTimer = null;
1281
+ }
1282
+ return existing;
1283
+ }
1284
+ &nbsp;
1285
+ const ydoc = new Y.Doc();
1286
+ &nbsp;
1287
+ // Load existing collab state or initialize
1288
+ const savedState = await this.config.loadState(docId);
1289
+ if (savedState) {
1290
+ try {
1291
+ const state = base64Decode(savedState);
1292
+ Y.applyUpdate(ydoc, state);
1293
+ } catch (error) {
1294
+ this.logError('[Collab] Corrupt yjsState, re-initializing', error, { docId });
1295
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (this.config.initializeDoc) {
1296
+ await this.config.initializeDoc(ydoc, docId);
1297
+ }
1298
+ }
1299
+ } else if (this.config.initializeDoc) {
1300
+ await this.config.initializeDoc(ydoc, docId);
1301
+ }
1302
+ &nbsp;
1303
+ // Subscribe to NATS for cross-server sync
1304
+ const natsSubs: (() =&gt; void)[] = [];
1305
+ &nbsp;
1306
+ const updateUnsub = await natsSubscribe(
1307
+ updateSubject(docId),
1308
+ ({ data }: { subject: string; data: unknown }) =&gt; {
1309
+ const msg = data as CollabUpdateMessage &amp; { serverId: string };
1310
+ if (msg.serverId === serverId) return;
1311
+ try {
1312
+ const update = base64Decode(msg.update);
1313
+ Y.applyUpdate(ydoc, update, 'remote');
1314
+ } catch (error) {
1315
+ this.logError('[Collab] Failed to apply NATS update', error, { docId });
1316
+ }
1317
+ const doc = this.activeDocuments.get(docId);
1318
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (doc) {
1319
+ this.broadcastToLocalPeers(doc, null, 'collab:update', {
1320
+ docId,
1321
+ update: msg.update,
1322
+ origin: msg.origin,
1323
+ });
1324
+ }
1325
+ },
1326
+ );
1327
+ natsSubs.push(updateUnsub);
1328
+ &nbsp;
1329
+ const awarenessUnsub = await natsSubscribe(
1330
+ awarenessSubject(docId),
1331
+ ({ data }: { subject: string; data: unknown }) =&gt; {
1332
+ const msg = data as CollabAwarenessMessage &amp; { serverId: string };
1333
+ if (msg.serverId === serverId) return;
1334
+ const doc = this.activeDocuments.get(docId);
1335
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (doc) {
1336
+ this.broadcastToLocalPeers(doc, null, 'collab:awareness', {
1337
+ docId,
1338
+ states: msg.states,
1339
+ });
1340
+ }
1341
+ },
1342
+ );
1343
+ natsSubs.push(awarenessUnsub);
1344
+ &nbsp;
1345
+ const peersUnsub = await natsSubscribe(
1346
+ peersSubject(docId),
1347
+ ({ data }: { subject: string; data: unknown }) =&gt; {
1348
+ const msg = data as { serverId: string };
1349
+ if (msg.serverId === serverId) return;
1350
+ const doc = this.activeDocuments.get(docId);
1351
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (doc) {
1352
+ this.broadcastPeerList(doc, docId);
1353
+ }
1354
+ },
1355
+ );
1356
+ natsSubs.push(peersUnsub);
1357
+ &nbsp;
1358
+ // Listen for local Yjs updates (for persistence)
1359
+ ydoc.on('update', () =&gt; {
1360
+ const doc = this.activeDocuments.get(docId);
1361
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (!doc) <span class="cstat-no" title="statement not covered" >return;</span>
1362
+ doc.dirtyFlag = true;
1363
+ if (!doc.firstDirtyAt) {
1364
+ doc.firstDirtyAt = Date.now();
1365
+ }
1366
+ this.schedulePersist(docId);
1367
+ });
1368
+ &nbsp;
1369
+ const activeDoc: ActiveDocument = {
1370
+ ydoc,
1371
+ peers: new Map(),
1372
+ natsSubs,
1373
+ persistTimer: null,
1374
+ firstDirtyAt: null,
1375
+ lastPersistedState: null,
1376
+ dirtyFlag: false,
1377
+ idleTimer: null,
1378
+ };
1379
+ &nbsp;
1380
+ this.activeDocuments.set(docId, activeDoc);
1381
+ return activeDoc;
1382
+ }
1383
+ &nbsp;
1384
+ private async unloadDocument(docId: string): Promise&lt;void&gt; {
1385
+ const doc = this.activeDocuments.get(docId);
1386
+ if (!doc) return;
1387
+ &nbsp;
1388
+ if (doc.dirtyFlag) {
1389
+ await this.persistDocument(docId);
1390
+ }
1391
+ &nbsp;
1392
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (doc.persistTimer) clearTimeout(doc.persistTimer);
1393
+ if (doc.idleTimer) clearTimeout(doc.idleTimer);
1394
+ for (const unsub of doc.natsSubs) {
1395
+ unsub();
1396
+ }
1397
+ doc.ydoc.destroy();
1398
+ this.activeDocuments.delete(docId);
1399
+ console.log('[Collab] Unloaded document', { docId });
1400
+ }
1401
+ &nbsp;
1402
+ // --- Persistence ---
1403
+ &nbsp;
1404
+ private schedulePersist(docId: string): void {
1405
+ const doc = this.activeDocuments.get(docId);
1406
+ if (!doc) return;
1407
+ &nbsp;
1408
+ if (doc.firstDirtyAt &amp;&amp; Date.now() - doc.firstDirtyAt &gt; this.config.persistMaxWaitMs) {
1409
+ void this.persistDocument(docId);
1410
+ return;
1411
+ }
1412
+ &nbsp;
1413
+ if (doc.persistTimer) clearTimeout(doc.persistTimer);
1414
+ doc.persistTimer = setTimeout(() =&gt; {
1415
+ void this.persistDocument(docId);
1416
+ }, this.config.persistDebounceMs);
1417
+ }
1418
+ &nbsp;
1419
+ private async persistDocument(docId: string): Promise&lt;void&gt; {
1420
+ const doc = this.activeDocuments.get(docId);
1421
+ if (!doc) return;
1422
+ &nbsp;
1423
+ if (doc.persistTimer) {
1424
+ clearTimeout(doc.persistTimer);
1425
+ doc.persistTimer = null;
1426
+ }
1427
+ &nbsp;
1428
+ try {
1429
+ const state = Y.encodeStateAsUpdate(doc.ydoc);
1430
+ &nbsp;
1431
+ // Skip if state hasn't changed
1432
+ if (
1433
+ doc.lastPersistedState?.length === state.length &amp;&amp;
1434
+ state.every((v, i) =&gt; v === doc.lastPersistedState![i])
1435
+ ) {
1436
+ doc.dirtyFlag = false;
1437
+ doc.firstDirtyAt = null;
1438
+ return;
1439
+ }
1440
+ &nbsp;
1441
+ const serialized = this.config.serialize ? this.config.serialize(doc.ydoc) : null;
1442
+ &nbsp;
1443
+ const collabState: CollabDocState = {
1444
+ yjsState: base64Encode(state),
1445
+ lastSyncedAt: Date.now(),
1446
+ version: (doc.lastPersistedState ? 1 : 0) + Date.now(),
1447
+ };
1448
+ &nbsp;
1449
+ await this.config.saveState(docId, collabState, serialized);
1450
+ &nbsp;
1451
+ doc.lastPersistedState = state;
1452
+ doc.dirtyFlag = false;
1453
+ doc.firstDirtyAt = null;
1454
+ } catch (error) {
1455
+ this.logError('[Collab] Failed to persist document', error, { docId });
1456
+ }
1457
+ }
1458
+ &nbsp;
1459
+ // --- Broadcasting ---
1460
+ &nbsp;
1461
+ private broadcastToLocalPeers(
1462
+ doc: ActiveDocument,
1463
+ excludeUserId: string | null,
1464
+ type: string,
1465
+ data: unknown,
1466
+ ): void {
1467
+ for (const [userId, sockets] of doc.peers) {
1468
+ if (userId === excludeUserId) continue;
1469
+ for (const ws of sockets) {
1470
+ wsSend(ws, type, data);
1471
+ }
1472
+ }
1473
+ }
1474
+ &nbsp;
1475
+ private broadcastPeerList(doc: ActiveDocument, docId: string): void {
1476
+ const peerIds = [...doc.peers.keys()];
1477
+ const msg: CollabPeersMessage = { docId, peerIds };
1478
+ this.broadcastToLocalPeers(doc, null, 'collab:peers', msg);
1479
+ }
1480
+ &nbsp;
1481
+ // --- Public API ---
1482
+ &nbsp;
1483
+ async join(
1484
+ ws: WebSocket,
1485
+ docId: string,
1486
+ userId: string,
1487
+ stateVector: string,
1488
+ messageId: string | undefined,
1489
+ ): Promise&lt;void&gt; {
1490
+ try {
1491
+ const doc = await this.loadDocument(docId);
1492
+ &nbsp;
1493
+ let sockets = doc.peers.get(userId);
1494
+ if (!sockets) {
1495
+ sockets = new Set();
1496
+ doc.peers.set(userId, sockets);
1497
+ }
1498
+ sockets.add(ws);
1499
+ &nbsp;
1500
+ let docs = this.socketDocs.get(ws);
1501
+ if (!docs) {
1502
+ docs = new Set();
1503
+ this.socketDocs.set(ws, docs);
1504
+ }
1505
+ docs.add(docId);
1506
+ &nbsp;
1507
+ const clientSV = stateVector ? base64Decode(stateVector) : undefined;
1508
+ const diff = clientSV
1509
+ ? Y.encodeStateAsUpdate(doc.ydoc, clientSV)
1510
+ : Y.encodeStateAsUpdate(doc.ydoc);
1511
+ &nbsp;
1512
+ const response: CollabSyncOutput = {
1513
+ docId,
1514
+ update: base64Encode(diff),
1515
+ peerIds: [...doc.peers.keys()],
1516
+ };
1517
+ &nbsp;
1518
+ wsSendResponse(ws, messageId, response);
1519
+ &nbsp;
1520
+ natsPublish(peersSubject(docId), {
1521
+ serverId,
1522
+ action: 'join',
1523
+ userId,
1524
+ });
1525
+ &nbsp;
1526
+ this.broadcastPeerList(doc, docId);
1527
+ console.log('[Collab] User joined', { docId, userId });
1528
+ } catch (error) {
1529
+ this.logError('[Collab] Failed to join', error, { docId, userId });
1530
+ wsSendResponse(ws, messageId, { error: 'errorUnknown' });
1531
+ }
1532
+ }
1533
+ &nbsp;
1534
+ handleUpdate(docId: string, userId: string, update: string): void {
1535
+ const doc = this.activeDocuments.get(docId);
1536
+ if (!doc) return;
1537
+ &nbsp;
1538
+ try {
1539
+ const decoded = base64Decode(update);
1540
+ Y.applyUpdate(doc.ydoc, decoded);
1541
+ &nbsp;
1542
+ natsPublish(updateSubject(docId), {
1543
+ docId,
1544
+ update,
1545
+ origin: userId,
1546
+ serverId,
1547
+ } satisfies CollabUpdateMessage &amp; { serverId: string });
1548
+ &nbsp;
1549
+ this.broadcastToLocalPeers(doc, userId, 'collab:update', {
1550
+ docId,
1551
+ update,
1552
+ origin: userId,
1553
+ } satisfies CollabUpdateMessage);
1554
+ } catch (error) {
1555
+ this.logError('[Collab] Failed to handle update', error, { docId, userId });
1556
+ }
1557
+ }
1558
+ &nbsp;
1559
+ handleAwareness(docId: string, userId: string, states: string): void {
1560
+ const doc = this.activeDocuments.get(docId);
1561
+ if (!doc) return;
1562
+ &nbsp;
1563
+ natsPublish(awarenessSubject(docId), {
1564
+ docId,
1565
+ states,
1566
+ serverId,
1567
+ } satisfies CollabAwarenessMessage &amp; { serverId: string });
1568
+ &nbsp;
1569
+ this.broadcastToLocalPeers(doc, userId, 'collab:awareness', {
1570
+ docId,
1571
+ states,
1572
+ } satisfies CollabAwarenessMessage);
1573
+ }
1574
+ &nbsp;
1575
+ leave(ws: WebSocket, docId: string, userId: string): void {
1576
+ const doc = this.activeDocuments.get(docId);
1577
+ if (!doc) return;
1578
+ &nbsp;
1579
+ const sockets = doc.peers.get(userId);
1580
+ if (sockets) {
1581
+ sockets.delete(ws);
1582
+ if (sockets.size === 0) {
1583
+ doc.peers.delete(userId);
1584
+ }
1585
+ }
1586
+ &nbsp;
1587
+ const docs = this.socketDocs.get(ws);
1588
+ if (docs) {
1589
+ docs.delete(docId);
1590
+ if (docs.size === 0) {
1591
+ this.socketDocs.delete(ws);
1592
+ }
1593
+ }
1594
+ &nbsp;
1595
+ natsPublish(peersSubject(docId), {
1596
+ serverId,
1597
+ action: 'leave',
1598
+ userId,
1599
+ });
1600
+ &nbsp;
1601
+ this.broadcastPeerList(doc, docId);
1602
+ console.log('[Collab] User left', { docId, userId });
1603
+ &nbsp;
1604
+ if (doc.peers.size === 0) {
1605
+ doc.idleTimer = setTimeout(() =&gt; {
1606
+ void this.unloadDocument(docId);
1607
+ }, this.config.idleCleanupMs);
1608
+ }
1609
+ }
1610
+ &nbsp;
1611
+ cleanupSocket(ws: WebSocket, userId: string): void {
1612
+ const docs = this.socketDocs.get(ws);
1613
+ if (!docs) return;
1614
+ &nbsp;
1615
+ for (const docId of docs) {
1616
+ this.leave(ws, docId, userId);
1617
+ }
1618
+ this.socketDocs.delete(ws);
1619
+ }
1620
+ &nbsp;
1621
+ /**
1622
+ * Returns a socket message handler suitable for use with
1623
+ * `configurator.addSocketMessageHandler()`.
1624
+ */
1625
+ createSocketHandler(): (ws: WebSocket, userId: string, msg: unknown) =&gt; boolean {
1626
+ return (ws: WebSocket, userId: string, msg: unknown): boolean =&gt; {
1627
+ const m = msg as { type?: string; data?: Record&lt;string, unknown&gt;; id?: string };
1628
+ if (!m.type?.startsWith('collab:')) return false;
1629
+ &nbsp;
1630
+ const data = m.data ?? {};
1631
+ const docId = data.docId as string;
1632
+ &nbsp;
1633
+ switch (m.type) {
1634
+ case 'collab:join':
1635
+ void this.join(
1636
+ ws,
1637
+ docId,
1638
+ userId,
1639
+ data.stateVector as string,
1640
+ m.id,
1641
+ );
1642
+ return true;
1643
+ &nbsp;
1644
+ case 'collab:update':
1645
+ this.handleUpdate(docId, userId, data.update as string);
1646
+ return true;
1647
+ &nbsp;
1648
+ case 'collab:awareness':
1649
+ this.handleAwareness(docId, userId, data.states as string);
1650
+ return true;
1651
+ &nbsp;
1652
+ case 'collab:leave':
1653
+ this.leave(ws, docId, userId);
1654
+ return true;
1655
+ &nbsp;
1656
+ default:
1657
+ return false;
1658
+ }
1659
+ };
1660
+ }
1661
+ }
1662
+ &nbsp;</pre></td></tr></table></pre>
1663
+
1664
+ <div class='push'></div><!-- for sticky footer -->
1665
+ </div><!-- /wrapper -->
1666
+ <div class='footer quiet pad2 space-top1 center small'>
1667
+ Code coverage generated by
1668
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1669
+ at 2026-04-08T15:51:54.932Z
1670
+ </div>
1671
+ <script src="prettify.js"></script>
1672
+ <script>
1673
+ window.onload = function () {
1674
+ prettyPrint();
1675
+ };
1676
+ </script>
1677
+ <script src="sorter.js"></script>
1678
+ <script src="block-navigation.js"></script>
1679
+ </body>
1680
+ </html>
1681
+