@firebase/performance 0.6.11 → 0.6.12-canary.01f36ea41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,12 @@
1
1
  import { ErrorFactory, areCookiesEnabled, isIndexedDBAvailable, validateIndexedDBOpenable, getModularInstance, deepEqual } from '@firebase/util';
2
2
  import { Logger, LogLevel } from '@firebase/logger';
3
+ import { onLCP, onINP, onCLS } from 'web-vitals/attribution';
3
4
  import { _getProvider, getApp, _registerComponent, registerVersion } from '@firebase/app';
4
5
  import { Component } from '@firebase/component';
5
6
  import '@firebase/installations';
6
7
 
7
8
  const name = "@firebase/performance";
8
- const version = "0.6.11";
9
+ const version = "0.6.12-canary.01f36ea41";
9
10
 
10
11
  /**
11
12
  * @license
@@ -35,6 +36,12 @@ const OOB_TRACE_PAGE_LOAD_PREFIX = '_wt_';
35
36
  const FIRST_PAINT_COUNTER_NAME = '_fp';
36
37
  const FIRST_CONTENTFUL_PAINT_COUNTER_NAME = '_fcp';
37
38
  const FIRST_INPUT_DELAY_COUNTER_NAME = '_fid';
39
+ const LARGEST_CONTENTFUL_PAINT_METRIC_NAME = '_lcp';
40
+ const LARGEST_CONTENTFUL_PAINT_ATTRIBUTE_NAME = 'lcp_element';
41
+ const INTERACTION_TO_NEXT_PAINT_METRIC_NAME = '_inp';
42
+ const INTERACTION_TO_NEXT_PAINT_ATTRIBUTE_NAME = 'inp_interactionTarget';
43
+ const CUMULATIVE_LAYOUT_SHIFT_METRIC_NAME = '_cls';
44
+ const CUMULATIVE_LAYOUT_SHIFT_ATTRIBUTE_NAME = 'cls_largestShiftTarget';
38
45
  const CONFIG_LOCAL_STORAGE_KEY = '@firebase/performance/config';
39
46
  const CONFIG_EXPIRY_LOCAL_STORAGE_KEY = '@firebase/performance/configexpire';
40
47
  const SERVICE = 'performance';
@@ -139,6 +146,9 @@ class Api {
139
146
  if (window.perfMetrics && window.perfMetrics.onFirstInputDelay) {
140
147
  this.onFirstInputDelay = window.perfMetrics.onFirstInputDelay;
141
148
  }
149
+ this.onLCP = onLCP;
150
+ this.onINP = onINP;
151
+ this.onCLS = onCLS;
142
152
  }
143
153
  getUrl() {
144
154
  // Do not capture the string query part of url.
@@ -692,9 +702,8 @@ function changeInitializationStatus() {
692
702
  */
693
703
  const DEFAULT_SEND_INTERVAL_MS = 10 * 1000;
694
704
  const INITIAL_SEND_TIME_DELAY_MS = 5.5 * 1000;
695
- // If end point does not work, the call will be tried for these many times.
696
- const DEFAULT_REMAINING_TRIES = 3;
697
705
  const MAX_EVENT_COUNT_PER_REQUEST = 1000;
706
+ const DEFAULT_REMAINING_TRIES = 3;
698
707
  let remainingTries = DEFAULT_REMAINING_TRIES;
699
708
  /* eslint-enable camelcase */
700
709
  let queue = [];
@@ -711,11 +720,10 @@ function processQueue(timeOffset) {
711
720
  if (remainingTries === 0) {
712
721
  return;
713
722
  }
714
- // If there are no events to process, wait for DEFAULT_SEND_INTERVAL_MS and try again.
715
- if (!queue.length) {
716
- return processQueue(DEFAULT_SEND_INTERVAL_MS);
723
+ if (queue.length > 0) {
724
+ dispatchQueueEvents();
717
725
  }
718
- dispatchQueueEvents();
726
+ processQueue(DEFAULT_SEND_INTERVAL_MS);
719
727
  }, timeOffset);
720
728
  }
