@nocios/crudify-components 2.0.36 → 2.0.38

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.
Files changed (134) hide show
  1. package/coverage/components/CrudiaAutoGenerate.tsx.html +583 -0
  2. package/coverage/components/CrudiaFileField/CrudiaFileField.tsx.html +1276 -0
  3. package/coverage/components/CrudiaFileField/components/DeleteConfirmationDialog.tsx.html +355 -0
  4. package/coverage/components/CrudiaFileField/components/DropZone.tsx.html +433 -0
  5. package/coverage/components/CrudiaFileField/components/FileItemRow.tsx.html +751 -0
  6. package/coverage/components/CrudiaFileField/components/FileThumbnail.tsx.html +1195 -0
  7. package/coverage/components/CrudiaFileField/components/index.html +176 -0
  8. package/coverage/components/CrudiaFileField/components/index.ts.html +115 -0
  9. package/coverage/components/CrudiaFileField/hooks/index.html +146 -0
  10. package/coverage/components/CrudiaFileField/hooks/index.ts.html +112 -0
  11. package/coverage/components/CrudiaFileField/hooks/useDeleteConfirmation.ts.html +421 -0
  12. package/coverage/components/CrudiaFileField/hooks/useDragDrop.ts.html +403 -0
  13. package/coverage/components/CrudiaFileField/index.html +131 -0
  14. package/coverage/components/CrudiaFileField/index.ts.html +112 -0
  15. package/coverage/components/CrudiaFileField/utils/formatters.ts.html +163 -0
  16. package/coverage/components/CrudiaFileField/utils/icons.tsx.html +253 -0
  17. package/coverage/components/CrudiaFileField/utils/index.html +131 -0
  18. package/coverage/components/CrudiaMarkdownField.tsx.html +619 -0
  19. package/coverage/components/CrudifyLogin/Forms/CheckCodeForm.tsx.html +586 -0
  20. package/coverage/components/CrudifyLogin/Forms/ForgotPasswordForm.tsx.html +694 -0
  21. package/coverage/components/CrudifyLogin/Forms/LoginForm.tsx.html +835 -0
  22. package/coverage/components/CrudifyLogin/Forms/ResetPasswordForm.tsx.html +1180 -0
  23. package/coverage/components/CrudifyLogin/Forms/components/CodeInput.tsx.html +283 -0
  24. package/coverage/components/CrudifyLogin/Forms/components/FormAlert.tsx.html +202 -0
  25. package/coverage/components/CrudifyLogin/Forms/components/PasswordInput.tsx.html +340 -0
  26. package/coverage/components/CrudifyLogin/Forms/components/index.html +161 -0
  27. package/coverage/components/CrudifyLogin/Forms/components/index.ts.html +106 -0
  28. package/coverage/components/CrudifyLogin/Forms/index.html +161 -0
  29. package/coverage/components/CrudifyLogin/Forms/utils/errorTranslation.ts.html +268 -0
  30. package/coverage/components/CrudifyLogin/Forms/utils/index.html +161 -0
  31. package/coverage/components/CrudifyLogin/Forms/utils/index.ts.html +106 -0
  32. package/coverage/components/CrudifyLogin/Forms/utils/paramUtils.ts.html +478 -0
  33. package/coverage/components/CrudifyLogin/Forms/utils/validation.ts.html +289 -0
  34. package/coverage/components/CrudifyLogin/components/CrudifyInitializer.tsx.html +262 -0
  35. package/coverage/components/CrudifyLogin/components/index.html +116 -0
  36. package/coverage/components/CrudifyLogin/context/CrudifyProvider.tsx.html +382 -0
  37. package/coverage/components/CrudifyLogin/context/I18nProvider.tsx.html +397 -0
  38. package/coverage/components/CrudifyLogin/context/LoginStateProvider.tsx.html +1249 -0
  39. package/coverage/components/CrudifyLogin/context/index.html +146 -0
  40. package/coverage/components/CrudifyLogin/hooks/index.html +116 -0
  41. package/coverage/components/CrudifyLogin/hooks/useTranslationsFromUrl.ts.html +292 -0
  42. package/coverage/components/CrudifyLogin/index.html +116 -0
  43. package/coverage/components/CrudifyLogin/index.tsx.html +475 -0
  44. package/coverage/components/GlobalNotificationProvider.tsx.html +781 -0
  45. package/coverage/components/LoginComponent.tsx.html +727 -0
  46. package/coverage/components/PasswordRequirements/index.html +116 -0
  47. package/coverage/components/PasswordRequirements/index.tsx.html +226 -0
  48. package/coverage/components/PublicPolicies/FieldSelector/FieldSelector.tsx.html +982 -0
  49. package/coverage/components/PublicPolicies/FieldSelector/index.html +131 -0
  50. package/coverage/components/PublicPolicies/FieldSelector/index.ts.html +85 -0
  51. package/coverage/components/PublicPolicies/Policies.tsx.html +610 -0
  52. package/coverage/components/PublicPolicies/PolicyItem/PolicyItem.tsx.html +856 -0
  53. package/coverage/components/PublicPolicies/PolicyItem/index.html +131 -0
  54. package/coverage/components/PublicPolicies/PolicyItem/index.ts.html +85 -0
  55. package/coverage/components/PublicPolicies/constants.ts.html +127 -0
  56. package/coverage/components/PublicPolicies/index.html +131 -0
  57. package/coverage/components/SessionTimeIndicator/index.html +116 -0
  58. package/coverage/components/SessionTimeIndicator/index.tsx.html +505 -0
  59. package/coverage/components/UserProfile/UserProfileDisplay.tsx.html +826 -0
  60. package/coverage/components/UserProfile/index.html +131 -0
  61. package/coverage/components/UserProfile/index.ts.html +85 -0
  62. package/coverage/components/index.html +176 -0
  63. package/coverage/components/index.ts.html +160 -0
  64. package/coverage/core/CrossTabSyncManager.ts.html +814 -0
  65. package/coverage/core/CrudifyInitializationManager.ts.html +1132 -0
  66. package/coverage/core/SessionManager.ts.html +2764 -0
  67. package/coverage/core/index.html +146 -0
  68. package/coverage/coverage-final.json +52 -85
  69. package/coverage/hooks/index.html +131 -0
  70. package/coverage/hooks/useAutoGenerate.ts.html +562 -0
  71. package/coverage/hooks/useFileUpload/index.html +131 -0
  72. package/coverage/hooks/useFileUpload/index.ts.html +112 -0
  73. package/coverage/hooks/useFileUpload/services/index.html +116 -0
  74. package/coverage/hooks/useFileUpload/services/uploadService.ts.html +610 -0
  75. package/coverage/hooks/useFileUpload/useFileUpload.ts.html +1870 -0
  76. package/coverage/hooks/useFileUpload/utils/fileUtils.ts.html +271 -0
  77. package/coverage/hooks/useFileUpload/utils/index.html +146 -0
  78. package/coverage/hooks/useFileUpload/utils/mimeTypes.ts.html +235 -0
  79. package/coverage/hooks/useFileUpload/utils/validation.ts.html +379 -0
  80. package/coverage/hooks/useSession/constants.ts.html +217 -0
  81. package/coverage/hooks/useSession/hooks/index.html +176 -0
  82. package/coverage/hooks/useSession/hooks/useAuthEventSubscriber.ts.html +331 -0
  83. package/coverage/hooks/useSession/hooks/useCrossTabSync.ts.html +433 -0
  84. package/coverage/hooks/useSession/hooks/useSessionActions.ts.html +664 -0
  85. package/coverage/hooks/useSession/hooks/useSessionState.ts.html +295 -0
  86. package/coverage/hooks/useSession/hooks/useTokenRefreshScheduler.ts.html +490 -0
  87. package/coverage/hooks/useSession/index.html +161 -0
  88. package/coverage/hooks/useSession/index.ts.html +127 -0
  89. package/coverage/hooks/useSession/types.ts.html +427 -0
  90. package/coverage/hooks/useSession/useSession.ts.html +526 -0
  91. package/coverage/hooks/useSession/utils/index.html +131 -0
  92. package/coverage/hooks/useSession/utils/initializeSession.ts.html +424 -0
  93. package/coverage/hooks/useSession/utils/tokenUtils.ts.html +280 -0
  94. package/coverage/hooks/useUserProfile.ts.html +658 -0
  95. package/coverage/index.html +237 -357
  96. package/coverage/providers/SessionProvider.tsx.html +1150 -0
  97. package/coverage/providers/TranslationsProvider.tsx.html +1450 -0
  98. package/coverage/providers/index.html +131 -0
  99. package/coverage/services/credentialsEventBus.ts.html +310 -0
  100. package/coverage/services/index.html +131 -0
  101. package/coverage/services/translationService.ts.html +1318 -0
  102. package/coverage/translations/critical.ts.html +1195 -0
  103. package/coverage/translations/index.html +116 -0
  104. package/coverage/types/index.html +116 -0
  105. package/coverage/types/password.ts.html +178 -0
  106. package/coverage/utils/authEventBus.ts.html +454 -0
  107. package/coverage/utils/configResolver.ts.html +460 -0
  108. package/coverage/utils/cookieSync.ts.html +580 -0
  109. package/coverage/utils/errorHandler.ts.html +1264 -0
  110. package/coverage/utils/errorTranslation.ts.html +862 -0
  111. package/coverage/utils/index.html +296 -0
  112. package/coverage/utils/jwtUtils.ts.html +301 -0
  113. package/coverage/utils/logger.ts.html +901 -0
  114. package/coverage/utils/navigationTracker.ts.html +565 -0
  115. package/coverage/utils/passwordValidation.ts.html +259 -0
  116. package/coverage/utils/redirectSecurity.ts.html +715 -0
  117. package/coverage/utils/tenantConfig.ts.html +700 -0
  118. package/coverage/utils/tokenStorage.ts.html +1768 -0
  119. package/coverage/utils/webCrypto.ts.html +472 -0
  120. package/dist/{CrudiaMarkdownField-Bxl08sRG.d.mts → CrudiaMarkdownField-BvJn2GL8.d.mts} +5 -0
  121. package/dist/{CrudiaMarkdownField-DUJNOOzP.d.ts → CrudiaMarkdownField-CggOpcBM.d.ts} +5 -0
  122. package/dist/chunk-SYHNHKFA.mjs +1 -0
  123. package/dist/chunk-W4ZYKPOK.js +1 -0
  124. package/dist/components.d.mts +1 -1
  125. package/dist/components.d.ts +1 -1
  126. package/dist/components.js +1 -1
  127. package/dist/components.mjs +1 -1
  128. package/dist/index.d.mts +6 -2
  129. package/dist/index.d.ts +6 -2
  130. package/dist/index.js +2 -2
  131. package/dist/index.mjs +1 -1
  132. package/package.json +1 -1
  133. package/dist/chunk-QFACMV2I.mjs +0 -1
  134. package/dist/chunk-QZH3KSJZ.js +0 -1
