@@ -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,101 @@ 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
185
+ }
186
+
187
+ let bandwidthLogs = this . logs
188
+ if ( this . hasPerformanceAPI ) {
189
+ bandwidthLogs = this . _matchLogsWithPerformanceMetrics ( bandwidthLogs )
191
190
}
191
+
192
+ await fetch (
193
+ this . opts . logURL ,
194
+ {
195
+ method : 'POST' ,
196
+ body : JSON . stringify ( { bandwidthLogs, logSender : this . opts . logSender } )
197
+ }
198
+ )
199
+
200
+ this . logs = [ ]
201
+ this . _clearPerformanceBuffer ( )
202
+ }
203
+
204
+ /**
205
+ *
206
+ * @param {Array<object> } logs
207
+ */
208
+ _matchLogsWithPerformanceMetrics ( logs ) {
209
+ const logsToReport = [ ]
210
+
211
+ for ( const log of logs ) {
212
+ const metrics = this . _getPerformanceMetricsForLog ( log )
213
+
214
+ if ( ! metrics . isFromBrowserCache ) {
215
+ delete metrics . isFromBrowserCache
216
+ Object . assign ( log , metrics )
217
+ logsToReport . push ( log )
218
+ }
219
+ }
220
+
221
+ return logsToReport
192
222
}
193
223
194
224
/**
195
225
*
196
226
* @param {object } log
227
+ * @returns {object }
197
228
*/
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.
229
+ _getPerformanceMetricsForLog ( log ) {
230
+ const metrics = { }
231
+
232
+ const entry = performance
233
+ . getEntriesByType ( 'resource' )
234
+ . find ( ( r ) => r . name === log . url . href )
235
+
236
+ if ( entry ) {
237
+ const dnsStart = entry . domainLookupStart
238
+ const dnsEnd = entry . domainLookupEnd
239
+ const hasDnsMetrics = dnsEnd > 0 && dnsStart > 0
240
+
241
+ if ( hasDnsMetrics ) {
242
+ metrics . dnsTimeMs = Math . round ( dnsEnd - dnsStart )
243
+ metrics . ttfbAfterDnsMs = Math . round (
244
+ entry . responseStart - entry . requestStart
245
+ )
246
+ }
247
+
248
+ if ( entry . nextHopProtocol ) {
249
+ metrics . httpProtocol = entry . nextHopProtocol
221
250
}
251
+
252
+ metrics . isFromBrowserCache = (
253
+ entry . deliveryType === 'cache' ||
254
+ ( log . httpStatusCode && entry . transferSize === 0 )
255
+ )
256
+ }
257
+
258
+ return metrics
259
+ }
260
+
261
+ _monitorPerformanceBuffer ( ) {
262
+ // Using static method prevents multiple unnecessary listeners.
263
+ performance . addEventListener ( 'resourcetimingbufferfull' , Saturn . _setResourceBufferSize )
264
+ }
265
+
266
+ static _setResourceBufferSize ( ) {
267
+ const increment = 250
268
+ const maxSize = 1000
269
+ const size = performance . getEntriesByType ( 'resource' ) . length
270
+ const newSize = Math . min ( size + increment , maxSize )
271
+
272
+ performance . setResourceTimingBufferSize ( newSize )
273
+ }
274
+
275
+ _clearPerformanceBuffer ( ) {
276
+ if ( this . hasPerformanceAPI ) {
277
+ performance . clearResourceTimings ( )
222
278
}
223
279
}
224
280
}
0 commit comments