@@ -24,6 +24,32 @@ const proxyLatenciesReducer = (
24
24
state : Record < string , ProxyLatencyReport > ,
25
25
action : ProxyLatencyAction ,
26
26
) : Record < string , ProxyLatencyReport > => {
27
+ // TODO: We should probably not read from local storage on every action.
28
+ const history = loadStoredLatencies ( )
29
+ const proxyHistory = history [ action . proxyID ] || [ ]
30
+ const minReport = proxyHistory . reduce ( ( min , report ) => {
31
+ if ( min . latencyMS === 0 ) {
32
+ // Not yet set, so use the new report.
33
+ return report
34
+ }
35
+ if ( min . latencyMS < report . latencyMS ) {
36
+ return min
37
+ }
38
+ return report
39
+ } , { } as ProxyLatencyReport )
40
+
41
+ if (
42
+ minReport . latencyMS > 0 &&
43
+ minReport . latencyMS < action . report . latencyMS
44
+ ) {
45
+ // The new report is slower then the min report, so use the min report.
46
+ return {
47
+ ...state ,
48
+ [ action . proxyID ] : minReport ,
49
+ }
50
+ }
51
+
52
+ // Use the new report
27
53
return {
28
54
...state ,
29
55
[ action . proxyID ] : action . report ,
@@ -38,6 +64,18 @@ export const useProxyLatency = (
38
64
refetch : ( ) => void
39
65
proxyLatencies : Record < string , ProxyLatencyReport >
40
66
} => {
67
+ // maxStoredLatencies is the maximum number of latencies to store per proxy in local storage.
68
+ let maxStoredLatencies = 8
69
+ // The reason we pull this from local storage is so for development purposes, a user can manually
70
+ // set a larger number to collect data in their normal usage. This data can later be analyzed to come up
71
+ // with some better magic numbers.
72
+ const maxStoredLatenciesVar = localStorage . getItem (
73
+ "workspace-proxy-latencies-max" ,
74
+ )
75
+ if ( maxStoredLatenciesVar ) {
76
+ maxStoredLatencies = Number ( maxStoredLatenciesVar )
77
+ }
78
+
41
79
const [ proxyLatencies , dispatchProxyLatencies ] = useReducer (
42
80
proxyLatenciesReducer ,
43
81
{ } ,
@@ -113,14 +151,17 @@ export const useProxyLatency = (
113
151
)
114
152
latencyMS = entry . duration
115
153
}
116
- dispatchProxyLatencies ( {
154
+ const update = {
117
155
proxyID : check . id ,
118
156
report : {
119
157
latencyMS,
120
158
accurate,
121
159
at : new Date ( ) ,
122
160
} ,
123
- } )
161
+ }
162
+ dispatchProxyLatencies ( update )
163
+ // Also save to local storage to persist the latency across page refreshes.
164
+ updateStoredLatencies ( update )
124
165
125
166
return
126
167
}
@@ -140,6 +181,10 @@ export const useProxyLatency = (
140
181
const proxyRequests = Object . keys ( proxyChecks ) . map ( ( latencyURL ) => {
141
182
return axios . get ( latencyURL , {
142
183
withCredentials : false ,
184
+ // Must add a custom header to make the request not a "simple request".
185
+ // We want to force a preflight request.
186
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
187
+ headers : { "X-LATENCY-CHECK" : "true" } ,
143
188
} )
144
189
} )
145
190
@@ -156,11 +201,74 @@ export const useProxyLatency = (
156
201
// At this point, we can be confident that all the proxy requests have been recorded
157
202
// via the performance observer. So we can disconnect the observer.
158
203
observer . disconnect ( )
204
+
205
+ // Local storage cleanup
206
+ garbageCollectStoredLatencies ( proxies , maxStoredLatencies )
159
207
} )
160
- } , [ proxies , latestFetchRequest ] )
208
+ } , [ proxies , latestFetchRequest , maxStoredLatencies ] )
161
209
162
210
return {
163
211
proxyLatencies,
164
212
refetch,
165
213
}
166
214
}
215
+
216
+ // Local storage functions
217
+
218
+ // loadStoredLatencies will load the stored latencies from local storage.
219
+ // Latencies are stored in local storage to minimize the impact of outliers.
220
+ // If a single request is slow, we want to omit that latency check, and go with
221
+ // a more accurate latency check.
222
+ const loadStoredLatencies = ( ) : Record < string , ProxyLatencyReport [ ] > => {
223
+ const str = localStorage . getItem ( "workspace-proxy-latencies" )
224
+ if ( ! str ) {
225
+ return { }
226
+ }
227
+
228
+ return JSON . parse ( str )
229
+ }
230
+
231
+ const updateStoredLatencies = ( action : ProxyLatencyAction ) : void => {
232
+ const latencies = loadStoredLatencies ( )
233
+ const reports = latencies [ action . proxyID ] || [ ]
234
+
235
+ reports . push ( action . report )
236
+ latencies [ action . proxyID ] = reports
237
+ localStorage . setItem ( "workspace-proxy-latencies" , JSON . stringify ( latencies ) )
238
+ }
239
+
240
+ // garbageCollectStoredLatencies will remove any latencies that are older then 1 week or latencies of proxies
241
+ // that no longer exist. This is intended to keep the size of local storage down.
242
+ const garbageCollectStoredLatencies = (
243
+ regions : RegionsResponse ,
244
+ maxStored : number ,
245
+ ) : void => {
246
+ const latencies = loadStoredLatencies ( )
247
+ const now = Date . now ( )
248
+ const cleaned = cleanupLatencies ( latencies , regions , new Date ( now ) , maxStored )
249
+
250
+ localStorage . setItem ( "workspace-proxy-latencies" , JSON . stringify ( cleaned ) )
251
+ }
252
+
253
+ const cleanupLatencies = (
254
+ stored : Record < string , ProxyLatencyReport [ ] > ,
255
+ regions : RegionsResponse ,
256
+ now : Date ,
257
+ maxStored : number ,
258
+ ) : Record < string , ProxyLatencyReport [ ] > => {
259
+ Object . keys ( stored ) . forEach ( ( proxyID ) => {
260
+ if ( ! regions . regions . find ( ( region ) => region . id === proxyID ) ) {
261
+ delete stored [ proxyID ]
262
+ return
263
+ }
264
+ const reports = stored [ proxyID ]
265
+ const nowMS = now . getTime ( )
266
+ stored [ proxyID ] = reports . filter ( ( report ) => {
267
+ // Only keep the reports that are less then 1 week old.
268
+ return new Date ( report . at ) . getTime ( ) > nowMS - 1000 * 60 * 60 * 24 * 7
269
+ } )
270
+ // Only keep the 5 latest
271
+ stored [ proxyID ] = stored [ proxyID ] . slice ( - 1 * maxStored )
272
+ } )
273
+ return stored
274
+ }
0 commit comments