@@ -17,12 +17,17 @@ class Saturn {
17
17
this . opts = Object . assign ( { } , {
18
18
clientId : randomUUID ( ) ,
19
19
cdnURL : 'saturn.ms' ,
20
+ logURL : 'https://twb3qukm2i654i3tnvx36char40aymqq.lambda-url.us-west-2.on.aws/' ,
20
21
connectTimeout : 5_000 ,
21
22
downloadTimeout : 0
22
23
} , opts )
23
24
24
- this . reportingLogs = process ?. env ?. NODE_ENV !== 'development'
25
25
this . logs = [ ]
26
+ this . reportingLogs = process ?. env ?. NODE_ENV !== 'development'
27
+ this . hasPerformanceAPI = typeof window !== 'undefined' && window ?. performance
28
+ if ( this . reportingLogs && this . hasPerformanceAPI ) {
29
+ this . _monitorPerformanceBuffer ( )
30
+ }
26
31
}
27
32
28
33
/**
@@ -43,9 +48,7 @@ class Saturn {
43
48
44
49
const log = {
45
50
url,
46
- range : null ,
47
- startTime : new Date ( ) ,
48
- numBytesSent : 0
51
+ startTime : new Date ( )
49
52
}
50
53
51
54
const controller = new AbortController ( )
@@ -80,8 +83,6 @@ class Saturn {
80
83
this . _finalizeLog ( log )
81
84
82
85
throw err
83
- } finally {
84
- this . _addPerformanceAPIMetrics ( log )
85
86
}
86
87
87
88
return { res, log }
@@ -100,6 +101,8 @@ class Saturn {
100
101
const { res, log } = await this . fetchCID ( cidPath , opts )
101
102
102
103
async function * metricsIterable ( itr ) {
104
+ log . numBytesSent = 0
105
+
103
106
for await ( const chunk of itr ) {
104
107
log . numBytesSent += chunk . length
105
108
yield chunk
@@ -177,48 +180,96 @@ class Saturn {
177
180
}
178
181
179
182
async _reportLogs ( ) {
180
- if ( this . logs . length ) {
181
- await fetch (
182
- 'https://twb3qukm2i654i3tnvx36char40aymqq.lambda-url.us-west-2.on.aws/' ,
183
- {
184
- method : 'POST' ,
185
- body : JSON . stringify ( {
186
- bandwidthLogs : this . logs
187
- } )
188
- }
189
- )
190
- this . logs = [ ]
183
+ if ( ! this . logs . length ) {
184
+ return
191
185
}
186
+
187
+ const bandwidthLogs = this . hasPerformanceAPI
188
+ ? this . _matchLogsWithPerformanceMetrics ( this . logs )
189
+ : this . logs
190
+
191
+ await fetch (
192
+ this . opts . logURL ,
193
+ {
194
+ method : 'POST' ,
195
+ body : JSON . stringify ( { bandwidthLogs, logSender : this . opts . logSender } )
196
+ }
197
+ )
198
+
199
+ this . logs = [ ]
200
+ this . _clearPerformanceBuffer ( )
201
+ }
202
+
203
+ /**
204
+ *
205
+ * @param {Array<object> } logs
206
+ */
207
+ _matchLogsWithPerformanceMetrics ( logs ) {
208
+ return logs
209
+ . map ( log => ( { ...log , ...this . _getPerformanceMetricsForLog ( log ) } ) )
210
+ . filter ( log => ! log . isFromBrowserCache )
211
+ . map ( log => {
212
+ const { isFromBrowserCache : _ , ...cleanLog } = log
213
+ return cleanLog
214
+ } )
192
215
}
193
216
194
217
/**
195
218
*
196
219
* @param {object } log
220
+ * @returns {object }
197
221
*/
198
- _addPerformanceAPIMetrics ( log ) {
199
- if ( typeof window !== 'undefined' && window ?. performance ) {
200
- const entry = performance
201
- . getEntriesByType ( 'resource' )
202
- . find ( ( r ) => r . name === log . url . href )
203
- if ( entry ) {
204
- const dnsStart = entry . domainLookupStart
205
- const dnsEnd = entry . domainLookupEnd
206
- const hasData = dnsEnd > 0 && dnsStart > 0
207
- if ( hasData ) {
208
- log . dnsTimeMs = Math . round ( dnsEnd - dnsStart )
209
- log . ttfbAfterDnsMs = Math . round (
210
- entry . responseStart - entry . requestStart
211
- )
212
- }
213
-
214
- if ( log . httpProtocol === null && entry . nextHopProtocol ) {
215
- log . httpProtocol = entry . nextHopProtocol
216
- }
217
- // else response didn't have Timing-Allow-Origin: *
218
- //
219
- // if both dnsStart and dnsEnd are > 0 but have the same value,
220
- // its a dns cache hit.
222
+ _getPerformanceMetricsForLog ( log ) {
223
+ const metrics = { }
224
+
225
+ // URL is the best differentiator available, though there can be multiple entries per URL.
226
+ // It's a good enough heuristic.
227
+ const entry = performance
228
+ . getEntriesByType ( 'resource' )
229
+ . find ( ( r ) => r . name === log . url . href )
230
+
231
+ if ( entry ) {
232
+ const dnsStart = entry . domainLookupStart
233
+ const dnsEnd = entry . domainLookupEnd
234
+ const hasDnsMetrics = dnsEnd > 0 && dnsStart > 0
235
+
236
+ if ( hasDnsMetrics ) {
237
+ metrics . dnsTimeMs = Math . round ( dnsEnd - dnsStart )
238
+ metrics . ttfbAfterDnsMs = Math . round (
239
+ entry . responseStart - entry . requestStart
240
+ )
241
+ }
242
+
243
+ if ( entry . nextHopProtocol ) {
244
+ metrics . httpProtocol = entry . nextHopProtocol
221
245
}
246
+
247
+ metrics . isFromBrowserCache = (
248
+ entry . deliveryType === 'cache' ||
249
+ ( log . httpStatusCode && entry . transferSize === 0 )
250
+ )
251
+ }
252
+
253
+ return metrics
254
+ }
255
+
256
+ _monitorPerformanceBuffer ( ) {
257
+ // Using static method prevents multiple unnecessary listeners.
258
+ performance . addEventListener ( 'resourcetimingbufferfull' , Saturn . _setResourceBufferSize )
259
+ }
260
+
261
+ static _setResourceBufferSize ( ) {
262
+ const increment = 250
263
+ const maxSize = 1000
264
+ const size = performance . getEntriesByType ( 'resource' ) . length
265
+ const newSize = Math . min ( size + increment , maxSize )
266
+
267
+ performance . setResourceTimingBufferSize ( newSize )
268
+ }
269
+
270
+ _clearPerformanceBuffer ( ) {
271
+ if ( this . hasPerformanceAPI ) {
272
+ performance . clearResourceTimings ( )
222
273
}
223
274
}
224
275
}
0 commit comments