@firebase/performance
Advanced tools
Comparing version 0.6.12-canary.3c1559b7e to 0.6.12-canary.59ae45e48
import { ErrorFactory, areCookiesEnabled, isIndexedDBAvailable, validateIndexedDBOpenable, getModularInstance, deepEqual } from '@firebase/util'; | ||
import { Logger, LogLevel } from '@firebase/logger'; | ||
import { onLCP, onINP, onCLS } from 'web-vitals/attribution'; | ||
import { _getProvider, getApp, _registerComponent, registerVersion } from '@firebase/app'; | ||
@@ -8,3 +9,3 @@ import { Component } from '@firebase/component'; | ||
const name = "@firebase/performance"; | ||
const version = "0.6.12-canary.3c1559b7e"; | ||
const version = "0.6.12-canary.59ae45e48"; | ||
@@ -39,2 +40,8 @@ /** | ||
const FIRST_INPUT_DELAY_COUNTER_NAME = '_fid'; | ||
const LARGEST_CONTENTFUL_PAINT_METRIC_NAME = '_lcp'; | ||
const LARGEST_CONTENTFUL_PAINT_ATTRIBUTE_NAME = 'lcp_element'; | ||
const INTERACTION_TO_NEXT_PAINT_METRIC_NAME = '_inp'; | ||
const INTERACTION_TO_NEXT_PAINT_ATTRIBUTE_NAME = 'inp_interactionTarget'; | ||
const CUMULATIVE_LAYOUT_SHIFT_METRIC_NAME = '_cls'; | ||
const CUMULATIVE_LAYOUT_SHIFT_ATTRIBUTE_NAME = 'cls_largestShiftTarget'; | ||
const CONFIG_LOCAL_STORAGE_KEY = '@firebase/performance/config'; | ||
@@ -144,2 +151,5 @@ const CONFIG_EXPIRY_LOCAL_STORAGE_KEY = '@firebase/performance/configexpire'; | ||
} | ||
this.onLCP = onLCP; | ||
this.onINP = onINP; | ||
this.onCLS = onCLS; | ||
} | ||
@@ -698,5 +708,4 @@ getUrl() { | ||
const INITIAL_SEND_TIME_DELAY_MS = 5.5 * 1000; | ||
// If end point does not work, the call will be tried for these many times. | ||
const MAX_EVENT_COUNT_PER_REQUEST = 1000; | ||
const DEFAULT_REMAINING_TRIES = 3; | ||
const MAX_EVENT_COUNT_PER_REQUEST = 1000; | ||
let remainingTries = DEFAULT_REMAINING_TRIES; | ||
@@ -718,7 +727,6 @@ /* eslint-enable camelcase */ | ||
} | ||
// If there are no events to process, wait for DEFAULT_SEND_INTERVAL_MS and try again. | ||
if (!queue.length) { | ||
return processQueue(DEFAULT_SEND_INTERVAL_MS); | ||
if (queue.length > 0) { | ||
dispatchQueueEvents(); | ||
} | ||
dispatchQueueEvents(); | ||
processQueue(DEFAULT_SEND_INTERVAL_MS); | ||
}, timeOffset); | ||
@@ -747,3 +755,7 @@ } | ||
/* eslint-enable camelcase */ | ||
sendEventsToFl(data, staged).catch(() => { | ||
postToFlEndpoint(data) | ||
.then(() => { | ||
remainingTries = DEFAULT_REMAINING_TRIES; | ||
}) | ||
.catch(() => { | ||
// If the request fails for some reason, add the events that were attempted | ||
@@ -757,37 +769,12 @@ // back to the primary queue to retry later. | ||
} | ||
function sendEventsToFl(data, staged) { | ||
return postToFlEndpoint(data) | ||
.then(res => { | ||
if (!res.ok) { | ||
consoleLogger.info('Call to Firebase backend failed.'); | ||
} | ||
return res.json(); | ||
}) | ||
.then(res => { | ||
// Find the next call wait time from the response. | ||
const transportWait = Number(res.nextRequestWaitMillis); | ||
let requestOffset = DEFAULT_SEND_INTERVAL_MS; | ||
if (!isNaN(transportWait)) { | ||
requestOffset = Math.max(transportWait, requestOffset); | ||
} | ||
// Delete request if response include RESPONSE_ACTION_UNKNOWN or DELETE_REQUEST action. | ||
// Otherwise, retry request using normal scheduling if response include RETRY_REQUEST_LATER. | ||
const logResponseDetails = res.logResponseDetails; | ||
if (Array.isArray(logResponseDetails) && | ||
logResponseDetails.length > 0 && | ||
logResponseDetails[0].responseAction === 'RETRY_REQUEST_LATER') { | ||
queue = [...staged, ...queue]; | ||
consoleLogger.info(`Retry transport request later.`); | ||
} | ||
remainingTries = DEFAULT_REMAINING_TRIES; | ||
// Schedule the next process. | ||
processQueue(requestOffset); | ||
}); | ||
} | ||
function postToFlEndpoint(data) { | ||
const flTransportFullUrl = SettingsService.getInstance().getFlTransportFullUrl(); | ||
return fetch(flTransportFullUrl, { | ||
method: 'POST', | ||
body: JSON.stringify(data) | ||
}); | ||
const body = JSON.stringify(data); | ||
return navigator.sendBeacon && navigator.sendBeacon(flTransportFullUrl, body) | ||
? Promise.resolve() | ||
: fetch(flTransportFullUrl, { | ||
method: 'POST', | ||
body, | ||
keepalive: true | ||
}).then(); | ||
} | ||
@@ -813,2 +800,11 @@ function addToQueue(evt) { | ||
} | ||
/** | ||
* Force flush the queued events. Useful at page unload time to ensure all | ||
* events are uploaded. | ||
*/ | ||
function flushQueuedEvents() { | ||
while (queue.length > 0) { | ||
dispatchQueueEvents(); | ||
} | ||
} | ||
@@ -832,8 +828,12 @@ /** | ||
let logger; | ||
// | ||
// This method is not called before initialization. | ||
function sendLog(resource, resourceType) { | ||
if (!logger) { | ||
logger = transportHandler(serializer); | ||
logger = { | ||
send: transportHandler(serializer), | ||
flush: flushQueuedEvents | ||
}; | ||
} | ||
logger(resource, resourceType); | ||
logger.send(resource, resourceType); | ||
} | ||
@@ -854,6 +854,2 @@ function logTrace(trace) { | ||
} | ||
// Only log the page load auto traces if page is visible. | ||
if (trace.isAuto && getVisibilityState() !== VisibilityState.VISIBLE) { | ||
return; | ||
} | ||
if (isPerfInitialized()) { | ||
@@ -868,2 +864,7 @@ sendTraceLog(trace); | ||
} | ||
function flushLogs() { | ||
if (logger) { | ||
logger.flush(); | ||
} | ||
} | ||
function sendTraceLog(trace) { | ||
@@ -878,3 +879,3 @@ if (!getIid()) { | ||
} | ||
setTimeout(() => sendLog(trace, 1 /* ResourceType.Trace */), 0); | ||
sendLog(trace, 1 /* ResourceType.Trace */); | ||
} | ||
@@ -902,3 +903,3 @@ function logNetworkRequest(networkRequest) { | ||
} | ||
setTimeout(() => sendLog(networkRequest, 0 /* ResourceType.NetworkRequest */), 0); | ||
sendLog(networkRequest, 0 /* ResourceType.NetworkRequest */); | ||
} | ||
@@ -979,2 +980,42 @@ function serializer(resource, resourceType) { | ||
*/ | ||
function createNetworkRequestEntry(performanceController, entry) { | ||
const performanceEntry = entry; | ||
if (!performanceEntry || performanceEntry.responseStart === undefined) { | ||
return; | ||
} | ||
const timeOrigin = Api.getInstance().getTimeOrigin(); | ||
const startTimeUs = Math.floor((performanceEntry.startTime + timeOrigin) * 1000); | ||
const timeToResponseInitiatedUs = performanceEntry.responseStart | ||
? Math.floor((performanceEntry.responseStart - performanceEntry.startTime) * 1000) | ||
: undefined; | ||
const timeToResponseCompletedUs = Math.floor((performanceEntry.responseEnd - performanceEntry.startTime) * 1000); | ||
// Remove the query params from logged network request url. | ||
const url = performanceEntry.name && performanceEntry.name.split('?')[0]; | ||
const networkRequest = { | ||
performanceController, | ||
url, | ||
responsePayloadBytes: performanceEntry.transferSize, | ||
startTimeUs, | ||
timeToResponseInitiatedUs, | ||
timeToResponseCompletedUs | ||
}; | ||
logNetworkRequest(networkRequest); | ||
} | ||
/** | ||
* @license | ||
* Copyright 2020 Google LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
const MAX_METRIC_NAME_LENGTH = 100; | ||
@@ -985,3 +1026,6 @@ const RESERVED_AUTO_PREFIX = '_'; | ||
FIRST_CONTENTFUL_PAINT_COUNTER_NAME, | ||
FIRST_INPUT_DELAY_COUNTER_NAME | ||
FIRST_INPUT_DELAY_COUNTER_NAME, | ||
LARGEST_CONTENTFUL_PAINT_METRIC_NAME, | ||
CUMULATIVE_LAYOUT_SHIFT_METRIC_NAME, | ||
INTERACTION_TO_NEXT_PAINT_METRIC_NAME | ||
]; | ||
@@ -1225,3 +1269,3 @@ /** | ||
*/ | ||
static createOobTrace(performanceController, navigationTimings, paintTimings, firstInputDelay) { | ||
static createOobTrace(performanceController, navigationTimings, paintTimings, webVitalMetrics, firstInputDelay) { | ||
const route = Api.getInstance().getUrl(); | ||
@@ -1256,4 +1300,18 @@ if (!route) { | ||
} | ||
this.addWebVitalMetric(trace, LARGEST_CONTENTFUL_PAINT_METRIC_NAME, LARGEST_CONTENTFUL_PAINT_ATTRIBUTE_NAME, webVitalMetrics.lcp); | ||
this.addWebVitalMetric(trace, CUMULATIVE_LAYOUT_SHIFT_METRIC_NAME, CUMULATIVE_LAYOUT_SHIFT_ATTRIBUTE_NAME, webVitalMetrics.cls); | ||
this.addWebVitalMetric(trace, INTERACTION_TO_NEXT_PAINT_METRIC_NAME, INTERACTION_TO_NEXT_PAINT_ATTRIBUTE_NAME, webVitalMetrics.inp); | ||
// Page load logs are sent at unload time and so should be logged and | ||
// flushed immediately. | ||
logTrace(trace); | ||
flushLogs(); | ||
} | ||
static addWebVitalMetric(trace, metricKey, attributeKey, metric) { | ||
if (metric) { | ||
trace.putMetric(metricKey, Math.floor(metric.value * 1000)); | ||
if (metric.elementAttribution) { | ||
trace.putAttribute(attributeKey, metric.elementAttribution); | ||
} | ||
} | ||
} | ||
static createUserTimingTrace(performanceController, measureName) { | ||
@@ -1281,43 +1339,5 @@ const trace = new Trace(performanceController, measureName, false, measureName); | ||
*/ | ||
function createNetworkRequestEntry(performanceController, entry) { | ||
const performanceEntry = entry; | ||
if (!performanceEntry || performanceEntry.responseStart === undefined) { | ||
return; | ||
} | ||
const timeOrigin = Api.getInstance().getTimeOrigin(); | ||
const startTimeUs = Math.floor((performanceEntry.startTime + timeOrigin) * 1000); | ||
const timeToResponseInitiatedUs = performanceEntry.responseStart | ||
? Math.floor((performanceEntry.responseStart - performanceEntry.startTime) * 1000) | ||
: undefined; | ||
const timeToResponseCompletedUs = Math.floor((performanceEntry.responseEnd - performanceEntry.startTime) * 1000); | ||
// Remove the query params from logged network request url. | ||
const url = performanceEntry.name && performanceEntry.name.split('?')[0]; | ||
const networkRequest = { | ||
performanceController, | ||
url, | ||
responsePayloadBytes: performanceEntry.transferSize, | ||
startTimeUs, | ||
timeToResponseInitiatedUs, | ||
timeToResponseCompletedUs | ||
}; | ||
logNetworkRequest(networkRequest); | ||
} | ||
/** | ||
* @license | ||
* Copyright 2020 Google LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
const FID_WAIT_TIME_MS = 5000; | ||
let webVitalMetrics = {}; | ||
let sentPageLoadTrace = false; | ||
let firstInputDelay; | ||
function setupOobResources(performanceController) { | ||
@@ -1328,4 +1348,5 @@ // Do not initialize unless iid is available. | ||
} | ||
// The load event might not have fired yet, and that means performance navigation timing | ||
// object has a duration of 0. The setup should run after all current tasks in js queue. | ||
// The load event might not have fired yet, and that means performance | ||
// navigation timing object has a duration of 0. The setup should run after | ||
// all current tasks in js queue. | ||
setTimeout(() => setupOobTraces(performanceController), 0); | ||
@@ -1345,23 +1366,40 @@ setTimeout(() => setupNetworkRequests(performanceController), 0); | ||
const api = Api.getInstance(); | ||
const navigationTimings = api.getEntriesByType('navigation'); | ||
const paintTimings = api.getEntriesByType('paint'); | ||
// If First Input Delay polyfill is added to the page, report the fid value. | ||
// https://github.com/GoogleChromeLabs/first-input-delay | ||
// Better support for Safari | ||
if ('onpagehide' in window) { | ||
api.document.addEventListener('pagehide', () => sendOobTrace(performanceController)); | ||
} | ||
else { | ||
api.document.addEventListener('unload', () => sendOobTrace(performanceController)); | ||
} | ||
api.document.addEventListener('visibilitychange', () => { | ||
if (api.document.visibilityState === 'hidden') { | ||
sendOobTrace(performanceController); | ||
} | ||
}); | ||
if (api.onFirstInputDelay) { | ||
// If the fid call back is not called for certain time, continue without it. | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
let timeoutId = setTimeout(() => { | ||
Trace.createOobTrace(performanceController, navigationTimings, paintTimings); | ||
timeoutId = undefined; | ||
}, FID_WAIT_TIME_MS); | ||
api.onFirstInputDelay((fid) => { | ||
if (timeoutId) { | ||
clearTimeout(timeoutId); | ||
Trace.createOobTrace(performanceController, navigationTimings, paintTimings, fid); | ||
} | ||
firstInputDelay = fid; | ||
}); | ||
} | ||
else { | ||
Trace.createOobTrace(performanceController, navigationTimings, paintTimings); | ||
} | ||
api.onLCP((metric) => { | ||
var _a; | ||
webVitalMetrics.lcp = { | ||
value: metric.value, | ||
elementAttribution: (_a = metric.attribution) === null || _a === void 0 ? void 0 : _a.element | ||
}; | ||
}); | ||
api.onCLS((metric) => { | ||
var _a; | ||
webVitalMetrics.cls = { | ||
value: metric.value, | ||
elementAttribution: (_a = metric.attribution) === null || _a === void 0 ? void 0 : _a.largestShiftTarget | ||
}; | ||
}); | ||
api.onINP((metric) => { | ||
var _a; | ||
webVitalMetrics.inp = { | ||
value: metric.value, | ||
elementAttribution: (_a = metric.attribution) === null || _a === void 0 ? void 0 : _a.interactionTarget | ||
}; | ||
}); | ||
} | ||
@@ -1380,3 +1418,4 @@ function setupUserTimingTraces(performanceController) { | ||
const measureName = measure.name; | ||
// Do not create a trace, if the user timing marks and measures are created by the sdk itself. | ||
// Do not create a trace, if the user timing marks and measures are created by | ||
// the sdk itself. | ||
if (measureName.substring(0, TRACE_MEASURE_PREFIX.length) === | ||
@@ -1388,2 +1427,15 @@ TRACE_MEASURE_PREFIX) { | ||
} | ||
function sendOobTrace(performanceController) { | ||
if (!sentPageLoadTrace) { | ||
sentPageLoadTrace = true; | ||
const api = Api.getInstance(); | ||
const navigationTimings = api.getEntriesByType('navigation'); | ||
const paintTimings = api.getEntriesByType('paint'); | ||
// On page unload web vitals may be updated so queue the oob trace creation | ||
// so that these updates have time to be included. | ||
setTimeout(() => { | ||
Trace.createOobTrace(performanceController, navigationTimings, paintTimings, webVitalMetrics, firstInputDelay); | ||
}, 0); | ||
} | ||
} | ||
@@ -1390,0 +1442,0 @@ /** |
@@ -29,2 +29,8 @@ /** | ||
export declare const FIRST_INPUT_DELAY_COUNTER_NAME = "_fid"; | ||
export declare const LARGEST_CONTENTFUL_PAINT_METRIC_NAME = "_lcp"; | ||
export declare const LARGEST_CONTENTFUL_PAINT_ATTRIBUTE_NAME = "lcp_element"; | ||
export declare const INTERACTION_TO_NEXT_PAINT_METRIC_NAME = "_inp"; | ||
export declare const INTERACTION_TO_NEXT_PAINT_ATTRIBUTE_NAME = "inp_interactionTarget"; | ||
export declare const CUMULATIVE_LAYOUT_SHIFT_METRIC_NAME = "_cls"; | ||
export declare const CUMULATIVE_LAYOUT_SHIFT_ATTRIBUTE_NAME = "cls_largestShiftTarget"; | ||
export declare const CONFIG_LOCAL_STORAGE_KEY = "@firebase/performance/config"; | ||
@@ -31,0 +37,0 @@ export declare const CONFIG_EXPIRY_LOCAL_STORAGE_KEY = "@firebase/performance/configexpire"; |
@@ -19,2 +19,3 @@ /** | ||
import { PerformanceController } from '../controllers/perf'; | ||
import { CoreVitalMetric, WebVitalMetrics } from './web_vitals'; | ||
export declare class Trace implements PerformanceTrace { | ||
@@ -118,4 +119,5 @@ readonly performanceController: PerformanceController; | ||
*/ | ||
static createOobTrace(performanceController: PerformanceController, navigationTimings: PerformanceNavigationTiming[], paintTimings: PerformanceEntry[], firstInputDelay?: number): void; | ||
static createOobTrace(performanceController: PerformanceController, navigationTimings: PerformanceNavigationTiming[], paintTimings: PerformanceEntry[], webVitalMetrics: WebVitalMetrics, firstInputDelay?: number): void; | ||
static addWebVitalMetric(trace: Trace, metricKey: string, attributeKey: string, metric?: CoreVitalMetric): void; | ||
static createUserTimingTrace(performanceController: PerformanceController, measureName: string): void; | ||
} |
@@ -17,2 +17,3 @@ /** | ||
*/ | ||
import { CLSMetricWithAttribution, INPMetricWithAttribution, LCPMetricWithAttribution } from 'web-vitals/attribution'; | ||
declare global { | ||
@@ -38,2 +39,5 @@ interface Window { | ||
readonly onFirstInputDelay?: (fn: (fid: number) => void) => void; | ||
readonly onLCP: (fn: (metric: LCPMetricWithAttribution) => void) => void; | ||
readonly onINP: (fn: (metric: INPMetricWithAttribution) => void) => void; | ||
readonly onCLS: (fn: (metric: CLSMetricWithAttribution) => void) => void; | ||
readonly localStorage?: Storage; | ||
@@ -40,0 +44,0 @@ readonly document: Document; |
@@ -19,1 +19,6 @@ /** | ||
export declare function setupOobResources(performanceController: PerformanceController): void; | ||
/** | ||
* This service will only export the page load trace once. This function allows | ||
* resetting it for unit tests | ||
*/ | ||
export declare function resetForUnitTests(): void; |
@@ -20,2 +20,3 @@ /** | ||
export declare function logTrace(trace: Trace): void; | ||
export declare function flushLogs(): void; | ||
export declare function logNetworkRequest(networkRequest: NetworkRequest): void; |
@@ -24,1 +24,6 @@ /** | ||
export declare function transportHandler(serializer: (...args: any[]) => string): (...args: unknown[]) => void; | ||
/** | ||
* Force flush the queued events. Useful at page unload time to ensure all | ||
* events are uploaded. | ||
*/ | ||
export declare function flushQueuedEvents(): void; |
@@ -7,2 +7,3 @@ 'use strict'; | ||
var logger$1 = require('@firebase/logger'); | ||
var attribution = require('web-vitals/attribution'); | ||
var app = require('@firebase/app'); | ||
@@ -13,3 +14,3 @@ var component = require('@firebase/component'); | ||
const name = "@firebase/performance"; | ||
const version = "0.6.12-canary.3c1559b7e"; | ||
const version = "0.6.12-canary.59ae45e48"; | ||
@@ -44,2 +45,8 @@ /** | ||
const FIRST_INPUT_DELAY_COUNTER_NAME = '_fid'; | ||
const LARGEST_CONTENTFUL_PAINT_METRIC_NAME = '_lcp'; | ||
const LARGEST_CONTENTFUL_PAINT_ATTRIBUTE_NAME = 'lcp_element'; | ||
const INTERACTION_TO_NEXT_PAINT_METRIC_NAME = '_inp'; | ||
const INTERACTION_TO_NEXT_PAINT_ATTRIBUTE_NAME = 'inp_interactionTarget'; | ||
const CUMULATIVE_LAYOUT_SHIFT_METRIC_NAME = '_cls'; | ||
const CUMULATIVE_LAYOUT_SHIFT_ATTRIBUTE_NAME = 'cls_largestShiftTarget'; | ||
const CONFIG_LOCAL_STORAGE_KEY = '@firebase/performance/config'; | ||
@@ -149,2 +156,5 @@ const CONFIG_EXPIRY_LOCAL_STORAGE_KEY = '@firebase/performance/configexpire'; | ||
} | ||
this.onLCP = attribution.onLCP; | ||
this.onINP = attribution.onINP; | ||
this.onCLS = attribution.onCLS; | ||
} | ||
@@ -703,5 +713,4 @@ getUrl() { | ||
const INITIAL_SEND_TIME_DELAY_MS = 5.5 * 1000; | ||
// If end point does not work, the call will be tried for these many times. | ||
const MAX_EVENT_COUNT_PER_REQUEST = 1000; | ||
const DEFAULT_REMAINING_TRIES = 3; | ||
const MAX_EVENT_COUNT_PER_REQUEST = 1000; | ||
let remainingTries = DEFAULT_REMAINING_TRIES; | ||
@@ -723,7 +732,6 @@ /* eslint-enable camelcase */ | ||
} | ||
// If there are no events to process, wait for DEFAULT_SEND_INTERVAL_MS and try again. | ||
if (!queue.length) { | ||
return processQueue(DEFAULT_SEND_INTERVAL_MS); | ||
if (queue.length > 0) { | ||
dispatchQueueEvents(); | ||
} | ||
dispatchQueueEvents(); | ||
processQueue(DEFAULT_SEND_INTERVAL_MS); | ||
}, timeOffset); | ||
@@ -752,3 +760,7 @@ } | ||
/* eslint-enable camelcase */ | ||
sendEventsToFl(data, staged).catch(() => { | ||
postToFlEndpoint(data) | ||
.then(() => { | ||
remainingTries = DEFAULT_REMAINING_TRIES; | ||
}) | ||
.catch(() => { | ||
// If the request fails for some reason, add the events that were attempted | ||
@@ -762,37 +774,12 @@ // back to the primary queue to retry later. | ||
} | ||
function sendEventsToFl(data, staged) { | ||
return postToFlEndpoint(data) | ||
.then(res => { | ||
if (!res.ok) { | ||
consoleLogger.info('Call to Firebase backend failed.'); | ||
} | ||
return res.json(); | ||
}) | ||
.then(res => { | ||
// Find the next call wait time from the response. | ||
const transportWait = Number(res.nextRequestWaitMillis); | ||
let requestOffset = DEFAULT_SEND_INTERVAL_MS; | ||
if (!isNaN(transportWait)) { | ||
requestOffset = Math.max(transportWait, requestOffset); | ||
} | ||
// Delete request if response include RESPONSE_ACTION_UNKNOWN or DELETE_REQUEST action. | ||
// Otherwise, retry request using normal scheduling if response include RETRY_REQUEST_LATER. | ||
const logResponseDetails = res.logResponseDetails; | ||
if (Array.isArray(logResponseDetails) && | ||
logResponseDetails.length > 0 && | ||
logResponseDetails[0].responseAction === 'RETRY_REQUEST_LATER') { | ||
queue = [...staged, ...queue]; | ||
consoleLogger.info(`Retry transport request later.`); | ||
} | ||
remainingTries = DEFAULT_REMAINING_TRIES; | ||
// Schedule the next process. | ||
processQueue(requestOffset); | ||
}); | ||
} | ||
function postToFlEndpoint(data) { | ||
const flTransportFullUrl = SettingsService.getInstance().getFlTransportFullUrl(); | ||
return fetch(flTransportFullUrl, { | ||
method: 'POST', | ||
body: JSON.stringify(data) | ||
}); | ||
const body = JSON.stringify(data); | ||
return navigator.sendBeacon && navigator.sendBeacon(flTransportFullUrl, body) | ||
? Promise.resolve() | ||
: fetch(flTransportFullUrl, { | ||
method: 'POST', | ||
body, | ||
keepalive: true | ||
}).then(); | ||
} | ||
@@ -818,2 +805,11 @@ function addToQueue(evt) { | ||
} | ||
/** | ||
* Force flush the queued events. Useful at page unload time to ensure all | ||
* events are uploaded. | ||
*/ | ||
function flushQueuedEvents() { | ||
while (queue.length > 0) { | ||
dispatchQueueEvents(); | ||
} | ||
} | ||
@@ -837,8 +833,12 @@ /** | ||
let logger; | ||
// | ||
// This method is not called before initialization. | ||
function sendLog(resource, resourceType) { | ||
if (!logger) { | ||
logger = transportHandler(serializer); | ||
logger = { | ||
send: transportHandler(serializer), | ||
flush: flushQueuedEvents | ||
}; | ||
} | ||
logger(resource, resourceType); | ||
logger.send(resource, resourceType); | ||
} | ||
@@ -859,6 +859,2 @@ function logTrace(trace) { | ||
} | ||
// Only log the page load auto traces if page is visible. | ||
if (trace.isAuto && getVisibilityState() !== VisibilityState.VISIBLE) { | ||
return; | ||
} | ||
if (isPerfInitialized()) { | ||
@@ -873,2 +869,7 @@ sendTraceLog(trace); | ||
} | ||
function flushLogs() { | ||
if (logger) { | ||
logger.flush(); | ||
} | ||
} | ||
function sendTraceLog(trace) { | ||
@@ -883,3 +884,3 @@ if (!getIid()) { | ||
} | ||
setTimeout(() => sendLog(trace, 1 /* ResourceType.Trace */), 0); | ||
sendLog(trace, 1 /* ResourceType.Trace */); | ||
} | ||
@@ -907,3 +908,3 @@ function logNetworkRequest(networkRequest) { | ||
} | ||
setTimeout(() => sendLog(networkRequest, 0 /* ResourceType.NetworkRequest */), 0); | ||
sendLog(networkRequest, 0 /* ResourceType.NetworkRequest */); | ||
} | ||
@@ -984,2 +985,42 @@ function serializer(resource, resourceType) { | ||
*/ | ||
function createNetworkRequestEntry(performanceController, entry) { | ||
const performanceEntry = entry; | ||
if (!performanceEntry || performanceEntry.responseStart === undefined) { | ||
return; | ||
} | ||
const timeOrigin = Api.getInstance().getTimeOrigin(); | ||
const startTimeUs = Math.floor((performanceEntry.startTime + timeOrigin) * 1000); | ||
const timeToResponseInitiatedUs = performanceEntry.responseStart | ||
? Math.floor((performanceEntry.responseStart - performanceEntry.startTime) * 1000) | ||
: undefined; | ||
const timeToResponseCompletedUs = Math.floor((performanceEntry.responseEnd - performanceEntry.startTime) * 1000); | ||
// Remove the query params from logged network request url. | ||
const url = performanceEntry.name && performanceEntry.name.split('?')[0]; | ||
const networkRequest = { | ||
performanceController, | ||
url, | ||
responsePayloadBytes: performanceEntry.transferSize, | ||
startTimeUs, | ||
timeToResponseInitiatedUs, | ||
timeToResponseCompletedUs | ||
}; | ||
logNetworkRequest(networkRequest); | ||
} | ||
/** | ||
* @license | ||
* Copyright 2020 Google LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
const MAX_METRIC_NAME_LENGTH = 100; | ||
@@ -990,3 +1031,6 @@ const RESERVED_AUTO_PREFIX = '_'; | ||
FIRST_CONTENTFUL_PAINT_COUNTER_NAME, | ||
FIRST_INPUT_DELAY_COUNTER_NAME | ||
FIRST_INPUT_DELAY_COUNTER_NAME, | ||
LARGEST_CONTENTFUL_PAINT_METRIC_NAME, | ||
CUMULATIVE_LAYOUT_SHIFT_METRIC_NAME, | ||
INTERACTION_TO_NEXT_PAINT_METRIC_NAME | ||
]; | ||
@@ -1230,3 +1274,3 @@ /** | ||
*/ | ||
static createOobTrace(performanceController, navigationTimings, paintTimings, firstInputDelay) { | ||
static createOobTrace(performanceController, navigationTimings, paintTimings, webVitalMetrics, firstInputDelay) { | ||
const route = Api.getInstance().getUrl(); | ||
@@ -1261,4 +1305,18 @@ if (!route) { | ||
} | ||
this.addWebVitalMetric(trace, LARGEST_CONTENTFUL_PAINT_METRIC_NAME, LARGEST_CONTENTFUL_PAINT_ATTRIBUTE_NAME, webVitalMetrics.lcp); | ||
this.addWebVitalMetric(trace, CUMULATIVE_LAYOUT_SHIFT_METRIC_NAME, CUMULATIVE_LAYOUT_SHIFT_ATTRIBUTE_NAME, webVitalMetrics.cls); | ||
this.addWebVitalMetric(trace, INTERACTION_TO_NEXT_PAINT_METRIC_NAME, INTERACTION_TO_NEXT_PAINT_ATTRIBUTE_NAME, webVitalMetrics.inp); | ||
// Page load logs are sent at unload time and so should be logged and | ||
// flushed immediately. | ||
logTrace(trace); | ||
flushLogs(); | ||
} | ||
static addWebVitalMetric(trace, metricKey, attributeKey, metric) { | ||
if (metric) { | ||
trace.putMetric(metricKey, Math.floor(metric.value * 1000)); | ||
if (metric.elementAttribution) { | ||
trace.putAttribute(attributeKey, metric.elementAttribution); | ||
} | ||
} | ||
} | ||
static createUserTimingTrace(performanceController, measureName) { | ||
@@ -1286,43 +1344,5 @@ const trace = new Trace(performanceController, measureName, false, measureName); | ||
*/ | ||
function createNetworkRequestEntry(performanceController, entry) { | ||
const performanceEntry = entry; | ||
if (!performanceEntry || performanceEntry.responseStart === undefined) { | ||
return; | ||
} | ||
const timeOrigin = Api.getInstance().getTimeOrigin(); | ||
const startTimeUs = Math.floor((performanceEntry.startTime + timeOrigin) * 1000); | ||
const timeToResponseInitiatedUs = performanceEntry.responseStart | ||
? Math.floor((performanceEntry.responseStart - performanceEntry.startTime) * 1000) | ||
: undefined; | ||
const timeToResponseCompletedUs = Math.floor((performanceEntry.responseEnd - performanceEntry.startTime) * 1000); | ||
// Remove the query params from logged network request url. | ||
const url = performanceEntry.name && performanceEntry.name.split('?')[0]; | ||
const networkRequest = { | ||
performanceController, | ||
url, | ||
responsePayloadBytes: performanceEntry.transferSize, | ||
startTimeUs, | ||
timeToResponseInitiatedUs, | ||
timeToResponseCompletedUs | ||
}; | ||
logNetworkRequest(networkRequest); | ||
} | ||
/** | ||
* @license | ||
* Copyright 2020 Google LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
const FID_WAIT_TIME_MS = 5000; | ||
let webVitalMetrics = {}; | ||
let sentPageLoadTrace = false; | ||
let firstInputDelay; | ||
function setupOobResources(performanceController) { | ||
@@ -1333,4 +1353,5 @@ // Do not initialize unless iid is available. | ||
} | ||
// The load event might not have fired yet, and that means performance navigation timing | ||
// object has a duration of 0. The setup should run after all current tasks in js queue. | ||
// The load event might not have fired yet, and that means performance | ||
// navigation timing object has a duration of 0. The setup should run after | ||
// all current tasks in js queue. | ||
setTimeout(() => setupOobTraces(performanceController), 0); | ||
@@ -1350,23 +1371,40 @@ setTimeout(() => setupNetworkRequests(performanceController), 0); | ||
const api = Api.getInstance(); | ||
const navigationTimings = api.getEntriesByType('navigation'); | ||
const paintTimings = api.getEntriesByType('paint'); | ||
// If First Input Delay polyfill is added to the page, report the fid value. | ||
// https://github.com/GoogleChromeLabs/first-input-delay | ||
// Better support for Safari | ||
if ('onpagehide' in window) { | ||
api.document.addEventListener('pagehide', () => sendOobTrace(performanceController)); | ||
} | ||
else { | ||
api.document.addEventListener('unload', () => sendOobTrace(performanceController)); | ||
} | ||
api.document.addEventListener('visibilitychange', () => { | ||
if (api.document.visibilityState === 'hidden') { | ||
sendOobTrace(performanceController); | ||
} | ||
}); | ||
if (api.onFirstInputDelay) { | ||
// If the fid call back is not called for certain time, continue without it. | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
let timeoutId = setTimeout(() => { | ||
Trace.createOobTrace(performanceController, navigationTimings, paintTimings); | ||
timeoutId = undefined; | ||
}, FID_WAIT_TIME_MS); | ||
api.onFirstInputDelay((fid) => { | ||
if (timeoutId) { | ||
clearTimeout(timeoutId); | ||
Trace.createOobTrace(performanceController, navigationTimings, paintTimings, fid); | ||
} | ||
firstInputDelay = fid; | ||
}); | ||
} | ||
else { | ||
Trace.createOobTrace(performanceController, navigationTimings, paintTimings); | ||
} | ||
api.onLCP((metric) => { | ||
var _a; | ||
webVitalMetrics.lcp = { | ||
value: metric.value, | ||
elementAttribution: (_a = metric.attribution) === null || _a === void 0 ? void 0 : _a.element | ||
}; | ||
}); | ||
api.onCLS((metric) => { | ||
var _a; | ||
webVitalMetrics.cls = { | ||
value: metric.value, | ||
elementAttribution: (_a = metric.attribution) === null || _a === void 0 ? void 0 : _a.largestShiftTarget | ||
}; | ||
}); | ||
api.onINP((metric) => { | ||
var _a; | ||
webVitalMetrics.inp = { | ||
value: metric.value, | ||
elementAttribution: (_a = metric.attribution) === null || _a === void 0 ? void 0 : _a.interactionTarget | ||
}; | ||
}); | ||
} | ||
@@ -1385,3 +1423,4 @@ function setupUserTimingTraces(performanceController) { | ||
const measureName = measure.name; | ||
// Do not create a trace, if the user timing marks and measures are created by the sdk itself. | ||
// Do not create a trace, if the user timing marks and measures are created by | ||
// the sdk itself. | ||
if (measureName.substring(0, TRACE_MEASURE_PREFIX.length) === | ||
@@ -1393,2 +1432,15 @@ TRACE_MEASURE_PREFIX) { | ||
} | ||
function sendOobTrace(performanceController) { | ||
if (!sentPageLoadTrace) { | ||
sentPageLoadTrace = true; | ||
const api = Api.getInstance(); | ||
const navigationTimings = api.getEntriesByType('navigation'); | ||
const paintTimings = api.getEntriesByType('paint'); | ||
// On page unload web vitals may be updated so queue the oob trace creation | ||
// so that these updates have time to be included. | ||
setTimeout(() => { | ||
Trace.createOobTrace(performanceController, navigationTimings, paintTimings, webVitalMetrics, firstInputDelay); | ||
}, 0); | ||
} | ||
} | ||
@@ -1395,0 +1447,0 @@ /** |
@@ -29,2 +29,8 @@ /** | ||
export declare const FIRST_INPUT_DELAY_COUNTER_NAME = "_fid"; | ||
export declare const LARGEST_CONTENTFUL_PAINT_METRIC_NAME = "_lcp"; | ||
export declare const LARGEST_CONTENTFUL_PAINT_ATTRIBUTE_NAME = "lcp_element"; | ||
export declare const INTERACTION_TO_NEXT_PAINT_METRIC_NAME = "_inp"; | ||
export declare const INTERACTION_TO_NEXT_PAINT_ATTRIBUTE_NAME = "inp_interactionTarget"; | ||
export declare const CUMULATIVE_LAYOUT_SHIFT_METRIC_NAME = "_cls"; | ||
export declare const CUMULATIVE_LAYOUT_SHIFT_ATTRIBUTE_NAME = "cls_largestShiftTarget"; | ||
export declare const CONFIG_LOCAL_STORAGE_KEY = "@firebase/performance/config"; | ||
@@ -31,0 +37,0 @@ export declare const CONFIG_EXPIRY_LOCAL_STORAGE_KEY = "@firebase/performance/configexpire"; |
@@ -19,2 +19,3 @@ /** | ||
import { PerformanceController } from '../controllers/perf'; | ||
import { CoreVitalMetric, WebVitalMetrics } from './web_vitals'; | ||
export declare class Trace implements PerformanceTrace { | ||
@@ -118,4 +119,5 @@ readonly performanceController: PerformanceController; | ||
*/ | ||
static createOobTrace(performanceController: PerformanceController, navigationTimings: PerformanceNavigationTiming[], paintTimings: PerformanceEntry[], firstInputDelay?: number): void; | ||
static createOobTrace(performanceController: PerformanceController, navigationTimings: PerformanceNavigationTiming[], paintTimings: PerformanceEntry[], webVitalMetrics: WebVitalMetrics, firstInputDelay?: number): void; | ||
static addWebVitalMetric(trace: Trace, metricKey: string, attributeKey: string, metric?: CoreVitalMetric): void; | ||
static createUserTimingTrace(performanceController: PerformanceController, measureName: string): void; | ||
} |
@@ -17,2 +17,3 @@ /** | ||
*/ | ||
import { CLSMetricWithAttribution, INPMetricWithAttribution, LCPMetricWithAttribution } from 'web-vitals/attribution'; | ||
declare global { | ||
@@ -38,2 +39,5 @@ interface Window { | ||
readonly onFirstInputDelay?: (fn: (fid: number) => void) => void; | ||
readonly onLCP: (fn: (metric: LCPMetricWithAttribution) => void) => void; | ||
readonly onINP: (fn: (metric: INPMetricWithAttribution) => void) => void; | ||
readonly onCLS: (fn: (metric: CLSMetricWithAttribution) => void) => void; | ||
readonly localStorage?: Storage; | ||
@@ -40,0 +44,0 @@ readonly document: Document; |
@@ -19,1 +19,6 @@ /** | ||
export declare function setupOobResources(performanceController: PerformanceController): void; | ||
/** | ||
* This service will only export the page load trace once. This function allows | ||
* resetting it for unit tests | ||
*/ | ||
export declare function resetForUnitTests(): void; |
@@ -20,2 +20,3 @@ /** | ||
export declare function logTrace(trace: Trace): void; | ||
export declare function flushLogs(): void; | ||
export declare function logNetworkRequest(networkRequest: NetworkRequest): void; |
@@ -24,1 +24,6 @@ /** | ||
export declare function transportHandler(serializer: (...args: any[]) => string): (...args: unknown[]) => void; | ||
/** | ||
* Force flush the queued events. Useful at page unload time to ensure all | ||
* events are uploaded. | ||
*/ | ||
export declare function flushQueuedEvents(): void; |
{ | ||
"name": "@firebase/performance", | ||
"version": "0.6.12-canary.3c1559b7e", | ||
"version": "0.6.12-canary.59ae45e48", | ||
"description": "Firebase performance for web", | ||
@@ -38,16 +38,17 @@ "author": "Firebase <firebase-support@google.com> (https://firebase.google.com/)", | ||
"peerDependencies": { | ||
"@firebase/app": "0.10.18-canary.3c1559b7e" | ||
"@firebase/app": "0.10.18-canary.59ae45e48" | ||
}, | ||
"dependencies": { | ||
"@firebase/logger": "0.4.4-canary.3c1559b7e", | ||
"@firebase/installations": "0.6.12-canary.3c1559b7e", | ||
"@firebase/util": "1.10.3-canary.3c1559b7e", | ||
"@firebase/component": "0.6.12-canary.3c1559b7e", | ||
"tslib": "^2.1.0" | ||
"@firebase/logger": "0.4.4-canary.59ae45e48", | ||
"@firebase/installations": "0.6.12-canary.59ae45e48", | ||
"@firebase/util": "1.10.3-canary.59ae45e48", | ||
"@firebase/component": "0.6.12-canary.59ae45e48", | ||
"tslib": "^2.1.0", | ||
"web-vitals": "^4.2.4" | ||
}, | ||
"license": "Apache-2.0", | ||
"devDependencies": { | ||
"@firebase/app": "0.10.18-canary.3c1559b7e", | ||
"@firebase/app": "0.10.18-canary.59ae45e48", | ||
"rollup": "2.79.1", | ||
"@rollup/plugin-json": "4.1.0", | ||
"@rollup/plugin-json": "6.1.0", | ||
"rollup-plugin-typescript2": "0.31.2", | ||
@@ -54,0 +55,0 @@ "typescript": "5.5.4" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
441730
80
5325
7
+ Addedweb-vitals@^4.2.4
+ Added@firebase/app@0.10.18-canary.59ae45e48(transitive)
+ Added@firebase/component@0.6.12-canary.59ae45e48(transitive)
+ Added@firebase/installations@0.6.12-canary.59ae45e48(transitive)
+ Added@firebase/logger@0.4.4-canary.59ae45e48(transitive)
+ Added@firebase/util@1.10.3-canary.59ae45e48(transitive)
+ Addedweb-vitals@4.2.4(transitive)
- Removed@firebase/app@0.10.18-canary.3c1559b7e(transitive)
- Removed@firebase/component@0.6.12-canary.3c1559b7e(transitive)
- Removed@firebase/installations@0.6.12-canary.3c1559b7e(transitive)
- Removed@firebase/logger@0.4.4-canary.3c1559b7e(transitive)
- Removed@firebase/util@1.10.3-canary.3c1559b7e(transitive)