@@ -0,0 +1,1768 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for utils/tokenStorage.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> / <a href="index.html">utils</a> tokenStorage.ts</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">33.51% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>188/561</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">100% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>1/1</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">4.34% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>1/23</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">33.51% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>188/561</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 low'></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>
599
+ <a name='L534'></a><a href='#L534'>534</a>
600
+ <a name='L535'></a><a href='#L535'>535</a>
601
+ <a name='L536'></a><a href='#L536'>536</a>
602
+ <a name='L537'></a><a href='#L537'>537</a>
603
+ <a name='L538'></a><a href='#L538'>538</a>
604
+ <a name='L539'></a><a href='#L539'>539</a>
605
+ <a name='L540'></a><a href='#L540'>540</a>
606
+ <a name='L541'></a><a href='#L541'>541</a>
607
+ <a name='L542'></a><a href='#L542'>542</a>
608
+ <a name='L543'></a><a href='#L543'>543</a>
609
+ <a name='L544'></a><a href='#L544'>544</a>
610
+ <a name='L545'></a><a href='#L545'>545</a>
611
+ <a name='L546'></a><a href='#L546'>546</a>
612
+ <a name='L547'></a><a href='#L547'>547</a>
613
+ <a name='L548'></a><a href='#L548'>548</a>
614
+ <a name='L549'></a><a href='#L549'>549</a>
615
+ <a name='L550'></a><a href='#L550'>550</a>
616
+ <a name='L551'></a><a href='#L551'>551</a>
617
+ <a name='L552'></a><a href='#L552'>552</a>
618
+ <a name='L553'></a><a href='#L553'>553</a>
619
+ <a name='L554'></a><a href='#L554'>554</a>
620
+ <a name='L555'></a><a href='#L555'>555</a>
621
+ <a name='L556'></a><a href='#L556'>556</a>
622
+ <a name='L557'></a><a href='#L557'>557</a>
623
+ <a name='L558'></a><a href='#L558'>558</a>
624
+ <a name='L559'></a><a href='#L559'>559</a>
625
+ <a name='L560'></a><a href='#L560'>560</a>
626
+ <a name='L561'></a><a href='#L561'>561</a>
627
+ <a name='L562'></a><a href='#L562'>562</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
628
+ <span class="cline-any cline-yes">1x</span>
629
+ <span class="cline-any cline-yes">1x</span>
630
+ <span class="cline-any cline-yes">1x</span>
631
+ <span class="cline-any cline-yes">1x</span>
632
+ <span class="cline-any cline-yes">1x</span>
633
+ <span class="cline-any cline-yes">1x</span>
634
+ <span class="cline-any cline-yes">1x</span>
635
+ <span class="cline-any cline-yes">1x</span>
636
+ <span class="cline-any cline-yes">1x</span>
637
+ <span class="cline-any cline-yes">1x</span>
638
+ <span class="cline-any cline-yes">1x</span>
639
+ <span class="cline-any cline-yes">1x</span>
640
+ <span class="cline-any cline-yes">1x</span>
641
+ <span class="cline-any cline-yes">1x</span>
642
+ <span class="cline-any cline-yes">1x</span>
643
+ <span class="cline-any cline-yes">1x</span>
644
+ <span class="cline-any cline-yes">1x</span>
645
+ <span class="cline-any cline-yes">1x</span>
646
+ <span class="cline-any cline-yes">1x</span>
647
+ <span class="cline-any cline-yes">1x</span>
648
+ <span class="cline-any cline-yes">1x</span>
649
+ <span class="cline-any cline-yes">1x</span>
650
+ <span class="cline-any cline-yes">1x</span>
651
+ <span class="cline-any cline-yes">1x</span>
652
+ <span class="cline-any cline-yes">1x</span>
653
+ <span class="cline-any cline-yes">1x</span>
654
+ <span class="cline-any cline-yes">1x</span>
655
+ <span class="cline-any cline-yes">1x</span>
656
+ <span class="cline-any cline-yes">1x</span>
657
+ <span class="cline-any cline-yes">1x</span>
658
+ <span class="cline-any cline-yes">1x</span>
659
+ <span class="cline-any cline-yes">1x</span>
660
+ <span class="cline-any cline-yes">1x</span>
661
+ <span class="cline-any cline-yes">1x</span>
662
+ <span class="cline-any cline-yes">1x</span>
663
+ <span class="cline-any cline-yes">1x</span>
664
+ <span class="cline-any cline-yes">1x</span>
665
+ <span class="cline-any cline-yes">1x</span>
666
+ <span class="cline-any cline-yes">1x</span>
667
+ <span class="cline-any cline-yes">1x</span>
668
+ <span class="cline-any cline-yes">1x</span>
669
+ <span class="cline-any cline-yes">1x</span>
670
+ <span class="cline-any cline-yes">1x</span>
671
+ <span class="cline-any cline-yes">1x</span>
672
+ <span class="cline-any cline-yes">1x</span>
673
+ <span class="cline-any cline-no">&nbsp;</span>
674
+ <span class="cline-any cline-no">&nbsp;</span>
675
+ <span class="cline-any cline-yes">1x</span>
676
+ <span class="cline-any cline-yes">1x</span>
677
+ <span class="cline-any cline-yes">1x</span>
678
+ <span class="cline-any cline-yes">1x</span>
679
+ <span class="cline-any cline-yes">1x</span>
680
+ <span class="cline-any cline-yes">1x</span>
681
+ <span class="cline-any cline-yes">1x</span>
682
+ <span class="cline-any cline-yes">1x</span>
683
+ <span class="cline-any cline-yes">1x</span>
684
+ <span class="cline-any cline-no">&nbsp;</span>
685
+ <span class="cline-any cline-no">&nbsp;</span>
686
+ <span class="cline-any cline-no">&nbsp;</span>
687
+ <span class="cline-any cline-no">&nbsp;</span>
688
+ <span class="cline-any cline-no">&nbsp;</span>
689
+ <span class="cline-any cline-no">&nbsp;</span>
690
+ <span class="cline-any cline-no">&nbsp;</span>
691
+ <span class="cline-any cline-no">&nbsp;</span>
692
+ <span class="cline-any cline-no">&nbsp;</span>
693
+ <span class="cline-any cline-no">&nbsp;</span>
694
+ <span class="cline-any cline-no">&nbsp;</span>
695
+ <span class="cline-any cline-no">&nbsp;</span>
696
+ <span class="cline-any cline-no">&nbsp;</span>
697
+ <span class="cline-any cline-no">&nbsp;</span>
698
+ <span class="cline-any cline-no">&nbsp;</span>
699
+ <span class="cline-any cline-no">&nbsp;</span>
700
+ <span class="cline-any cline-no">&nbsp;</span>
701
+ <span class="cline-any cline-no">&nbsp;</span>
702
+ <span class="cline-any cline-no">&nbsp;</span>
703
+ <span class="cline-any cline-no">&nbsp;</span>
704
+ <span class="cline-any cline-no">&nbsp;</span>
705
+ <span class="cline-any cline-no">&nbsp;</span>
706
+ <span class="cline-any cline-no">&nbsp;</span>
707
+ <span class="cline-any cline-no">&nbsp;</span>
708
+ <span class="cline-any cline-no">&nbsp;</span>
709
+ <span class="cline-any cline-no">&nbsp;</span>
710
+ <span class="cline-any cline-no">&nbsp;</span>
711
+ <span class="cline-any cline-no">&nbsp;</span>
712
+ <span class="cline-any cline-no">&nbsp;</span>
713
+ <span class="cline-any cline-no">&nbsp;</span>
714
+ <span class="cline-any cline-no">&nbsp;</span>
715
+ <span class="cline-any cline-no">&nbsp;</span>
716
+ <span class="cline-any cline-no">&nbsp;</span>
717
+ <span class="cline-any cline-no">&nbsp;</span>
718
+ <span class="cline-any cline-no">&nbsp;</span>
719
+ <span class="cline-any cline-no">&nbsp;</span>
720
+ <span class="cline-any cline-no">&nbsp;</span>
721
+ <span class="cline-any cline-no">&nbsp;</span>
722
+ <span class="cline-any cline-no">&nbsp;</span>
723
+ <span class="cline-any cline-no">&nbsp;</span>
724
+ <span class="cline-any cline-no">&nbsp;</span>
725
+ <span class="cline-any cline-no">&nbsp;</span>
726
+ <span class="cline-any cline-no">&nbsp;</span>
727
+ <span class="cline-any cline-no">&nbsp;</span>
728
+ <span class="cline-any cline-no">&nbsp;</span>
729
+ <span class="cline-any cline-yes">1x</span>
730
+ <span class="cline-any cline-yes">1x</span>
731
+ <span class="cline-any cline-yes">1x</span>
732
+ <span class="cline-any cline-yes">1x</span>
733
+ <span class="cline-any cline-yes">1x</span>
734
+ <span class="cline-any cline-no">&nbsp;</span>
735
+ <span class="cline-any cline-no">&nbsp;</span>
736
+ <span class="cline-any cline-no">&nbsp;</span>
737
+ <span class="cline-any cline-no">&nbsp;</span>
738
+ <span class="cline-any cline-no">&nbsp;</span>
739
+ <span class="cline-any cline-no">&nbsp;</span>
740
+ <span class="cline-any cline-no">&nbsp;</span>
741
+ <span class="cline-any cline-no">&nbsp;</span>
742
+ <span class="cline-any cline-no">&nbsp;</span>
743
+ <span class="cline-any cline-no">&nbsp;</span>
744
+ <span class="cline-any cline-no">&nbsp;</span>
745
+ <span class="cline-any cline-no">&nbsp;</span>
746
+ <span class="cline-any cline-no">&nbsp;</span>
747
+ <span class="cline-any cline-no">&nbsp;</span>
748
+ <span class="cline-any cline-no">&nbsp;</span>
749
+ <span class="cline-any cline-no">&nbsp;</span>
750
+ <span class="cline-any cline-no">&nbsp;</span>
751
+ <span class="cline-any cline-yes">1x</span>
752
+ <span class="cline-any cline-yes">1x</span>
753
+ <span class="cline-any cline-yes">1x</span>
754
+ <span class="cline-any cline-yes">1x</span>
755
+ <span class="cline-any cline-yes">1x</span>
756
+ <span class="cline-any cline-no">&nbsp;</span>
757
+ <span class="cline-any cline-no">&nbsp;</span>
758
+ <span class="cline-any cline-no">&nbsp;</span>
759
+ <span class="cline-any cline-no">&nbsp;</span>
760
+ <span class="cline-any cline-no">&nbsp;</span>
761
+ <span class="cline-any cline-no">&nbsp;</span>
762
+ <span class="cline-any cline-no">&nbsp;</span>
763
+ <span class="cline-any cline-no">&nbsp;</span>
764
+ <span class="cline-any cline-no">&nbsp;</span>
765
+ <span class="cline-any cline-no">&nbsp;</span>
766
+ <span class="cline-any cline-no">&nbsp;</span>
767
+ <span class="cline-any cline-no">&nbsp;</span>
768
+ <span class="cline-any cline-no">&nbsp;</span>
769
+ <span class="cline-any cline-no">&nbsp;</span>
770
+ <span class="cline-any cline-no">&nbsp;</span>
771
+ <span class="cline-any cline-no">&nbsp;</span>
772
+ <span class="cline-any cline-no">&nbsp;</span>
773
+ <span class="cline-any cline-no">&nbsp;</span>
774
+ <span class="cline-any cline-no">&nbsp;</span>
775
+ <span class="cline-any cline-no">&nbsp;</span>
776
+ <span class="cline-any cline-no">&nbsp;</span>
777
+ <span class="cline-any cline-no">&nbsp;</span>
778
+ <span class="cline-any cline-no">&nbsp;</span>
779
+ <span class="cline-any cline-no">&nbsp;</span>
780
+ <span class="cline-any cline-no">&nbsp;</span>
781
+ <span class="cline-any cline-no">&nbsp;</span>
782
+ <span class="cline-any cline-no">&nbsp;</span>
783
+ <span class="cline-any cline-yes">1x</span>
784
+ <span class="cline-any cline-yes">1x</span>
785
+ <span class="cline-any cline-yes">1x</span>
786
+ <span class="cline-any cline-yes">1x</span>
787
+ <span class="cline-any cline-yes">1x</span>
788
+ <span class="cline-any cline-yes">1x</span>
789
+ <span class="cline-any cline-yes">1x</span>
790
+ <span class="cline-any cline-yes">1x</span>
791
+ <span class="cline-any cline-yes">1x</span>
792
+ <span class="cline-any cline-yes">1x</span>
793
+ <span class="cline-any cline-yes">1x</span>
794
+ <span class="cline-any cline-yes">1x</span>
795
+ <span class="cline-any cline-yes">1x</span>
796
+ <span class="cline-any cline-no">&nbsp;</span>
797
+ <span class="cline-any cline-no">&nbsp;</span>
798
+ <span class="cline-any cline-no">&nbsp;</span>
799
+ <span class="cline-any cline-no">&nbsp;</span>
800
+ <span class="cline-any cline-no">&nbsp;</span>
801
+ <span class="cline-any cline-no">&nbsp;</span>
802
+ <span class="cline-any cline-no">&nbsp;</span>
803
+ <span class="cline-any cline-no">&nbsp;</span>
804
+ <span class="cline-any cline-no">&nbsp;</span>
805
+ <span class="cline-any cline-no">&nbsp;</span>
806
+ <span class="cline-any cline-no">&nbsp;</span>
807
+ <span class="cline-any cline-no">&nbsp;</span>
808
+ <span class="cline-any cline-no">&nbsp;</span>
809
+ <span class="cline-any cline-no">&nbsp;</span>
810
+ <span class="cline-any cline-no">&nbsp;</span>
811
+ <span class="cline-any cline-no">&nbsp;</span>
812
+ <span class="cline-any cline-no">&nbsp;</span>
813
+ <span class="cline-any cline-no">&nbsp;</span>
814
+ <span class="cline-any cline-no">&nbsp;</span>
815
+ <span class="cline-any cline-no">&nbsp;</span>
816
+ <span class="cline-any cline-no">&nbsp;</span>
817
+ <span class="cline-any cline-no">&nbsp;</span>
818
+ <span class="cline-any cline-no">&nbsp;</span>
819
+ <span class="cline-any cline-no">&nbsp;</span>
820
+ <span class="cline-any cline-no">&nbsp;</span>
821
+ <span class="cline-any cline-no">&nbsp;</span>
822
+ <span class="cline-any cline-no">&nbsp;</span>
823
+ <span class="cline-any cline-no">&nbsp;</span>
824
+ <span class="cline-any cline-no">&nbsp;</span>
825
+ <span class="cline-any cline-no">&nbsp;</span>
826
+ <span class="cline-any cline-no">&nbsp;</span>
827
+ <span class="cline-any cline-no">&nbsp;</span>
828
+ <span class="cline-any cline-no">&nbsp;</span>
829
+ <span class="cline-any cline-no">&nbsp;</span>
830
+ <span class="cline-any cline-no">&nbsp;</span>
831
+ <span class="cline-any cline-yes">1x</span>
832
+ <span class="cline-any cline-yes">1x</span>
833
+ <span class="cline-any cline-yes">1x</span>
834
+ <span class="cline-any cline-yes">1x</span>
835
+ <span class="cline-any cline-yes">1x</span>
836
+ <span class="cline-any cline-no">&nbsp;</span>
837
+ <span class="cline-any cline-no">&nbsp;</span>
838
+ <span class="cline-any cline-no">&nbsp;</span>
839
+ <span class="cline-any cline-no">&nbsp;</span>
840
+ <span class="cline-any cline-no">&nbsp;</span>
841
+ <span class="cline-any cline-no">&nbsp;</span>
842
+ <span class="cline-any cline-no">&nbsp;</span>
843
+ <span class="cline-any cline-no">&nbsp;</span>
844
+ <span class="cline-any cline-no">&nbsp;</span>
845
+ <span class="cline-any cline-no">&nbsp;</span>
846
+ <span class="cline-any cline-yes">1x</span>
847
+ <span class="cline-any cline-yes">1x</span>
848
+ <span class="cline-any cline-yes">1x</span>
849
+ <span class="cline-any cline-yes">1x</span>
850
+ <span class="cline-any cline-yes">1x</span>
851
+ <span class="cline-any cline-no">&nbsp;</span>
852
+ <span class="cline-any cline-no">&nbsp;</span>
853
+ <span class="cline-any cline-no">&nbsp;</span>
854
+ <span class="cline-any cline-no">&nbsp;</span>
855
+ <span class="cline-any cline-no">&nbsp;</span>
856
+ <span class="cline-any cline-no">&nbsp;</span>
857
+ <span class="cline-any cline-no">&nbsp;</span>
858
+ <span class="cline-any cline-no">&nbsp;</span>
859
+ <span class="cline-any cline-no">&nbsp;</span>
860
+ <span class="cline-any cline-yes">1x</span>
861
+ <span class="cline-any cline-yes">1x</span>
862
+ <span class="cline-any cline-yes">1x</span>
863
+ <span class="cline-any cline-yes">1x</span>
864
+ <span class="cline-any cline-yes">1x</span>
865
+ <span class="cline-any cline-no">&nbsp;</span>
866
+ <span class="cline-any cline-no">&nbsp;</span>
867
+ <span class="cline-any cline-no">&nbsp;</span>
868
+ <span class="cline-any cline-no">&nbsp;</span>
869
+ <span class="cline-any cline-no">&nbsp;</span>
870
+ <span class="cline-any cline-no">&nbsp;</span>
871
+ <span class="cline-any cline-yes">1x</span>
872
+ <span class="cline-any cline-yes">1x</span>
873
+ <span class="cline-any cline-yes">1x</span>
874
+ <span class="cline-any cline-yes">1x</span>
875
+ <span class="cline-any cline-yes">1x</span>
876
+ <span class="cline-any cline-no">&nbsp;</span>
877
+ <span class="cline-any cline-no">&nbsp;</span>
878
+ <span class="cline-any cline-no">&nbsp;</span>
879
+ <span class="cline-any cline-no">&nbsp;</span>
880
+ <span class="cline-any cline-no">&nbsp;</span>
881
+ <span class="cline-any cline-no">&nbsp;</span>
882
+ <span class="cline-any cline-no">&nbsp;</span>
883
+ <span class="cline-any cline-no">&nbsp;</span>
884
+ <span class="cline-any cline-no">&nbsp;</span>
885
+ <span class="cline-any cline-no">&nbsp;</span>
886
+ <span class="cline-any cline-no">&nbsp;</span>
887
+ <span class="cline-any cline-no">&nbsp;</span>
888
+ <span class="cline-any cline-no">&nbsp;</span>
889
+ <span class="cline-any cline-no">&nbsp;</span>
890
+ <span class="cline-any cline-yes">1x</span>
891
+ <span class="cline-any cline-yes">1x</span>
892
+ <span class="cline-any cline-yes">1x</span>
893
+ <span class="cline-any cline-yes">1x</span>
894
+ <span class="cline-any cline-yes">1x</span>
895
+ <span class="cline-any cline-no">&nbsp;</span>
896
+ <span class="cline-any cline-no">&nbsp;</span>
897
+ <span class="cline-any cline-no">&nbsp;</span>
898
+ <span class="cline-any cline-no">&nbsp;</span>
899
+ <span class="cline-any cline-no">&nbsp;</span>
900
+ <span class="cline-any cline-no">&nbsp;</span>
901
+ <span class="cline-any cline-no">&nbsp;</span>
902
+ <span class="cline-any cline-no">&nbsp;</span>
903
+ <span class="cline-any cline-no">&nbsp;</span>
904
+ <span class="cline-any cline-no">&nbsp;</span>
905
+ <span class="cline-any cline-no">&nbsp;</span>
906
+ <span class="cline-any cline-no">&nbsp;</span>
907
+ <span class="cline-any cline-no">&nbsp;</span>
908
+ <span class="cline-any cline-no">&nbsp;</span>
909
+ <span class="cline-any cline-no">&nbsp;</span>
910
+ <span class="cline-any cline-no">&nbsp;</span>
911
+ <span class="cline-any cline-no">&nbsp;</span>
912
+ <span class="cline-any cline-no">&nbsp;</span>
913
+ <span class="cline-any cline-no">&nbsp;</span>
914
+ <span class="cline-any cline-no">&nbsp;</span>
915
+ <span class="cline-any cline-yes">1x</span>
916
+ <span class="cline-any cline-yes">1x</span>
917
+ <span class="cline-any cline-yes">1x</span>
918
+ <span class="cline-any cline-yes">1x</span>
919
+ <span class="cline-any cline-yes">1x</span>
920
+ <span class="cline-any cline-no">&nbsp;</span>
921
+ <span class="cline-any cline-no">&nbsp;</span>
922
+ <span class="cline-any cline-no">&nbsp;</span>
923
+ <span class="cline-any cline-no">&nbsp;</span>
924
+ <span class="cline-any cline-no">&nbsp;</span>
925
+ <span class="cline-any cline-no">&nbsp;</span>
926
+ <span class="cline-any cline-no">&nbsp;</span>
927
+ <span class="cline-any cline-no">&nbsp;</span>
928
+ <span class="cline-any cline-no">&nbsp;</span>
929
+ <span class="cline-any cline-no">&nbsp;</span>
930
+ <span class="cline-any cline-no">&nbsp;</span>
931
+ <span class="cline-any cline-no">&nbsp;</span>
932
+ <span class="cline-any cline-no">&nbsp;</span>
933
+ <span class="cline-any cline-no">&nbsp;</span>
934
+ <span class="cline-any cline-no">&nbsp;</span>
935
+ <span class="cline-any cline-no">&nbsp;</span>
936
+ <span class="cline-any cline-no">&nbsp;</span>
937
+ <span class="cline-any cline-no">&nbsp;</span>
938
+ <span class="cline-any cline-no">&nbsp;</span>
939
+ <span class="cline-any cline-no">&nbsp;</span>
940
+ <span class="cline-any cline-no">&nbsp;</span>
941
+ <span class="cline-any cline-no">&nbsp;</span>
942
+ <span class="cline-any cline-no">&nbsp;</span>
943
+ <span class="cline-any cline-no">&nbsp;</span>
944
+ <span class="cline-any cline-no">&nbsp;</span>
945
+ <span class="cline-any cline-no">&nbsp;</span>
946
+ <span class="cline-any cline-no">&nbsp;</span>
947
+ <span class="cline-any cline-no">&nbsp;</span>
948
+ <span class="cline-any cline-no">&nbsp;</span>
949
+ <span class="cline-any cline-no">&nbsp;</span>
950
+ <span class="cline-any cline-no">&nbsp;</span>
951
+ <span class="cline-any cline-no">&nbsp;</span>
952
+ <span class="cline-any cline-no">&nbsp;</span>
953
+ <span class="cline-any cline-no">&nbsp;</span>
954
+ <span class="cline-any cline-no">&nbsp;</span>
955
+ <span class="cline-any cline-no">&nbsp;</span>
956
+ <span class="cline-any cline-no">&nbsp;</span>
957
+ <span class="cline-any cline-no">&nbsp;</span>
958
+ <span class="cline-any cline-no">&nbsp;</span>
959
+ <span class="cline-any cline-no">&nbsp;</span>
960
+ <span class="cline-any cline-no">&nbsp;</span>
961
+ <span class="cline-any cline-no">&nbsp;</span>
962
+ <span class="cline-any cline-yes">1x</span>
963
+ <span class="cline-any cline-yes">1x</span>
964
+ <span class="cline-any cline-yes">1x</span>
965
+ <span class="cline-any cline-yes">1x</span>
966
+ <span class="cline-any cline-yes">1x</span>
967
+ <span class="cline-any cline-no">&nbsp;</span>
968
+ <span class="cline-any cline-no">&nbsp;</span>
969
+ <span class="cline-any cline-no">&nbsp;</span>
970
+ <span class="cline-any cline-no">&nbsp;</span>
971
+ <span class="cline-any cline-no">&nbsp;</span>
972
+ <span class="cline-any cline-no">&nbsp;</span>
973
+ <span class="cline-any cline-no">&nbsp;</span>
974
+ <span class="cline-any cline-no">&nbsp;</span>
975
+ <span class="cline-any cline-no">&nbsp;</span>
976
+ <span class="cline-any cline-no">&nbsp;</span>
977
+ <span class="cline-any cline-yes">1x</span>
978
+ <span class="cline-any cline-yes">1x</span>
979
+ <span class="cline-any cline-yes">1x</span>
980
+ <span class="cline-any cline-yes">1x</span>
981
+ <span class="cline-any cline-yes">1x</span>
982
+ <span class="cline-any cline-no">&nbsp;</span>
983
+ <span class="cline-any cline-no">&nbsp;</span>
984
+ <span class="cline-any cline-no">&nbsp;</span>
985
+ <span class="cline-any cline-no">&nbsp;</span>
986
+ <span class="cline-any cline-no">&nbsp;</span>
987
+ <span class="cline-any cline-no">&nbsp;</span>
988
+ <span class="cline-any cline-no">&nbsp;</span>
989
+ <span class="cline-any cline-no">&nbsp;</span>
990
+ <span class="cline-any cline-no">&nbsp;</span>
991
+ <span class="cline-any cline-no">&nbsp;</span>
992
+ <span class="cline-any cline-no">&nbsp;</span>
993
+ <span class="cline-any cline-no">&nbsp;</span>
994
+ <span class="cline-any cline-no">&nbsp;</span>
995
+ <span class="cline-any cline-no">&nbsp;</span>
996
+ <span class="cline-any cline-no">&nbsp;</span>
997
+ <span class="cline-any cline-no">&nbsp;</span>
998
+ <span class="cline-any cline-no">&nbsp;</span>
999
+ <span class="cline-any cline-no">&nbsp;</span>
1000
+ <span class="cline-any cline-no">&nbsp;</span>
1001
+ <span class="cline-any cline-no">&nbsp;</span>
1002
+ <span class="cline-any cline-no">&nbsp;</span>
1003
+ <span class="cline-any cline-no">&nbsp;</span>
1004
+ <span class="cline-any cline-no">&nbsp;</span>
1005
+ <span class="cline-any cline-no">&nbsp;</span>
1006
+ <span class="cline-any cline-yes">1x</span>
1007
+ <span class="cline-any cline-yes">1x</span>
1008
+ <span class="cline-any cline-yes">1x</span>
1009
+ <span class="cline-any cline-yes">1x</span>
1010
+ <span class="cline-any cline-yes">1x</span>
1011
+ <span class="cline-any cline-no">&nbsp;</span>
1012
+ <span class="cline-any cline-no">&nbsp;</span>
1013
+ <span class="cline-any cline-no">&nbsp;</span>
1014
+ <span class="cline-any cline-yes">1x</span>
1015
+ <span class="cline-any cline-yes">1x</span>
1016
+ <span class="cline-any cline-yes">1x</span>
1017
+ <span class="cline-any cline-yes">1x</span>
1018
+ <span class="cline-any cline-yes">1x</span>
1019
+ <span class="cline-any cline-yes">1x</span>
1020
+ <span class="cline-any cline-yes">1x</span>
1021
+ <span class="cline-any cline-no">&nbsp;</span>
1022
+ <span class="cline-any cline-no">&nbsp;</span>
1023
+ <span class="cline-any cline-yes">1x</span>
1024
+ <span class="cline-any cline-yes">1x</span>
1025
+ <span class="cline-any cline-yes">1x</span>
1026
+ <span class="cline-any cline-yes">1x</span>
1027
+ <span class="cline-any cline-yes">1x</span>
1028
+ <span class="cline-any cline-no">&nbsp;</span>
1029
+ <span class="cline-any cline-no">&nbsp;</span>
1030
+ <span class="cline-any cline-no">&nbsp;</span>
1031
+ <span class="cline-any cline-no">&nbsp;</span>
1032
+ <span class="cline-any cline-no">&nbsp;</span>
1033
+ <span class="cline-any cline-no">&nbsp;</span>
1034
+ <span class="cline-any cline-no">&nbsp;</span>
1035
+ <span class="cline-any cline-no">&nbsp;</span>
1036
+ <span class="cline-any cline-no">&nbsp;</span>
1037
+ <span class="cline-any cline-no">&nbsp;</span>
1038
+ <span class="cline-any cline-no">&nbsp;</span>
1039
+ <span class="cline-any cline-no">&nbsp;</span>
1040
+ <span class="cline-any cline-no">&nbsp;</span>
1041
+ <span class="cline-any cline-no">&nbsp;</span>
1042
+ <span class="cline-any cline-no">&nbsp;</span>
1043
+ <span class="cline-any cline-no">&nbsp;</span>
1044
+ <span class="cline-any cline-yes">1x</span>
1045
+ <span class="cline-any cline-yes">1x</span>
1046
+ <span class="cline-any cline-yes">1x</span>
1047
+ <span class="cline-any cline-yes">1x</span>
1048
+ <span class="cline-any cline-yes">1x</span>
1049
+ <span class="cline-any cline-no">&nbsp;</span>
1050
+ <span class="cline-any cline-no">&nbsp;</span>
1051
+ <span class="cline-any cline-no">&nbsp;</span>
1052
+ <span class="cline-any cline-no">&nbsp;</span>
1053
+ <span class="cline-any cline-no">&nbsp;</span>
1054
+ <span class="cline-any cline-no">&nbsp;</span>
1055
+ <span class="cline-any cline-no">&nbsp;</span>
1056
+ <span class="cline-any cline-no">&nbsp;</span>
1057
+ <span class="cline-any cline-no">&nbsp;</span>
1058
+ <span class="cline-any cline-no">&nbsp;</span>
1059
+ <span class="cline-any cline-no">&nbsp;</span>
1060
+ <span class="cline-any cline-no">&nbsp;</span>
1061
+ <span class="cline-any cline-yes">1x</span>
1062
+ <span class="cline-any cline-yes">1x</span>
1063
+ <span class="cline-any cline-yes">1x</span>
1064
+ <span class="cline-any cline-yes">1x</span>
1065
+ <span class="cline-any cline-yes">1x</span>
1066
+ <span class="cline-any cline-yes">1x</span>
1067
+ <span class="cline-any cline-yes">1x</span>
1068
+ <span class="cline-any cline-yes">1x</span>
1069
+ <span class="cline-any cline-yes">1x</span>
1070
+ <span class="cline-any cline-yes">1x</span>
1071
+ <span class="cline-any cline-yes">1x</span>
1072
+ <span class="cline-any cline-yes">1x</span>
1073
+ <span class="cline-any cline-yes">1x</span>
1074
+ <span class="cline-any cline-no">&nbsp;</span>
1075
+ <span class="cline-any cline-no">&nbsp;</span>
1076
+ <span class="cline-any cline-no">&nbsp;</span>
1077
+ <span class="cline-any cline-no">&nbsp;</span>
1078
+ <span class="cline-any cline-no">&nbsp;</span>
1079
+ <span class="cline-any cline-no">&nbsp;</span>
1080
+ <span class="cline-any cline-yes">1x</span>
1081
+ <span class="cline-any cline-yes">1x</span>
1082
+ <span class="cline-any cline-yes">1x</span>
1083
+ <span class="cline-any cline-yes">1x</span>
1084
+ <span class="cline-any cline-yes">1x</span>
1085
+ <span class="cline-any cline-yes">1x</span>
1086
+ <span class="cline-any cline-yes">1x</span>
1087
+ <span class="cline-any cline-yes">1x</span>
1088
+ <span class="cline-any cline-yes">1x</span>
1089
+ <span class="cline-any cline-yes">1x</span>
1090
+ <span class="cline-any cline-yes">1x</span>
1091
+ <span class="cline-any cline-yes">1x</span>
1092
+ <span class="cline-any cline-yes">1x</span>
1093
+ <span class="cline-any cline-yes">1x</span>
1094
+ <span class="cline-any cline-yes">1x</span>
1095
+ <span class="cline-any cline-yes">1x</span>
1096
+ <span class="cline-any cline-yes">1x</span>
1097
+ <span class="cline-any cline-yes">1x</span>
1098
+ <span class="cline-any cline-yes">1x</span>
1099
+ <span class="cline-any cline-yes">1x</span>
1100
+ <span class="cline-any cline-no">&nbsp;</span>
1101
+ <span class="cline-any cline-no">&nbsp;</span>
1102
+ <span class="cline-any cline-no">&nbsp;</span>
1103
+ <span class="cline-any cline-no">&nbsp;</span>
1104
+ <span class="cline-any cline-no">&nbsp;</span>
1105
+ <span class="cline-any cline-no">&nbsp;</span>
1106
+ <span class="cline-any cline-no">&nbsp;</span>
1107
+ <span class="cline-any cline-no">&nbsp;</span>
1108
+ <span class="cline-any cline-no">&nbsp;</span>
1109
+ <span class="cline-any cline-yes">1x</span>
1110
+ <span class="cline-any cline-yes">1x</span>
1111
+ <span class="cline-any cline-yes">1x</span>
1112
+ <span class="cline-any cline-yes">1x</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-no">&nbsp;</span>
1116
+ <span class="cline-any cline-no">&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-yes">1x</span>
1120
+ <span class="cline-any cline-yes">1x</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-no">&nbsp;</span>
1124
+ <span class="cline-any cline-no">&nbsp;</span>
1125
+ <span class="cline-any cline-no">&nbsp;</span>
1126
+ <span class="cline-any cline-no">&nbsp;</span>
1127
+ <span class="cline-any cline-yes">1x</span>
1128
+ <span class="cline-any cline-yes">1x</span>
1129
+ <span class="cline-any cline-no">&nbsp;</span>
1130
+ <span class="cline-any cline-no">&nbsp;</span>
1131
+ <span class="cline-any cline-no">&nbsp;</span>
1132
+ <span class="cline-any cline-no">&nbsp;</span>
1133
+ <span class="cline-any cline-no">&nbsp;</span>
1134
+ <span class="cline-any cline-no">&nbsp;</span>
1135
+ <span class="cline-any cline-no">&nbsp;</span>
1136
+ <span class="cline-any cline-no">&nbsp;</span>
1137
+ <span class="cline-any cline-no">&nbsp;</span>
1138
+ <span class="cline-any cline-no">&nbsp;</span>
1139
+ <span class="cline-any cline-no">&nbsp;</span>
1140
+ <span class="cline-any cline-no">&nbsp;</span>
1141
+ <span class="cline-any cline-no">&nbsp;</span>
1142
+ <span class="cline-any cline-no">&nbsp;</span>
1143
+ <span class="cline-any cline-no">&nbsp;</span>
1144
+ <span class="cline-any cline-no">&nbsp;</span>
1145
+ <span class="cline-any cline-no">&nbsp;</span>
1146
+ <span class="cline-any cline-no">&nbsp;</span>
1147
+ <span class="cline-any cline-no">&nbsp;</span>
1148
+ <span class="cline-any cline-no">&nbsp;</span>
1149
+ <span class="cline-any cline-no">&nbsp;</span>
1150
+ <span class="cline-any cline-no">&nbsp;</span>
1151
+ <span class="cline-any cline-no">&nbsp;</span>
1152
+ <span class="cline-any cline-no">&nbsp;</span>
1153
+ <span class="cline-any cline-no">&nbsp;</span>
1154
+ <span class="cline-any cline-no">&nbsp;</span>
1155
+ <span class="cline-any cline-no">&nbsp;</span>
1156
+ <span class="cline-any cline-no">&nbsp;</span>
1157
+ <span class="cline-any cline-no">&nbsp;</span>
1158
+ <span class="cline-any cline-no">&nbsp;</span>
1159
+ <span class="cline-any cline-no">&nbsp;</span>
1160
+ <span class="cline-any cline-no">&nbsp;</span>
1161
+ <span class="cline-any cline-no">&nbsp;</span>
1162
+ <span class="cline-any cline-no">&nbsp;</span>
1163
+ <span class="cline-any cline-no">&nbsp;</span>
1164
+ <span class="cline-any cline-no">&nbsp;</span>
1165
+ <span class="cline-any cline-no">&nbsp;</span>
1166
+ <span class="cline-any cline-no">&nbsp;</span>
1167
+ <span class="cline-any cline-no">&nbsp;</span>
1168
+ <span class="cline-any cline-no">&nbsp;</span>
1169
+ <span class="cline-any cline-no">&nbsp;</span>
1170
+ <span class="cline-any cline-no">&nbsp;</span>
1171
+ <span class="cline-any cline-no">&nbsp;</span>
1172
+ <span class="cline-any cline-no">&nbsp;</span>
1173
+ <span class="cline-any cline-no">&nbsp;</span>
1174
+ <span class="cline-any cline-no">&nbsp;</span>
1175
+ <span class="cline-any cline-no">&nbsp;</span>
1176
+ <span class="cline-any cline-no">&nbsp;</span>
1177
+ <span class="cline-any cline-no">&nbsp;</span>
1178
+ <span class="cline-any cline-no">&nbsp;</span>
1179
+ <span class="cline-any cline-no">&nbsp;</span>
1180
+ <span class="cline-any cline-no">&nbsp;</span>
1181
+ <span class="cline-any cline-no">&nbsp;</span>
1182
+ <span class="cline-any cline-no">&nbsp;</span>
1183
+ <span class="cline-any cline-no">&nbsp;</span>
1184
+ <span class="cline-any cline-no">&nbsp;</span>
1185
+ <span class="cline-any cline-no">&nbsp;</span>
1186
+ <span class="cline-any cline-no">&nbsp;</span>
1187
+ <span class="cline-any cline-yes">1x</span>
1188
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">// =============================
1189
+ // TOKEN STORAGE UTILITY
1190
+ // =============================
1191
+ &nbsp;
1192
+ /**
1193
+ * Utility para manejo seguro de tokens en localStorage/sessionStorage
1194
+ * Implementa el Refresh Token Pattern con persistencia
1195
+ *
1196
+ * Updated to use native Web Crypto API (replaces crypto-js)
1197
+ */
1198
+ &nbsp;
1199
+ import { sha256, deriveKey, encrypt, decrypt, generateSalt, isNewFormat } from "./webCrypto";
1200
+ import { logger } from "./logger";
1201
+ &nbsp;
1202
+ export type TokenData = {
1203
+ accessToken: string;
1204
+ refreshToken: string;
1205
+ expiresAt: number;
1206
+ refreshExpiresAt: number;
1207
+ apiEndpointAdmin?: string;
1208
+ apiKeyEndpointAdmin?: string;
1209
+ };
1210
+ &nbsp;
1211
+ export type StorageType = "localStorage" | "sessionStorage" | "none";
1212
+ &nbsp;
1213
+ const STORAGE_VERSION_KEY = "crudify_storage_version";
1214
+ const CURRENT_STORAGE_VERSION = 2;
1215
+ &nbsp;
1216
+ export class TokenStorage {
1217
+ private static readonly TOKEN_KEY = "crudify_tokens";
1218
+ private static readonly ENCRYPTION_KEY_STORAGE = "crudify_enc_key";
1219
+ private static readonly SALT_KEY = "crudify_enc_salt";
1220
+ private static encryptionKey: CryptoKey | null = null;
1221
+ private static salt: Uint8Array | null = null;
1222
+ private static fingerprint: string | null = null;
1223
+ private static storageType: StorageType = "localStorage";
1224
+ private static initPromise: Promise&lt;void&gt; | null = null;
1225
+ &nbsp;
1226
+ // Login Grace Period - protección contra eventos cross-tab espurios después del login
1227
+ private static lastSuccessfulLoginTime: number = 0;
1228
+ private static readonly LOGIN_GRACE_PERIOD_MS = 5000; // 5 segundos
1229
+ &nbsp;
1230
+ /**
1231
+ * Configurar tipo de almacenamiento
1232
+ */
1233
+ static <span class="fstat-no" title="function not covered" >setStorageType(type: StorageType): void {</span>
1234
+ <span class="cstat-no" title="statement not covered" > TokenStorage.storageType = type;</span>
1235
+ <span class="cstat-no" title="statement not covered" > }</span>
1236
+ &nbsp;
1237
+ /**
1238
+ * Initialize encryption key (async)
1239
+ * Must be called before encrypt/decrypt operations
1240
+ *
1241
+ * Incluye lógica de retry para manejar casos donde localStorage
1242
+ * puede no estar disponible momentáneamente (ej: durante hydration)
1243
+ */
1244
+ <span class="fstat-no" title="function not covered" > private static async initialize(): Promise&lt;void&gt; {</span>
1245
+ <span class="cstat-no" title="statement not covered" > if (TokenStorage.encryptionKey &amp;&amp; TokenStorage.salt) {</span>
1246
+ <span class="cstat-no" title="statement not covered" > return; // Already initialized</span>
1247
+ <span class="cstat-no" title="statement not covered" > }</span>
1248
+ <span class="cstat-no" title="statement not covered" ></span>
1249
+ <span class="cstat-no" title="statement not covered" > if (TokenStorage.initPromise) {</span>
1250
+ <span class="cstat-no" title="statement not covered" > return TokenStorage.initPromise; // Initialization in progress</span>
1251
+ <span class="cstat-no" title="statement not covered" > }</span>
1252
+ <span class="cstat-no" title="statement not covered" ></span>
1253
+ <span class="cstat-no" title="statement not covered" > TokenStorage.initPromise = (async () =&gt; {</span>
1254
+ <span class="cstat-no" title="statement not covered" > const MAX_RETRIES = 3;</span>
1255
+ <span class="cstat-no" title="statement not covered" > let lastError: Error | null = null;</span>
1256
+ <span class="cstat-no" title="statement not covered" ></span>
1257
+ <span class="cstat-no" title="statement not covered" > for (let attempt = 1; attempt &lt;= MAX_RETRIES; attempt++) {</span>
1258
+ <span class="cstat-no" title="statement not covered" > try {</span>
1259
+ <span class="cstat-no" title="statement not covered" > // Check storage version and clear if outdated</span>
1260
+ <span class="cstat-no" title="statement not covered" > TokenStorage.checkStorageVersion();</span>
1261
+ <span class="cstat-no" title="statement not covered" ></span>
1262
+ <span class="cstat-no" title="statement not covered" > // Get or create salt</span>
1263
+ <span class="cstat-no" title="statement not covered" > TokenStorage.salt = TokenStorage.getOrCreateSalt();</span>
1264
+ <span class="cstat-no" title="statement not covered" ></span>
1265
+ <span class="cstat-no" title="statement not covered" > // Generate fingerprint (determinístico)</span>
1266
+ <span class="cstat-no" title="statement not covered" > TokenStorage.fingerprint = await TokenStorage.generateFingerprint();</span>
1267
+ <span class="cstat-no" title="statement not covered" ></span>
1268
+ <span class="cstat-no" title="statement not covered" > // Derive encryption key</span>
1269
+ <span class="cstat-no" title="statement not covered" > TokenStorage.encryptionKey = await deriveKey(TokenStorage.fingerprint, TokenStorage.salt);</span>
1270
+ <span class="cstat-no" title="statement not covered" ></span>
1271
+ <span class="cstat-no" title="statement not covered" > // Éxito - salir del loop</span>
1272
+ <span class="cstat-no" title="statement not covered" > return;</span>
1273
+ <span class="cstat-no" title="statement not covered" > } catch (error) {</span>
1274
+ <span class="cstat-no" title="statement not covered" > lastError = error instanceof Error ? error : new Error(String(error));</span>
1275
+ <span class="cstat-no" title="statement not covered" ></span>
1276
+ <span class="cstat-no" title="statement not covered" > if (attempt &lt; MAX_RETRIES) {</span>
1277
+ <span class="cstat-no" title="statement not covered" > // Esperar antes de reintentar (backoff exponencial)</span>
1278
+ <span class="cstat-no" title="statement not covered" > await new Promise((resolve) =&gt; setTimeout(resolve, 100 * attempt));</span>
1279
+ <span class="cstat-no" title="statement not covered" > }</span>
1280
+ <span class="cstat-no" title="statement not covered" > }</span>
1281
+ <span class="cstat-no" title="statement not covered" > }</span>
1282
+ <span class="cstat-no" title="statement not covered" ></span>
1283
+ <span class="cstat-no" title="statement not covered" > // Si llegamos aquí, todos los intentos fallaron</span>
1284
+ <span class="cstat-no" title="statement not covered" > logger.error("Crudify: Failed to initialize encryption after retries", lastError ?? { message: "Unknown error" });</span>
1285
+ <span class="cstat-no" title="statement not covered" > throw lastError;</span>
1286
+ <span class="cstat-no" title="statement not covered" > })();</span>
1287
+ <span class="cstat-no" title="statement not covered" ></span>
1288
+ <span class="cstat-no" title="statement not covered" > return TokenStorage.initPromise;</span>
1289
+ <span class="cstat-no" title="statement not covered" > }</span>
1290
+ &nbsp;
1291
+ /**
1292
+ * Check storage version and clear if outdated (migration)
1293
+ */
1294
+ private static <span class="fstat-no" title="function not covered" >checkStorageVersion(): void {</span>
1295
+ <span class="cstat-no" title="statement not covered" > try {</span>
1296
+ <span class="cstat-no" title="statement not covered" > const version = parseInt(localStorage.getItem(STORAGE_VERSION_KEY) || "1", 10);</span>
1297
+ <span class="cstat-no" title="statement not covered" > if (version &lt; CURRENT_STORAGE_VERSION) {</span>
1298
+ <span class="cstat-no" title="statement not covered" > // Clear old encrypted data (user will need to re-authenticate)</span>
1299
+ <span class="cstat-no" title="statement not covered" > const storage = TokenStorage.getStorage();</span>
1300
+ <span class="cstat-no" title="statement not covered" > if (storage) {</span>
1301
+ <span class="cstat-no" title="statement not covered" > storage.removeItem(TokenStorage.TOKEN_KEY);</span>
1302
+ <span class="cstat-no" title="statement not covered" > }</span>
1303
+ <span class="cstat-no" title="statement not covered" > localStorage.removeItem(TokenStorage.ENCRYPTION_KEY_STORAGE);</span>
1304
+ <span class="cstat-no" title="statement not covered" > localStorage.removeItem(TokenStorage.SALT_KEY);</span>
1305
+ <span class="cstat-no" title="statement not covered" > localStorage.setItem(STORAGE_VERSION_KEY, CURRENT_STORAGE_VERSION.toString());</span>
1306
+ <span class="cstat-no" title="statement not covered" > logger.info("Crudify: Storage upgraded to v2, tokens cleared for security");</span>
1307
+ <span class="cstat-no" title="statement not covered" > }</span>
1308
+ <span class="cstat-no" title="statement not covered" > } catch {</span>
1309
+ <span class="cstat-no" title="statement not covered" > // Ignore errors during version check</span>
1310
+ <span class="cstat-no" title="statement not covered" > }</span>
1311
+ <span class="cstat-no" title="statement not covered" > }</span>
1312
+ &nbsp;
1313
+ /**
1314
+ * Get or create salt for key derivation
1315
+ */
1316
+ private static <span class="fstat-no" title="function not covered" >getOrCreateSalt(): Uint8Array {</span>
1317
+ <span class="cstat-no" title="statement not covered" > try {</span>
1318
+ <span class="cstat-no" title="statement not covered" > const existingSalt = localStorage.getItem(TokenStorage.SALT_KEY);</span>
1319
+ <span class="cstat-no" title="statement not covered" > if (existingSalt) {</span>
1320
+ <span class="cstat-no" title="statement not covered" > const binary = atob(existingSalt);</span>
1321
+ <span class="cstat-no" title="statement not covered" > const bytes = new Uint8Array(binary.length);</span>
1322
+ <span class="cstat-no" title="statement not covered" > for (let i = 0; i &lt; binary.length; i++) {</span>
1323
+ <span class="cstat-no" title="statement not covered" > bytes[i] = binary.charCodeAt(i);</span>
1324
+ <span class="cstat-no" title="statement not covered" > }</span>
1325
+ <span class="cstat-no" title="statement not covered" > return bytes;</span>
1326
+ <span class="cstat-no" title="statement not covered" > }</span>
1327
+ <span class="cstat-no" title="statement not covered" > } catch {</span>
1328
+ <span class="cstat-no" title="statement not covered" > // Fall through to generate new salt</span>
1329
+ <span class="cstat-no" title="statement not covered" > }</span>
1330
+ <span class="cstat-no" title="statement not covered" ></span>
1331
+ <span class="cstat-no" title="statement not covered" > // Generate new salt</span>
1332
+ <span class="cstat-no" title="statement not covered" > const newSalt = generateSalt();</span>
1333
+ <span class="cstat-no" title="statement not covered" > try {</span>
1334
+ <span class="cstat-no" title="statement not covered" > let binary = "";</span>
1335
+ <span class="cstat-no" title="statement not covered" > for (let i = 0; i &lt; newSalt.length; i++) {</span>
1336
+ <span class="cstat-no" title="statement not covered" > binary += String.fromCharCode(newSalt[i]);</span>
1337
+ <span class="cstat-no" title="statement not covered" > }</span>
1338
+ <span class="cstat-no" title="statement not covered" > localStorage.setItem(TokenStorage.SALT_KEY, btoa(binary));</span>
1339
+ <span class="cstat-no" title="statement not covered" > } catch {</span>
1340
+ <span class="cstat-no" title="statement not covered" > logger.warn("Crudify: Cannot persist salt, encryption may not persist across sessions");</span>
1341
+ <span class="cstat-no" title="statement not covered" > }</span>
1342
+ <span class="cstat-no" title="statement not covered" > return newSalt;</span>
1343
+ <span class="cstat-no" title="statement not covered" > }</span>
1344
+ &nbsp;
1345
+ /**
1346
+ * Generate browser fingerprint for encryption key derivation
1347
+ * IMPORTANTE: Esta función genera un fingerprint DETERMINÍSTICO basado en
1348
+ * características estables del navegador. NO usa valores aleatorios para
1349
+ * garantizar que los tokens puedan desencriptarse después de un page reload.
1350
+ *
1351
+ * Flujo:
1352
+ * 1. Si existe un hash guardado en localStorage, lo usa (consistencia)
1353
+ * 2. Si no existe, genera uno basado SOLO en características estables del navegador
1354
+ * 3. Guarda el hash para futuras sesiones
1355
+ */
1356
+ <span class="fstat-no" title="function not covered" > private static async generateFingerprint(): Promise&lt;string&gt; {</span>
1357
+ <span class="cstat-no" title="statement not covered" > // PASO 1: Verificar cache PRIMERO - esto es CRÍTICO para persistencia</span>
1358
+ <span class="cstat-no" title="statement not covered" > try {</span>
1359
+ <span class="cstat-no" title="statement not covered" > const storedHash = localStorage.getItem(TokenStorage.ENCRYPTION_KEY_STORAGE);</span>
1360
+ <span class="cstat-no" title="statement not covered" > if (storedHash &amp;&amp; storedHash.length &gt;= 32) {</span>
1361
+ <span class="cstat-no" title="statement not covered" > return storedHash;</span>
1362
+ <span class="cstat-no" title="statement not covered" > }</span>
1363
+ <span class="cstat-no" title="statement not covered" > } catch {</span>
1364
+ <span class="cstat-no" title="statement not covered" > // localStorage no disponible, continuamos con generación</span>
1365
+ <span class="cstat-no" title="statement not covered" > }</span>
1366
+ <span class="cstat-no" title="statement not covered" ></span>
1367
+ <span class="cstat-no" title="statement not covered" > // PASO 2: Generar desde características ESTABLES del navegador (SIN random)</span>
1368
+ <span class="cstat-no" title="statement not covered" > // Estas características son constantes mientras el usuario use el mismo navegador</span>
1369
+ <span class="cstat-no" title="statement not covered" > const stableComponents = [</span>
1370
+ <span class="cstat-no" title="statement not covered" > navigator.userAgent,</span>
1371
+ <span class="cstat-no" title="statement not covered" > navigator.language,</span>
1372
+ <span class="cstat-no" title="statement not covered" > navigator.platform || "unknown",</span>
1373
+ <span class="cstat-no" title="statement not covered" > String(screen.width),</span>
1374
+ <span class="cstat-no" title="statement not covered" > String(screen.height),</span>
1375
+ <span class="cstat-no" title="statement not covered" > String(screen.colorDepth || 24),</span>
1376
+ <span class="cstat-no" title="statement not covered" > String(new Date().getTimezoneOffset()),</span>
1377
+ <span class="cstat-no" title="statement not covered" > "crudify-session-v3", // Versión del algoritmo para migración futura</span>
1378
+ <span class="cstat-no" title="statement not covered" > ];</span>
1379
+ <span class="cstat-no" title="statement not covered" ></span>
1380
+ <span class="cstat-no" title="statement not covered" > const stableFingerprint = stableComponents.join("|");</span>
1381
+ <span class="cstat-no" title="statement not covered" > const hash = await sha256(stableFingerprint);</span>
1382
+ <span class="cstat-no" title="statement not covered" ></span>
1383
+ <span class="cstat-no" title="statement not covered" > // PASO 3: Persistir el hash para garantizar consistencia</span>
1384
+ <span class="cstat-no" title="statement not covered" > try {</span>
1385
+ <span class="cstat-no" title="statement not covered" > localStorage.setItem(TokenStorage.ENCRYPTION_KEY_STORAGE, hash);</span>
1386
+ <span class="cstat-no" title="statement not covered" > } catch {</span>
1387
+ <span class="cstat-no" title="statement not covered" > logger.warn("Crudify: Cannot persist encryption key hash, session may not survive page reload");</span>
1388
+ <span class="cstat-no" title="statement not covered" > }</span>
1389
+ <span class="cstat-no" title="statement not covered" ></span>
1390
+ <span class="cstat-no" title="statement not covered" > return hash;</span>
1391
+ <span class="cstat-no" title="statement not covered" > }</span>
1392
+ &nbsp;
1393
+ /**
1394
+ * Verificar si el storage está disponible
1395
+ */
1396
+ private static <span class="fstat-no" title="function not covered" >isStorageAvailable(type: "localStorage" | "sessionStorage"): boolean {</span>
1397
+ <span class="cstat-no" title="statement not covered" > try {</span>
1398
+ <span class="cstat-no" title="statement not covered" > const storage = window[type];</span>
1399
+ <span class="cstat-no" title="statement not covered" > const testKey = "__storage_test__";</span>
1400
+ <span class="cstat-no" title="statement not covered" > storage.setItem(testKey, "test");</span>
1401
+ <span class="cstat-no" title="statement not covered" > storage.removeItem(testKey);</span>
1402
+ <span class="cstat-no" title="statement not covered" > return true;</span>
1403
+ <span class="cstat-no" title="statement not covered" > } catch {</span>
1404
+ <span class="cstat-no" title="statement not covered" > return false;</span>
1405
+ <span class="cstat-no" title="statement not covered" > }</span>
1406
+ <span class="cstat-no" title="statement not covered" > }</span>
1407
+ &nbsp;
1408
+ /**
1409
+ * Obtener instancia de storage
1410
+ */
1411
+ private static <span class="fstat-no" title="function not covered" >getStorage(): Storage | null {</span>
1412
+ <span class="cstat-no" title="statement not covered" > if (TokenStorage.storageType === "none") return null;</span>
1413
+ <span class="cstat-no" title="statement not covered" ></span>
1414
+ <span class="cstat-no" title="statement not covered" > if (!TokenStorage.isStorageAvailable(TokenStorage.storageType)) {</span>
1415
+ <span class="cstat-no" title="statement not covered" > logger.warn(`Crudify: ${TokenStorage.storageType} not available, tokens won't persist`);</span>
1416
+ <span class="cstat-no" title="statement not covered" > return null;</span>
1417
+ <span class="cstat-no" title="statement not covered" > }</span>
1418
+ <span class="cstat-no" title="statement not covered" ></span>
1419
+ <span class="cstat-no" title="statement not covered" > return window[TokenStorage.storageType];</span>
1420
+ <span class="cstat-no" title="statement not covered" > }</span>
1421
+ &nbsp;
1422
+ /**
1423
+ * Encrypt data (async)
1424
+ */
1425
+ <span class="fstat-no" title="function not covered" > private static async encryptData(data: string): Promise&lt;string&gt; {</span>
1426
+ <span class="cstat-no" title="statement not covered" > await TokenStorage.initialize();</span>
1427
+ <span class="cstat-no" title="statement not covered" > if (!TokenStorage.encryptionKey || !TokenStorage.salt) {</span>
1428
+ <span class="cstat-no" title="statement not covered" > throw new Error("Encryption not initialized");</span>
1429
+ <span class="cstat-no" title="statement not covered" > }</span>
1430
+ <span class="cstat-no" title="statement not covered" > return encrypt(data, TokenStorage.encryptionKey, TokenStorage.salt);</span>
1431
+ <span class="cstat-no" title="statement not covered" > }</span>
1432
+ &nbsp;
1433
+ /**
1434
+ * Decrypt data (async)
1435
+ */
1436
+ <span class="fstat-no" title="function not covered" > private static async decryptData(encryptedData: string): Promise&lt;string | null&gt; {</span>
1437
+ <span class="cstat-no" title="statement not covered" > await TokenStorage.initialize();</span>
1438
+ <span class="cstat-no" title="statement not covered" > if (!TokenStorage.fingerprint) {</span>
1439
+ <span class="cstat-no" title="statement not covered" > throw new Error("Encryption not initialized");</span>
1440
+ <span class="cstat-no" title="statement not covered" > }</span>
1441
+ <span class="cstat-no" title="statement not covered" ></span>
1442
+ <span class="cstat-no" title="statement not covered" > // Check if this is new format</span>
1443
+ <span class="cstat-no" title="statement not covered" > if (!isNewFormat(encryptedData)) {</span>
1444
+ <span class="cstat-no" title="statement not covered" > // Legacy format - cannot decrypt, return null</span>
1445
+ <span class="cstat-no" title="statement not covered" > logger.warn("Crudify: Legacy encrypted data detected, cannot decrypt");</span>
1446
+ <span class="cstat-no" title="statement not covered" > return null;</span>
1447
+ <span class="cstat-no" title="statement not covered" > }</span>
1448
+ <span class="cstat-no" title="statement not covered" ></span>
1449
+ <span class="cstat-no" title="statement not covered" > return decrypt(encryptedData, TokenStorage.fingerprint);</span>
1450
+ <span class="cstat-no" title="statement not covered" > }</span>
1451
+ &nbsp;
1452
+ /**
1453
+ * Guardar tokens de forma segura (async)
1454
+ */
1455
+ <span class="fstat-no" title="function not covered" > static async saveTokens(tokens: TokenData): Promise&lt;void&gt; {</span>
1456
+ <span class="cstat-no" title="statement not covered" > const storage = TokenStorage.getStorage();</span>
1457
+ <span class="cstat-no" title="statement not covered" > if (!storage) return;</span>
1458
+ <span class="cstat-no" title="statement not covered" ></span>
1459
+ <span class="cstat-no" title="statement not covered" > try {</span>
1460
+ <span class="cstat-no" title="statement not covered" > const tokenData = {</span>
1461
+ <span class="cstat-no" title="statement not covered" > accessToken: tokens.accessToken,</span>
1462
+ <span class="cstat-no" title="statement not covered" > refreshToken: tokens.refreshToken,</span>
1463
+ <span class="cstat-no" title="statement not covered" > expiresAt: tokens.expiresAt,</span>
1464
+ <span class="cstat-no" title="statement not covered" > refreshExpiresAt: tokens.refreshExpiresAt,</span>
1465
+ <span class="cstat-no" title="statement not covered" > savedAt: Date.now(),</span>
1466
+ <span class="cstat-no" title="statement not covered" > };</span>
1467
+ <span class="cstat-no" title="statement not covered" ></span>
1468
+ <span class="cstat-no" title="statement not covered" > const encrypted = await TokenStorage.encryptData(JSON.stringify(tokenData));</span>
1469
+ <span class="cstat-no" title="statement not covered" > storage.setItem(TokenStorage.TOKEN_KEY, encrypted);</span>
1470
+ <span class="cstat-no" title="statement not covered" ></span>
1471
+ <span class="cstat-no" title="statement not covered" > logger.debug("Crudify: Tokens saved successfully");</span>
1472
+ <span class="cstat-no" title="statement not covered" > } catch (error: unknown) {</span>
1473
+ <span class="cstat-no" title="statement not covered" > logger.error("Crudify: Failed to save tokens", error instanceof Error ? error : { message: String(error) });</span>
1474
+ <span class="cstat-no" title="statement not covered" > }</span>
1475
+ <span class="cstat-no" title="statement not covered" > }</span>
1476
+ &nbsp;
1477
+ /**
1478
+ * Obtener tokens guardados (async)
1479
+ */
1480
+ <span class="fstat-no" title="function not covered" > static async getTokens(): Promise&lt;TokenData | null&gt; {</span>
1481
+ <span class="cstat-no" title="statement not covered" > const storage = TokenStorage.getStorage();</span>
1482
+ <span class="cstat-no" title="statement not covered" > if (!storage) return null;</span>
1483
+ <span class="cstat-no" title="statement not covered" ></span>
1484
+ <span class="cstat-no" title="statement not covered" > try {</span>
1485
+ <span class="cstat-no" title="statement not covered" > const encrypted = storage.getItem(TokenStorage.TOKEN_KEY);</span>
1486
+ <span class="cstat-no" title="statement not covered" > if (!encrypted) return null;</span>
1487
+ <span class="cstat-no" title="statement not covered" ></span>
1488
+ <span class="cstat-no" title="statement not covered" > const decrypted = await TokenStorage.decryptData(encrypted);</span>
1489
+ <span class="cstat-no" title="statement not covered" > if (!decrypted) {</span>
1490
+ <span class="cstat-no" title="statement not covered" > logger.warn("Crudify: Failed to decrypt tokens, clearing storage");</span>
1491
+ <span class="cstat-no" title="statement not covered" > await TokenStorage.clearTokens();</span>
1492
+ <span class="cstat-no" title="statement not covered" > return null;</span>
1493
+ <span class="cstat-no" title="statement not covered" > }</span>
1494
+ <span class="cstat-no" title="statement not covered" ></span>
1495
+ <span class="cstat-no" title="statement not covered" > const tokenData = JSON.parse(decrypted);</span>
1496
+ <span class="cstat-no" title="statement not covered" ></span>
1497
+ <span class="cstat-no" title="statement not covered" > // Verificar que los datos estén completos</span>
1498
+ <span class="cstat-no" title="statement not covered" > if (!tokenData.accessToken || !tokenData.refreshToken || !tokenData.expiresAt || !tokenData.refreshExpiresAt) {</span>
1499
+ <span class="cstat-no" title="statement not covered" > logger.warn("Crudify: Incomplete token data found, clearing storage");</span>
1500
+ <span class="cstat-no" title="statement not covered" > await TokenStorage.clearTokens();</span>
1501
+ <span class="cstat-no" title="statement not covered" > return null;</span>
1502
+ <span class="cstat-no" title="statement not covered" > }</span>
1503
+ <span class="cstat-no" title="statement not covered" ></span>
1504
+ <span class="cstat-no" title="statement not covered" > // Verificar que el refresh token no esté expirado</span>
1505
+ <span class="cstat-no" title="statement not covered" > if (Date.now() &gt;= tokenData.refreshExpiresAt) {</span>
1506
+ <span class="cstat-no" title="statement not covered" > logger.info("Crudify: Refresh token expired, clearing storage");</span>
1507
+ <span class="cstat-no" title="statement not covered" > await TokenStorage.clearTokens();</span>
1508
+ <span class="cstat-no" title="statement not covered" > return null;</span>
1509
+ <span class="cstat-no" title="statement not covered" > }</span>
1510
+ <span class="cstat-no" title="statement not covered" ></span>
1511
+ <span class="cstat-no" title="statement not covered" > return {</span>
1512
+ <span class="cstat-no" title="statement not covered" > accessToken: tokenData.accessToken,</span>
1513
+ <span class="cstat-no" title="statement not covered" > refreshToken: tokenData.refreshToken,</span>
1514
+ <span class="cstat-no" title="statement not covered" > expiresAt: tokenData.expiresAt,</span>
1515
+ <span class="cstat-no" title="statement not covered" > refreshExpiresAt: tokenData.refreshExpiresAt,</span>
1516
+ <span class="cstat-no" title="statement not covered" > };</span>
1517
+ <span class="cstat-no" title="statement not covered" > } catch (error: unknown) {</span>
1518
+ <span class="cstat-no" title="statement not covered" > logger.error("Crudify: Failed to retrieve tokens", error instanceof Error ? error : { message: String(error) });</span>
1519
+ <span class="cstat-no" title="statement not covered" > await TokenStorage.clearTokens(); // Limpiar datos corruptos</span>
1520
+ <span class="cstat-no" title="statement not covered" > return null;</span>
1521
+ <span class="cstat-no" title="statement not covered" > }</span>
1522
+ <span class="cstat-no" title="statement not covered" > }</span>
1523
+ &nbsp;
1524
+ /**
1525
+ * Limpiar tokens almacenados (async for consistency)
1526
+ */
1527
+ <span class="fstat-no" title="function not covered" > static async clearTokens(): Promise&lt;void&gt; {</span>
1528
+ <span class="cstat-no" title="statement not covered" > const storage = TokenStorage.getStorage();</span>
1529
+ <span class="cstat-no" title="statement not covered" > if (!storage) return;</span>
1530
+ <span class="cstat-no" title="statement not covered" ></span>
1531
+ <span class="cstat-no" title="statement not covered" > try {</span>
1532
+ <span class="cstat-no" title="statement not covered" > storage.removeItem(TokenStorage.TOKEN_KEY);</span>
1533
+ <span class="cstat-no" title="statement not covered" > logger.debug("Crudify: Tokens cleared from storage");</span>
1534
+ <span class="cstat-no" title="statement not covered" > } catch (error: unknown) {</span>
1535
+ <span class="cstat-no" title="statement not covered" > logger.error("Crudify: Failed to clear tokens", error instanceof Error ? error : { message: String(error) });</span>
1536
+ <span class="cstat-no" title="statement not covered" > }</span>
1537
+ <span class="cstat-no" title="statement not covered" > }</span>
1538
+ &nbsp;
1539
+ /**
1540
+ * Rotar clave de encriptación (limpia tokens existentes por seguridad)
1541
+ */
1542
+ <span class="fstat-no" title="function not covered" > static async rotateEncryptionKey(): Promise&lt;void&gt; {</span>
1543
+ <span class="cstat-no" title="statement not covered" > try {</span>
1544
+ <span class="cstat-no" title="statement not covered" > // Limpiar tokens existentes ya que no podrán desencriptarse con la nueva clave</span>
1545
+ <span class="cstat-no" title="statement not covered" > await TokenStorage.clearTokens();</span>
1546
+ <span class="cstat-no" title="statement not covered" ></span>
1547
+ <span class="cstat-no" title="statement not covered" > // Limpiar clave de memoria</span>
1548
+ <span class="cstat-no" title="statement not covered" > TokenStorage.encryptionKey = null;</span>
1549
+ <span class="cstat-no" title="statement not covered" > TokenStorage.salt = null;</span>
1550
+ <span class="cstat-no" title="statement not covered" > TokenStorage.fingerprint = null;</span>
1551
+ <span class="cstat-no" title="statement not covered" > TokenStorage.initPromise = null;</span>
1552
+ <span class="cstat-no" title="statement not covered" ></span>
1553
+ <span class="cstat-no" title="statement not covered" > // Limpiar claves persistidas</span>
1554
+ <span class="cstat-no" title="statement not covered" > try {</span>
1555
+ <span class="cstat-no" title="statement not covered" > localStorage.removeItem(TokenStorage.ENCRYPTION_KEY_STORAGE);</span>
1556
+ <span class="cstat-no" title="statement not covered" > localStorage.removeItem(TokenStorage.SALT_KEY);</span>
1557
+ <span class="cstat-no" title="statement not covered" > } catch {</span>
1558
+ <span class="cstat-no" title="statement not covered" > // Ignore storage errors</span>
1559
+ <span class="cstat-no" title="statement not covered" > }</span>
1560
+ <span class="cstat-no" title="statement not covered" ></span>
1561
+ <span class="cstat-no" title="statement not covered" > // La nueva clave se generará automáticamente en el próximo acceso</span>
1562
+ <span class="cstat-no" title="statement not covered" > logger.info("Crudify: Encryption key rotated successfully");</span>
1563
+ <span class="cstat-no" title="statement not covered" > } catch (error: unknown) {</span>
1564
+ <span class="cstat-no" title="statement not covered" > logger.error("Crudify: Failed to rotate encryption key", error instanceof Error ? error : { message: String(error) });</span>
1565
+ <span class="cstat-no" title="statement not covered" > }</span>
1566
+ <span class="cstat-no" title="statement not covered" > }</span>
1567
+ &nbsp;
1568
+ /**
1569
+ * Verificar si hay tokens válidos guardados (async)
1570
+ */
1571
+ <span class="fstat-no" title="function not covered" > static async hasValidTokens(): Promise&lt;boolean&gt; {</span>
1572
+ <span class="cstat-no" title="statement not covered" > const tokens = await TokenStorage.getTokens();</span>
1573
+ <span class="cstat-no" title="statement not covered" > return tokens !== null;</span>
1574
+ <span class="cstat-no" title="statement not covered" > }</span>
1575
+ &nbsp;
1576
+ /**
1577
+ * Asegura que TokenStorage esté inicializado
1578
+ * Útil para cross-tab sync donde necesitamos esperar
1579
+ * a que la clave de encriptación esté lista antes de leer tokens
1580
+ */
1581
+ <span class="fstat-no" title="function not covered" > static async ensureInitialized(): Promise&lt;void&gt; {</span>
1582
+ <span class="cstat-no" title="statement not covered" > await TokenStorage.initialize();</span>
1583
+ <span class="cstat-no" title="statement not covered" > }</span>
1584
+ &nbsp;
1585
+ /**
1586
+ * Obtener información de expiración (async)
1587
+ */
1588
+ <span class="fstat-no" title="function not covered" > static async getExpirationInfo(): Promise&lt;{</span>
1589
+ <span class="cstat-no" title="statement not covered" > accessExpired: boolean;</span>
1590
+ <span class="cstat-no" title="statement not covered" > refreshExpired: boolean;</span>
1591
+ <span class="cstat-no" title="statement not covered" > accessExpiresIn: number;</span>
1592
+ <span class="cstat-no" title="statement not covered" > refreshExpiresIn: number;</span>
1593
+ <span class="cstat-no" title="statement not covered" > } | null&gt; {</span>
1594
+ <span class="cstat-no" title="statement not covered" > const tokens = await TokenStorage.getTokens();</span>
1595
+ <span class="cstat-no" title="statement not covered" > if (!tokens) return null;</span>
1596
+ <span class="cstat-no" title="statement not covered" ></span>
1597
+ <span class="cstat-no" title="statement not covered" > const now = Date.now();</span>
1598
+ <span class="cstat-no" title="statement not covered" > return {</span>
1599
+ <span class="cstat-no" title="statement not covered" > accessExpired: now &gt;= tokens.expiresAt,</span>
1600
+ <span class="cstat-no" title="statement not covered" > refreshExpired: now &gt;= tokens.refreshExpiresAt,</span>
1601
+ <span class="cstat-no" title="statement not covered" > accessExpiresIn: Math.max(0, tokens.expiresAt - now),</span>
1602
+ <span class="cstat-no" title="statement not covered" > refreshExpiresIn: Math.max(0, tokens.refreshExpiresAt - now),</span>
1603
+ <span class="cstat-no" title="statement not covered" > };</span>
1604
+ <span class="cstat-no" title="statement not covered" > }</span>
1605
+ &nbsp;
1606
+ /**
1607
+ * Actualizar solo el access token (después de refresh) - async
1608
+ */
1609
+ <span class="fstat-no" title="function not covered" > static async updateAccessToken(newAccessToken: string, newExpiresAt: number): Promise&lt;void&gt; {</span>
1610
+ <span class="cstat-no" title="statement not covered" > const existingTokens = await TokenStorage.getTokens();</span>
1611
+ <span class="cstat-no" title="statement not covered" > if (!existingTokens) {</span>
1612
+ <span class="cstat-no" title="statement not covered" > logger.warn("Crudify: Cannot update access token, no existing tokens found");</span>
1613
+ <span class="cstat-no" title="statement not covered" > return;</span>
1614
+ <span class="cstat-no" title="statement not covered" > }</span>
1615
+ <span class="cstat-no" title="statement not covered" ></span>
1616
+ <span class="cstat-no" title="statement not covered" > await TokenStorage.saveTokens({</span>
1617
+ <span class="cstat-no" title="statement not covered" > ...existingTokens,</span>
1618
+ <span class="cstat-no" title="statement not covered" > accessToken: newAccessToken,</span>
1619
+ <span class="cstat-no" title="statement not covered" > expiresAt: newExpiresAt,</span>
1620
+ <span class="cstat-no" title="statement not covered" > });</span>
1621
+ <span class="cstat-no" title="statement not covered" > }</span>
1622
+ &nbsp;
1623
+ /**
1624
+ * Event types for cross-tab synchronization
1625
+ */
1626
+ static readonly SYNC_EVENTS = {
1627
+ TOKENS_CLEARED: "TOKENS_CLEARED", // Logout in another tab
1628
+ TOKENS_UPDATED: "TOKENS_UPDATED", // Login or refresh in another tab
1629
+ } as const;
1630
+ &nbsp;
1631
+ /**
1632
+ * Cross-tab sync event details
1633
+ */
1634
+ static <span class="fstat-no" title="function not covered" >createSyncEvent(</span>
1635
+ <span class="cstat-no" title="statement not covered" > type: (typeof TokenStorage.SYNC_EVENTS)[keyof typeof TokenStorage.SYNC_EVENTS],</span>
1636
+ <span class="cstat-no" title="statement not covered" > tokens: TokenData | null,</span>
1637
+ <span class="cstat-no" title="statement not covered" > hadPreviousTokens: boolean</span>
1638
+ <span class="cstat-no" title="statement not covered" > ) {</span>
1639
+ <span class="cstat-no" title="statement not covered" > return { type, tokens, hadPreviousTokens };</span>
1640
+ <span class="cstat-no" title="statement not covered" > }</span>
1641
+ &nbsp;
1642
+ /**
1643
+ * Suscribirse a cambios de tokens entre tabs
1644
+ * Permite sincronizar logout/login entre múltiples tabs/ventanas
1645
+ *
1646
+ * Note: This remains sync as it's event-based, but the callback receives
1647
+ * tokens that are already decrypted asynchronously
1648
+ */
1649
+ /**
1650
+ * Flag interno para ignorar eventos de storage generados por esta pestaña
1651
+ * Se usa para prevenir que cambios locales disparen callbacks de cross-tab sync
1652
+ */
1653
+ private static ignoreNextStorageEvent = false;
1654
+ private static ignoreStorageEventTimeout: ReturnType&lt;typeof setTimeout&gt; | null = null;
1655
+ &nbsp;
1656
+ /**
1657
+ * Marca que el próximo evento de storage debe ser ignorado
1658
+ * Útil cuando hacemos cambios locales que no queremos sincronizar como cross-tab
1659
+ */
1660
+ static <span class="fstat-no" title="function not covered" >markLocalChange(): void {</span>
1661
+ <span class="cstat-no" title="statement not covered" > TokenStorage.ignoreNextStorageEvent = true;</span>
1662
+ <span class="cstat-no" title="statement not covered" > // Reset después de 100ms para evitar que quede en true indefinidamente</span>
1663
+ <span class="cstat-no" title="statement not covered" > if (TokenStorage.ignoreStorageEventTimeout) {</span>
1664
+ <span class="cstat-no" title="statement not covered" > clearTimeout(TokenStorage.ignoreStorageEventTimeout);</span>
1665
+ <span class="cstat-no" title="statement not covered" > }</span>
1666
+ <span class="cstat-no" title="statement not covered" > TokenStorage.ignoreStorageEventTimeout = setTimeout(() =&gt; {</span>
1667
+ <span class="cstat-no" title="statement not covered" > TokenStorage.ignoreNextStorageEvent = false;</span>
1668
+ <span class="cstat-no" title="statement not covered" > }, 100);</span>
1669
+ <span class="cstat-no" title="statement not covered" > }</span>
1670
+ &nbsp;
1671
+ /**
1672
+ * Marca que se acaba de hacer un login exitoso
1673
+ * Inicia el grace period durante el cual se ignoran eventos TOKENS_CLEARED
1674
+ */
1675
+ static <span class="fstat-no" title="function not covered" >markSuccessfulLogin(): void {</span>
1676
+ <span class="cstat-no" title="statement not covered" > TokenStorage.lastSuccessfulLoginTime = Date.now();</span>
1677
+ <span class="cstat-no" title="statement not covered" > }</span>
1678
+ &nbsp;
1679
+ /**
1680
+ * Verifica si estamos dentro del grace period del login
1681
+ * @returns true si el login exitoso fue hace menos de LOGIN_GRACE_PERIOD_MS
1682
+ */
1683
+ static <span class="fstat-no" title="function not covered" >isWithinLoginGracePeriod(): boolean {</span>
1684
+ <span class="cstat-no" title="statement not covered" > if (TokenStorage.lastSuccessfulLoginTime === 0) return false;</span>
1685
+ <span class="cstat-no" title="statement not covered" > const elapsed = Date.now() - TokenStorage.lastSuccessfulLoginTime;</span>
1686
+ <span class="cstat-no" title="statement not covered" > return elapsed &lt; TokenStorage.LOGIN_GRACE_PERIOD_MS;</span>
1687
+ <span class="cstat-no" title="statement not covered" > }</span>
1688
+ &nbsp;
1689
+ static <span class="fstat-no" title="function not covered" >subscribeToChanges(</span>
1690
+ <span class="cstat-no" title="statement not covered" > callback: (</span>
1691
+ <span class="cstat-no" title="statement not covered" > tokens: TokenData | null,</span>
1692
+ <span class="cstat-no" title="statement not covered" > eventType: (typeof TokenStorage.SYNC_EVENTS)[keyof typeof TokenStorage.SYNC_EVENTS],</span>
1693
+ <span class="cstat-no" title="statement not covered" > hadPreviousTokens: boolean</span>
1694
+ <span class="cstat-no" title="statement not covered" > ) =&gt; void</span>
1695
+ <span class="cstat-no" title="statement not covered" > ): () =&gt; void {</span>
1696
+ <span class="cstat-no" title="statement not covered" > const handleStorageChange = async (event: StorageEvent) =&gt; {</span>
1697
+ <span class="cstat-no" title="statement not covered" > // Solo procesar cambios en los tokens de crudify</span>
1698
+ <span class="cstat-no" title="statement not covered" > if (event.key !== TokenStorage.TOKEN_KEY) return;</span>
1699
+ <span class="cstat-no" title="statement not covered" ></span>
1700
+ <span class="cstat-no" title="statement not covered" > // PROTECCIÓN 1: Ignorar eventos marcados como locales</span>
1701
+ <span class="cstat-no" title="statement not covered" > if (TokenStorage.ignoreNextStorageEvent) {</span>
1702
+ <span class="cstat-no" title="statement not covered" > TokenStorage.ignoreNextStorageEvent = false;</span>
1703
+ <span class="cstat-no" title="statement not covered" > return;</span>
1704
+ <span class="cstat-no" title="statement not covered" > }</span>
1705
+ <span class="cstat-no" title="statement not covered" ></span>
1706
+ <span class="cstat-no" title="statement not covered" > // PROTECCIÓN 2: Ignorar TOKENS_CLEARED durante el grace period del login</span>
1707
+ <span class="cstat-no" title="statement not covered" > // Esto previene que eventos de otras pestañas interfieran con un login reciente</span>
1708
+ <span class="cstat-no" title="statement not covered" > if (event.newValue === null &amp;&amp; TokenStorage.isWithinLoginGracePeriod()) {</span>
1709
+ <span class="cstat-no" title="statement not covered" > return;</span>
1710
+ <span class="cstat-no" title="statement not covered" > }</span>
1711
+ <span class="cstat-no" title="statement not covered" ></span>
1712
+ <span class="cstat-no" title="statement not covered" > // PROTECCIÓN 3: Verificar si el storage actual coincide con el evento</span>
1713
+ <span class="cstat-no" title="statement not covered" > // Si newValue es null pero localStorage tiene tokens, es un evento espurio</span>
1714
+ <span class="cstat-no" title="statement not covered" > if (event.newValue === null) {</span>
1715
+ <span class="cstat-no" title="statement not covered" > const currentStorage = TokenStorage.getStorage();</span>
1716
+ <span class="cstat-no" title="statement not covered" > const currentValue = currentStorage?.getItem(TokenStorage.TOKEN_KEY);</span>
1717
+ <span class="cstat-no" title="statement not covered" > if (currentValue !== null &amp;&amp; currentValue !== "") {</span>
1718
+ <span class="cstat-no" title="statement not covered" > return;</span>
1719
+ <span class="cstat-no" title="statement not covered" > }</span>
1720
+ <span class="cstat-no" title="statement not covered" > }</span>
1721
+ <span class="cstat-no" title="statement not covered" ></span>
1722
+ <span class="cstat-no" title="statement not covered" > // Determinar si había tokens antes del cambio</span>
1723
+ <span class="cstat-no" title="statement not covered" > const hadPreviousTokens = event.oldValue !== null &amp;&amp; event.oldValue !== "";</span>
1724
+ <span class="cstat-no" title="statement not covered" ></span>
1725
+ <span class="cstat-no" title="statement not covered" > // Si se eliminaron los tokens (logout en otra tab)</span>
1726
+ <span class="cstat-no" title="statement not covered" > if (event.newValue === null) {</span>
1727
+ <span class="cstat-no" title="statement not covered" > logger.debug("Crudify: Tokens removed in another tab");</span>
1728
+ <span class="cstat-no" title="statement not covered" > callback(null, TokenStorage.SYNC_EVENTS.TOKENS_CLEARED, hadPreviousTokens);</span>
1729
+ <span class="cstat-no" title="statement not covered" > return;</span>
1730
+ <span class="cstat-no" title="statement not covered" > }</span>
1731
+ <span class="cstat-no" title="statement not covered" ></span>
1732
+ <span class="cstat-no" title="statement not covered" > // Si se actualizaron los tokens (login o refresh en otra tab)</span>
1733
+ <span class="cstat-no" title="statement not covered" > if (event.newValue) {</span>
1734
+ <span class="cstat-no" title="statement not covered" > logger.debug("Crudify: Tokens updated in another tab");</span>
1735
+ <span class="cstat-no" title="statement not covered" > const tokens = await TokenStorage.getTokens();</span>
1736
+ <span class="cstat-no" title="statement not covered" > callback(tokens, TokenStorage.SYNC_EVENTS.TOKENS_UPDATED, hadPreviousTokens);</span>
1737
+ <span class="cstat-no" title="statement not covered" > }</span>
1738
+ <span class="cstat-no" title="statement not covered" > };</span>
1739
+ <span class="cstat-no" title="statement not covered" ></span>
1740
+ <span class="cstat-no" title="statement not covered" > // Escuchar eventos de storage</span>
1741
+ <span class="cstat-no" title="statement not covered" > window.addEventListener("storage", handleStorageChange);</span>
1742
+ <span class="cstat-no" title="statement not covered" ></span>
1743
+ <span class="cstat-no" title="statement not covered" > // Retornar función de cleanup</span>
1744
+ <span class="cstat-no" title="statement not covered" > return () =&gt; {</span>
1745
+ <span class="cstat-no" title="statement not covered" > window.removeEventListener("storage", handleStorageChange);</span>
1746
+ <span class="cstat-no" title="statement not covered" > };</span>
1747
+ <span class="cstat-no" title="statement not covered" > }</span>
1748
+ }
1749
+ &nbsp;</pre></td></tr></table></pre>
1750
+
1751
+ <div class='push'></div><!-- for sticky footer -->
1752
+ </div><!-- /wrapper -->
1753
+ <div class='footer quiet pad2 space-top1 center small'>
1754
+ Code coverage generated by
1755
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1756
+ at 2026-01-28T06:22:15.683Z
1757
+ </div>
1758
+ <script src="../prettify.js"></script>
1759
+ <script>
1760
+ window.onload = function () {
1761
+ prettyPrint();
1762
+ };
1763
+ </script>
1764
+ <script src="../sorter.js"></script>
1765
+ <script src="../block-navigation.js"></script>
1766
+ </body>
1767
+ </html>
1768
+