721
729
  function dispatchQueueEvents() {
@@ -739,7 +747,11 @@ function dispatchQueueEvents() {
739
747
  log_event
740
748
  };
741
749
  /* eslint-enable camelcase */
742
- sendEventsToFl(data, staged).catch(() => {
750
+ postToFlEndpoint(data)
751
+ .then(() => {
752
+ remainingTries = DEFAULT_REMAINING_TRIES;
753
+ })
754
+ .catch(() => {
743
755
  // If the request fails for some reason, add the events that were attempted
744
756
  // back to the primary queue to retry later.
745
757
  queue = [...staged, ...queue];
@@ -748,41 +760,16 @@ function dispatchQueueEvents() {
748
760
  processQueue(DEFAULT_SEND_INTERVAL_MS);
749
761
  });
750
762
  }
751
- function sendEventsToFl(data, staged) {
752
- return postToFlEndpoint(data)
753
- .then(res => {
754
- if (!res.ok) {
755
- consoleLogger.info('Call to Firebase backend failed.');
756
- }
757
- return res.json();
758
- })
759
- .then(res => {
760
- // Find the next call wait time from the response.
761
- const transportWait = Number(res.nextRequestWaitMillis);
762
- let requestOffset = DEFAULT_SEND_INTERVAL_MS;
763
- if (!isNaN(transportWait)) {
764
- requestOffset = Math.max(transportWait, requestOffset);
765
- }
766
- // Delete request if response include RESPONSE_ACTION_UNKNOWN or DELETE_REQUEST action.
767
- // Otherwise, retry request using normal scheduling if response include RETRY_REQUEST_LATER.
768
- const logResponseDetails = res.logResponseDetails;
769
- if (Array.isArray(logResponseDetails) &&
770
- logResponseDetails.length > 0 &&
771
- logResponseDetails[0].responseAction === 'RETRY_REQUEST_LATER') {
772
- queue = [...staged, ...queue];
773
- consoleLogger.info(`Retry transport request later.`);
774
- }
775
- remainingTries = DEFAULT_REMAINING_TRIES;
776
- // Schedule the next process.
777
- processQueue(requestOffset);
778
- });
779
- }
780
763
  function postToFlEndpoint(data) {
781
764
  const flTransportFullUrl = SettingsService.getInstance().getFlTransportFullUrl();
782
- return fetch(flTransportFullUrl, {
783
- method: 'POST',
784
- body: JSON.stringify(data)
785
- });
765
+ const body = JSON.stringify(data);
766
+ return navigator.sendBeacon && navigator.sendBeacon(flTransportFullUrl, body)
767
+ ? Promise.resolve()
768
+ : fetch(flTransportFullUrl, {
769
+ method: 'POST',
770
+ body,
771
+ keepalive: true
772
+ }).then();
786
773
  }
787
774
  function addToQueue(evt) {
788
775
  if (!evt.eventTime || !evt.message) {
@@ -803,6 +790,15 @@ serializer) {
803
790
  });
804
791
  };
805
792
  }
793
+ /**
794
+ * Force flush the queued events. Useful at page unload time to ensure all
795
+ * events are uploaded.
796
+ */
797
+ function flushQueuedEvents() {
798
+ while (queue.length > 0) {
799
+ dispatchQueueEvents();
800
+ }
801
+ }
806
802
 
807
803
  /**
808
804
  * @license
@@ -821,12 +817,16 @@ serializer) {
821
817
  * limitations under the License.
822
818
  */
823
819
  let logger;
820
+ //
824
821
  // This method is not called before initialization.
825
822
  function sendLog(resource, resourceType) {
826
823
  if (!logger) {
827
- logger = transportHandler(serializer);
824
+ logger = {
825
+ send: transportHandler(serializer),
826
+ flush: flushQueuedEvents
827
+ };
828
828
  }
829
- logger(resource, resourceType);
829
+ logger.send(resource, resourceType);
830
830
  }
831
831
  function logTrace(trace) {
832
832
  const settingsService = SettingsService.getInstance();
@@ -842,10 +842,6 @@ function logTrace(trace) {
842
842
  if (!Api.getInstance().requiredApisAvailable()) {
843
843
  return;
844
844
  }
845
- // Only log the page load auto traces if page is visible.
846
- if (trace.isAuto && getVisibilityState() !== VisibilityState.VISIBLE) {
847
- return;
848
- }
849
845
  if (isPerfInitialized()) {
850
846
  sendTraceLog(trace);
851
847
  }
@@ -855,6 +851,11 @@ function logTrace(trace) {
855
851
  getInitializationPromise(trace.performanceController).then(() => sendTraceLog(trace), () => sendTraceLog(trace));
856
852
  }
857
853
  }
854
+ function flushLogs() {
855
+ if (logger) {
856
+ logger.flush();
857
+ }
858
+ }
858
859
  function sendTraceLog(trace) {
859
860
  if (!getIid()) {
860
861
  return;
@@ -864,7 +865,7 @@ function sendTraceLog(trace) {
864
865
  !settingsService.logTraceAfterSampling) {
865
866
  return;
866
867
  }
867
- setTimeout(() => sendLog(trace, 1 /* ResourceType.Trace */), 0);
868
+ sendLog(trace, 1 /* ResourceType.Trace */);
868
869
  }
869
870
  function logNetworkRequest(networkRequest) {
870
871
  const settingsService = SettingsService.getInstance();
@@ -887,7 +888,7 @@ function logNetworkRequest(networkRequest) {
887
888
  !settingsService.logNetworkAfterSampling) {
888
889
  return;
889
890
  }
890
- setTimeout(() => sendLog(networkRequest, 0 /* ResourceType.NetworkRequest */), 0);
891
+ sendLog(networkRequest, 0 /* ResourceType.NetworkRequest */);
891
892
  }
892
893
  function serializer(resource, resourceType) {
893
894
  if (resourceType === 0 /* ResourceType.NetworkRequest */) {
@@ -947,6 +948,46 @@ function getApplicationInfo(firebaseApp) {
947
948
  }
948
949
  /* eslint-enable camelcase */
949
950
 
951
+ /**
952
+ * @license
953
+ * Copyright 2020 Google LLC
954
+ *
955
+ * Licensed under the Apache License, Version 2.0 (the "License");
956
+ * you may not use this file except in compliance with the License.
957
+ * You may obtain a copy of the License at
958
+ *
959
+ * http://www.apache.org/licenses/LICENSE-2.0
960
+ *
961
+ * Unless required by applicable law or agreed to in writing, software
962
+ * distributed under the License is distributed on an "AS IS" BASIS,
963
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
964
+ * See the License for the specific language governing permissions and
965
+ * limitations under the License.
966
+ */
967
+ function createNetworkRequestEntry(performanceController, entry) {
968
+ const performanceEntry = entry;
969
+ if (!performanceEntry || performanceEntry.responseStart === undefined) {
970
+ return;
971
+ }
972
+ const timeOrigin = Api.getInstance().getTimeOrigin();
973
+ const startTimeUs = Math.floor((performanceEntry.startTime + timeOrigin) * 1000);
974
+ const timeToResponseInitiatedUs = performanceEntry.responseStart
975
+ ? Math.floor((performanceEntry.responseStart - performanceEntry.startTime) * 1000)
976
+ : undefined;
977
+ const timeToResponseCompletedUs = Math.floor((performanceEntry.responseEnd - performanceEntry.startTime) * 1000);
978
+ // Remove the query params from logged network request url.
979
+ const url = performanceEntry.name && performanceEntry.name.split('?')[0];
980
+ const networkRequest = {
981
+ performanceController,
982
+ url,
983
+ responsePayloadBytes: performanceEntry.transferSize,
984
+ startTimeUs,
985
+ timeToResponseInitiatedUs,
986
+ timeToResponseCompletedUs
987
+ };
988
+ logNetworkRequest(networkRequest);
989
+ }
990
+
950
991
  /**
951
992
  * @license
952
993
  * Copyright 2020 Google LLC
@@ -968,7 +1009,10 @@ const RESERVED_AUTO_PREFIX = '_';
968
1009
  const oobMetrics = [
969
1010
  FIRST_PAINT_COUNTER_NAME,
970
1011
  FIRST_CONTENTFUL_PAINT_COUNTER_NAME,
971
- FIRST_INPUT_DELAY_COUNTER_NAME
1012
+ FIRST_INPUT_DELAY_COUNTER_NAME,
1013
+ LARGEST_CONTENTFUL_PAINT_METRIC_NAME,
1014
+ CUMULATIVE_LAYOUT_SHIFT_METRIC_NAME,
1015
+ INTERACTION_TO_NEXT_PAINT_METRIC_NAME
972
1016
  ];
973
1017
  /**
974
1018
  * Returns true if the metric is custom and does not start with reserved prefix, or if
@@ -1207,7 +1251,7 @@ class Trace {
1207
1251
  * @param paintTimings A array which contains paintTiming object of the page load
1208
1252
  * @param firstInputDelay First input delay in millisec
1209
1253
  */
1210
- static createOobTrace(performanceController, navigationTimings, paintTimings, firstInputDelay) {
1254
+ static createOobTrace(performanceController, navigationTimings, paintTimings, webVitalMetrics, firstInputDelay) {
1211
1255
  const route = Api.getInstance().getUrl();
1212
1256
  if (!route) {
1213
1257
  return;
@@ -1237,7 +1281,21 @@ class Trace {
1237
1281
  trace.putMetric(FIRST_INPUT_DELAY_COUNTER_NAME, Math.floor(firstInputDelay * 1000));
1238
1282
  }
1239
1283
  }
1284
+ this.addWebVitalMetric(trace, LARGEST_CONTENTFUL_PAINT_METRIC_NAME, LARGEST_CONTENTFUL_PAINT_ATTRIBUTE_NAME, webVitalMetrics.lcp);
1285
+ this.addWebVitalMetric(trace, CUMULATIVE_LAYOUT_SHIFT_METRIC_NAME, CUMULATIVE_LAYOUT_SHIFT_ATTRIBUTE_NAME, webVitalMetrics.cls);
1286
+ this.addWebVitalMetric(trace, INTERACTION_TO_NEXT_PAINT_METRIC_NAME, INTERACTION_TO_NEXT_PAINT_ATTRIBUTE_NAME, webVitalMetrics.inp);
1287
+ // Page load logs are sent at unload time and so should be logged and
1288
+ // flushed immediately.
1240
1289
  logTrace(trace);
1290
+ flushLogs();
1291
+ }
1292
+ static addWebVitalMetric(trace, metricKey, attributeKey, metric) {
1293
+ if (metric) {
1294
+ trace.putMetric(metricKey, Math.floor(metric.value * 1000));
1295
+ if (metric.elementAttribution) {
1296
+ trace.putAttribute(attributeKey, metric.elementAttribution);
1297
+ }
1298
+ }
1241
1299
  }
1242
1300
  static createUserTimingTrace(performanceController, measureName) {
1243
1301
  const trace = new Trace(performanceController, measureName, false, measureName);
@@ -1261,54 +1319,17 @@ class Trace {
1261
1319
  * See the License for the specific language governing permissions and
1262
1320
  * limitations under the License.
1263
1321
  */
1264
- function createNetworkRequestEntry(performanceController, entry) {
1265
- const performanceEntry = entry;
1266
- if (!performanceEntry || performanceEntry.responseStart === undefined) {
1267
- return;
1268
- }
1269
- const timeOrigin = Api.getInstance().getTimeOrigin();
1270
- const startTimeUs = Math.floor((performanceEntry.startTime + timeOrigin) * 1000);
1271
- const timeToResponseInitiatedUs = performanceEntry.responseStart
1272
- ? Math.floor((performanceEntry.responseStart - performanceEntry.startTime) * 1000)
1273
- : undefined;
1274
- const timeToResponseCompletedUs = Math.floor((performanceEntry.responseEnd - performanceEntry.startTime) * 1000);
1275
- // Remove the query params from logged network request url.
1276
- const url = performanceEntry.name && performanceEntry.name.split('?')[0];
1277
- const networkRequest = {
1278
- performanceController,
1279
- url,
1280
- responsePayloadBytes: performanceEntry.transferSize,
1281
- startTimeUs,
1282
- timeToResponseInitiatedUs,
1283
- timeToResponseCompletedUs
1284
- };
1285
- logNetworkRequest(networkRequest);
1286
- }
1287
-
1288
- /**
1289
- * @license
1290
- * Copyright 2020 Google LLC
1291
- *
1292
- * Licensed under the Apache License, Version 2.0 (the "License");
1293
- * you may not use this file except in compliance with the License.
1294
- * You may obtain a copy of the License at
1295
- *
1296
- * http://www.apache.org/licenses/LICENSE-2.0
1297
- *
1298
- * Unless required by applicable law or agreed to in writing, software
1299
- * distributed under the License is distributed on an "AS IS" BASIS,
1300
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1301
- * See the License for the specific language governing permissions and
1302
- * limitations under the License.
1303
- */
1304
- const FID_WAIT_TIME_MS = 5000;
1322
+ let webVitalMetrics = {};
1323
+ let sentPageLoadTrace = false;
1324
+ let firstInputDelay;
1305
1325
  function setupOobResources(performanceController) {
1306
1326
  // Do not initialize unless iid is available.
1307
1327
  if (!getIid()) {
1308
1328
  return;
1309
1329
  }
1310
- // The load event might not have fired yet, and that means performance navigation timing
1311
- // object has a duration of 0. The setup should run after all current tasks in js queue.
1330
+ // The load event might not have fired yet, and that means performance
1331
+ // navigation timing object has a duration of 0. The setup should run after
1332
+ // all current tasks in js queue.
1312
1333
  setTimeout(() => setupOobTraces(performanceController), 0);
1313
1334
  setTimeout(() => setupNetworkRequests(performanceController), 0);
1314
1335
  setTimeout(() => setupUserTimingTraces(performanceController), 0);
@@ -1323,27 +1344,44 @@ function setupNetworkRequests(performanceController) {
1323
1344
  }
1324
1345
  function setupOobTraces(performanceController) {
1325
1346
  const api = Api.getInstance();
1326
- const navigationTimings = api.getEntriesByType('navigation');
1327
- const paintTimings = api.getEntriesByType('paint');
1328
- // If First Input Delay polyfill is added to the page, report the fid value.
1329
- // https://github.com/GoogleChromeLabs/first-input-delay
1347
+ // Better support for Safari
1348
+ if ('onpagehide' in window) {
1349
+ api.document.addEventListener('pagehide', () => sendOobTrace(performanceController));
1350
+ }
1351
+ else {
1352
+ api.document.addEventListener('unload', () => sendOobTrace(performanceController));
1353
+ }
1354
+ api.document.addEventListener('visibilitychange', () => {
1355
+ if (api.document.visibilityState === 'hidden') {
1356
+ sendOobTrace(performanceController);
1357
+ }
1358
+ });
1330
1359
  if (api.onFirstInputDelay) {
1331
- // If the fid call back is not called for certain time, continue without it.
1332
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1333
- let timeoutId = setTimeout(() => {
1334
- Trace.createOobTrace(performanceController, navigationTimings, paintTimings);
1335
- timeoutId = undefined;
1336
- }, FID_WAIT_TIME_MS);
1337
1360
  api.onFirstInputDelay((fid) => {
1338
- if (timeoutId) {
1339
- clearTimeout(timeoutId);
1340
- Trace.createOobTrace(performanceController, navigationTimings, paintTimings, fid);
1341
- }
1361
+ firstInputDelay = fid;
1342
1362
  });
1343
1363
  }
1344
- else {
1345
- Trace.createOobTrace(performanceController, navigationTimings, paintTimings);
1346
- }
1364
+ api.onLCP((metric) => {
1365
+ var _a;
1366
+ webVitalMetrics.lcp = {
1367
+ value: metric.value,
1368
+ elementAttribution: (_a = metric.attribution) === null || _a === void 0 ? void 0 : _a.element
1369
+ };
1370
+ });
1371
+ api.onCLS((metric) => {
1372
+ var _a;
1373
+ webVitalMetrics.cls = {
1374
+ value: metric.value,
1375
+ elementAttribution: (_a = metric.attribution) === null || _a === void 0 ? void 0 : _a.largestShiftTarget
1376
+ };
1377
+ });
1378
+ api.onINP((metric) => {
1379
+ var _a;
1380
+ webVitalMetrics.inp = {
1381
+ value: metric.value,
1382
+ elementAttribution: (_a = metric.attribution) === null || _a === void 0 ? void 0 : _a.interactionTarget
1383
+ };
1384
+ });
1347
1385
  }
1348
1386
  function setupUserTimingTraces(performanceController) {
1349
1387
  const api = Api.getInstance();
@@ -1357,13 +1395,27 @@ function setupUserTimingTraces(performanceController) {
1357
1395
  }
1358
1396
  function createUserTimingTrace(performanceController, measure) {
1359
1397
  const measureName = measure.name;
1360
- // Do not create a trace, if the user timing marks and measures are created by the sdk itself.
1398
+ // Do not create a trace, if the user timing marks and measures are created by
1399
+ // the sdk itself.
1361
1400
  if (measureName.substring(0, TRACE_MEASURE_PREFIX.length) ===
1362
1401
  TRACE_MEASURE_PREFIX) {
1363
1402
  return;
1364
1403
  }
1365
1404
  Trace.createUserTimingTrace(performanceController, measureName);
1366
1405
  }
1406
+ function sendOobTrace(performanceController) {
1407
+ if (!sentPageLoadTrace) {
1408
+ sentPageLoadTrace = true;
1409
+ const api = Api.getInstance();
1410
+ const navigationTimings = api.getEntriesByType('navigation');
1411
+ const paintTimings = api.getEntriesByType('paint');
1412
+ // On page unload web vitals may be updated so queue the oob trace creation
1413
+ // so that these updates have time to be included.
1414
+ setTimeout(() => {
1415
+ Trace.createOobTrace(performanceController, navigationTimings, paintTimings, webVitalMetrics, firstInputDelay);
1416
+ }, 0);
1417
+ }
1418
+ }
1367
1419
 
1368
1420
  /**
1369
1421
  * @license