Skip to content

Commit d2a0e24

Browse files
authored
Merge pull request #10 from filecoin-saturn/no-browser-cache-reports
feat: dont report responses served from browser cache
2 parents 5d61f9d + 45a9522 commit d2a0e24

File tree

1 file changed

+91
-40
lines changed

1 file changed

+91
-40
lines changed

src/index.js

Lines changed: 91 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@ class Saturn {
1717
this.opts = Object.assign({}, {
1818
clientId: randomUUID(),
1919
cdnURL: 'saturn.ms',
20+
logURL: 'https://twb3qukm2i654i3tnvx36char40aymqq.lambda-url.us-west-2.on.aws/',
2021
connectTimeout: 5_000,
2122
downloadTimeout: 0
2223
}, opts)
2324

24-
this.reportingLogs = process?.env?.NODE_ENV !== 'development'
2525
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+
}
2631
}
2732

2833
/**
@@ -43,9 +48,7 @@ class Saturn {
4348

4449
const log = {
4550
url,
46-
range: null,
47-
startTime: new Date(),
48-
numBytesSent: 0
51+
startTime: new Date()
4952
}
5053

5154
const controller = new AbortController()
@@ -80,8 +83,6 @@ class Saturn {
8083
this._finalizeLog(log)
8184

8285
throw err
83-
} finally {
84-
this._addPerformanceAPIMetrics(log)
8586
}
8687

8788
return { res, log }
@@ -100,6 +101,8 @@ class Saturn {
100101
const { res, log } = await this.fetchCID(cidPath, opts)
101102

102103
async function * metricsIterable (itr) {
104+
log.numBytesSent = 0
105+
103106
for await (const chunk of itr) {
104107
log.numBytesSent += chunk.length
105108
yield chunk
@@ -177,48 +180,96 @@ class Saturn {
177180
}
178181

179182
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
191185
}
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+
})
192215
}
193216

194217
/**
195218
*
196219
* @param {object} log
220+
* @returns {object}
197221
*/
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
221245
}
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()
222273
}
223274
}
224275
}

0 commit comments

Comments
 (0)