No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.esm2017.js 59KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520
  1. import { ErrorFactory, areCookiesEnabled, isIndexedDBAvailable, validateIndexedDBOpenable, getModularInstance, deepEqual } from '@firebase/util';
  2. import { Logger, LogLevel } from '@firebase/logger';
  3. import { _getProvider, getApp, _registerComponent, registerVersion } from '@firebase/app';
  4. import { Component } from '@firebase/component';
  5. import '@firebase/installations';
  6. const name = "@firebase/performance";
  7. const version = "0.6.1";
  8. /**
  9. * @license
  10. * Copyright 2020 Google LLC
  11. *
  12. * Licensed under the Apache License, Version 2.0 (the "License");
  13. * you may not use this file except in compliance with the License.
  14. * You may obtain a copy of the License at
  15. *
  16. * http://www.apache.org/licenses/LICENSE-2.0
  17. *
  18. * Unless required by applicable law or agreed to in writing, software
  19. * distributed under the License is distributed on an "AS IS" BASIS,
  20. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21. * See the License for the specific language governing permissions and
  22. * limitations under the License.
  23. */
  24. const SDK_VERSION = version;
  25. /** The prefix for start User Timing marks used for creating Traces. */
  26. const TRACE_START_MARK_PREFIX = 'FB-PERF-TRACE-START';
  27. /** The prefix for stop User Timing marks used for creating Traces. */
  28. const TRACE_STOP_MARK_PREFIX = 'FB-PERF-TRACE-STOP';
  29. /** The prefix for User Timing measure used for creating Traces. */
  30. const TRACE_MEASURE_PREFIX = 'FB-PERF-TRACE-MEASURE';
  31. /** The prefix for out of the box page load Trace name. */
  32. const OOB_TRACE_PAGE_LOAD_PREFIX = '_wt_';
  33. const FIRST_PAINT_COUNTER_NAME = '_fp';
  34. const FIRST_CONTENTFUL_PAINT_COUNTER_NAME = '_fcp';
  35. const FIRST_INPUT_DELAY_COUNTER_NAME = '_fid';
  36. const CONFIG_LOCAL_STORAGE_KEY = '@firebase/performance/config';
  37. const CONFIG_EXPIRY_LOCAL_STORAGE_KEY = '@firebase/performance/configexpire';
  38. const SERVICE = 'performance';
  39. const SERVICE_NAME = 'Performance';
  40. /**
  41. * @license
  42. * Copyright 2020 Google LLC
  43. *
  44. * Licensed under the Apache License, Version 2.0 (the "License");
  45. * you may not use this file except in compliance with the License.
  46. * You may obtain a copy of the License at
  47. *
  48. * http://www.apache.org/licenses/LICENSE-2.0
  49. *
  50. * Unless required by applicable law or agreed to in writing, software
  51. * distributed under the License is distributed on an "AS IS" BASIS,
  52. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  53. * See the License for the specific language governing permissions and
  54. * limitations under the License.
  55. */
  56. const ERROR_DESCRIPTION_MAP = {
  57. ["trace started" /* ErrorCode.TRACE_STARTED_BEFORE */]: 'Trace {$traceName} was started before.',
  58. ["trace stopped" /* ErrorCode.TRACE_STOPPED_BEFORE */]: 'Trace {$traceName} is not running.',
  59. ["nonpositive trace startTime" /* ErrorCode.NONPOSITIVE_TRACE_START_TIME */]: 'Trace {$traceName} startTime should be positive.',
  60. ["nonpositive trace duration" /* ErrorCode.NONPOSITIVE_TRACE_DURATION */]: 'Trace {$traceName} duration should be positive.',
  61. ["no window" /* ErrorCode.NO_WINDOW */]: 'Window is not available.',
  62. ["no app id" /* ErrorCode.NO_APP_ID */]: 'App id is not available.',
  63. ["no project id" /* ErrorCode.NO_PROJECT_ID */]: 'Project id is not available.',
  64. ["no api key" /* ErrorCode.NO_API_KEY */]: 'Api key is not available.',
  65. ["invalid cc log" /* ErrorCode.INVALID_CC_LOG */]: 'Attempted to queue invalid cc event',
  66. ["FB not default" /* ErrorCode.FB_NOT_DEFAULT */]: 'Performance can only start when Firebase app instance is the default one.',
  67. ["RC response not ok" /* ErrorCode.RC_NOT_OK */]: 'RC response is not ok',
  68. ["invalid attribute name" /* ErrorCode.INVALID_ATTRIBUTE_NAME */]: 'Attribute name {$attributeName} is invalid.',
  69. ["invalid attribute value" /* ErrorCode.INVALID_ATTRIBUTE_VALUE */]: 'Attribute value {$attributeValue} is invalid.',
  70. ["invalid custom metric name" /* ErrorCode.INVALID_CUSTOM_METRIC_NAME */]: 'Custom metric name {$customMetricName} is invalid',
  71. ["invalid String merger input" /* ErrorCode.INVALID_STRING_MERGER_PARAMETER */]: 'Input for String merger is invalid, contact support team to resolve.',
  72. ["already initialized" /* ErrorCode.ALREADY_INITIALIZED */]: 'initializePerformance() has already been called with ' +
  73. 'different options. To avoid this error, call initializePerformance() with the ' +
  74. 'same options as when it was originally called, or call getPerformance() to return the' +
  75. ' already initialized instance.'
  76. };
  77. const ERROR_FACTORY = new ErrorFactory(SERVICE, SERVICE_NAME, ERROR_DESCRIPTION_MAP);
  78. /**
  79. * @license
  80. * Copyright 2020 Google LLC
  81. *
  82. * Licensed under the Apache License, Version 2.0 (the "License");
  83. * you may not use this file except in compliance with the License.
  84. * You may obtain a copy of the License at
  85. *
  86. * http://www.apache.org/licenses/LICENSE-2.0
  87. *
  88. * Unless required by applicable law or agreed to in writing, software
  89. * distributed under the License is distributed on an "AS IS" BASIS,
  90. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  91. * See the License for the specific language governing permissions and
  92. * limitations under the License.
  93. */
  94. const consoleLogger = new Logger(SERVICE_NAME);
  95. consoleLogger.logLevel = LogLevel.INFO;
  96. /**
  97. * @license
  98. * Copyright 2020 Google LLC
  99. *
  100. * Licensed under the Apache License, Version 2.0 (the "License");
  101. * you may not use this file except in compliance with the License.
  102. * You may obtain a copy of the License at
  103. *
  104. * http://www.apache.org/licenses/LICENSE-2.0
  105. *
  106. * Unless required by applicable law or agreed to in writing, software
  107. * distributed under the License is distributed on an "AS IS" BASIS,
  108. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  109. * See the License for the specific language governing permissions and
  110. * limitations under the License.
  111. */
  112. let apiInstance;
  113. let windowInstance;
  114. /**
  115. * This class holds a reference to various browser related objects injected by
  116. * set methods.
  117. */
  118. class Api {
  119. constructor(window) {
  120. this.window = window;
  121. if (!window) {
  122. throw ERROR_FACTORY.create("no window" /* ErrorCode.NO_WINDOW */);
  123. }
  124. this.performance = window.performance;
  125. this.PerformanceObserver = window.PerformanceObserver;
  126. this.windowLocation = window.location;
  127. this.navigator = window.navigator;
  128. this.document = window.document;
  129. if (this.navigator && this.navigator.cookieEnabled) {
  130. // If user blocks cookies on the browser, accessing localStorage will
  131. // throw an exception.
  132. this.localStorage = window.localStorage;
  133. }
  134. if (window.perfMetrics && window.perfMetrics.onFirstInputDelay) {
  135. this.onFirstInputDelay = window.perfMetrics.onFirstInputDelay;
  136. }
  137. }
  138. getUrl() {
  139. // Do not capture the string query part of url.
  140. return this.windowLocation.href.split('?')[0];
  141. }
  142. mark(name) {
  143. if (!this.performance || !this.performance.mark) {
  144. return;
  145. }
  146. this.performance.mark(name);
  147. }
  148. measure(measureName, mark1, mark2) {
  149. if (!this.performance || !this.performance.measure) {
  150. return;
  151. }
  152. this.performance.measure(measureName, mark1, mark2);
  153. }
  154. getEntriesByType(type) {
  155. if (!this.performance || !this.performance.getEntriesByType) {
  156. return [];
  157. }
  158. return this.performance.getEntriesByType(type);
  159. }
  160. getEntriesByName(name) {
  161. if (!this.performance || !this.performance.getEntriesByName) {
  162. return [];
  163. }
  164. return this.performance.getEntriesByName(name);
  165. }
  166. getTimeOrigin() {
  167. // Polyfill the time origin with performance.timing.navigationStart.
  168. return (this.performance &&
  169. (this.performance.timeOrigin || this.performance.timing.navigationStart));
  170. }
  171. requiredApisAvailable() {
  172. if (!fetch || !Promise || !areCookiesEnabled()) {
  173. consoleLogger.info('Firebase Performance cannot start if browser does not support fetch and Promise or cookie is disabled.');
  174. return false;
  175. }
  176. if (!isIndexedDBAvailable()) {
  177. consoleLogger.info('IndexedDB is not supported by current browswer');
  178. return false;
  179. }
  180. return true;
  181. }
  182. setupObserver(entryType, callback) {
  183. if (!this.PerformanceObserver) {
  184. return;
  185. }
  186. const observer = new this.PerformanceObserver(list => {
  187. for (const entry of list.getEntries()) {
  188. // `entry` is a PerformanceEntry instance.
  189. callback(entry);
  190. }
  191. });
  192. // Start observing the entry types you care about.
  193. observer.observe({ entryTypes: [entryType] });
  194. }
  195. static getInstance() {
  196. if (apiInstance === undefined) {
  197. apiInstance = new Api(windowInstance);
  198. }
  199. return apiInstance;
  200. }
  201. }
  202. function setupApi(window) {
  203. windowInstance = window;
  204. }
  205. /**
  206. * @license
  207. * Copyright 2020 Google LLC
  208. *
  209. * Licensed under the Apache License, Version 2.0 (the "License");
  210. * you may not use this file except in compliance with the License.
  211. * You may obtain a copy of the License at
  212. *
  213. * http://www.apache.org/licenses/LICENSE-2.0
  214. *
  215. * Unless required by applicable law or agreed to in writing, software
  216. * distributed under the License is distributed on an "AS IS" BASIS,
  217. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  218. * See the License for the specific language governing permissions and
  219. * limitations under the License.
  220. */
  221. let iid;
  222. function getIidPromise(installationsService) {
  223. const iidPromise = installationsService.getId();
  224. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  225. iidPromise.then((iidVal) => {
  226. iid = iidVal;
  227. });
  228. return iidPromise;
  229. }
  230. // This method should be used after the iid is retrieved by getIidPromise method.
  231. function getIid() {
  232. return iid;
  233. }
  234. function getAuthTokenPromise(installationsService) {
  235. const authTokenPromise = installationsService.getToken();
  236. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  237. authTokenPromise.then((authTokenVal) => {
  238. });
  239. return authTokenPromise;
  240. }
  241. /**
  242. * @license
  243. * Copyright 2020 Google LLC
  244. *
  245. * Licensed under the Apache License, Version 2.0 (the "License");
  246. * you may not use this file except in compliance with the License.
  247. * You may obtain a copy of the License at
  248. *
  249. * http://www.apache.org/licenses/LICENSE-2.0
  250. *
  251. * Unless required by applicable law or agreed to in writing, software
  252. * distributed under the License is distributed on an "AS IS" BASIS,
  253. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  254. * See the License for the specific language governing permissions and
  255. * limitations under the License.
  256. */
  257. function mergeStrings(part1, part2) {
  258. const sizeDiff = part1.length - part2.length;
  259. if (sizeDiff < 0 || sizeDiff > 1) {
  260. throw ERROR_FACTORY.create("invalid String merger input" /* ErrorCode.INVALID_STRING_MERGER_PARAMETER */);
  261. }
  262. const resultArray = [];
  263. for (let i = 0; i < part1.length; i++) {
  264. resultArray.push(part1.charAt(i));
  265. if (part2.length > i) {
  266. resultArray.push(part2.charAt(i));
  267. }
  268. }
  269. return resultArray.join('');
  270. }
  271. /**
  272. * @license
  273. * Copyright 2019 Google LLC
  274. *
  275. * Licensed under the Apache License, Version 2.0 (the "License");
  276. * you may not use this file except in compliance with the License.
  277. * You may obtain a copy of the License at
  278. *
  279. * http://www.apache.org/licenses/LICENSE-2.0
  280. *
  281. * Unless required by applicable law or agreed to in writing, software
  282. * distributed under the License is distributed on an "AS IS" BASIS,
  283. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  284. * See the License for the specific language governing permissions and
  285. * limitations under the License.
  286. */
  287. let settingsServiceInstance;
  288. class SettingsService {
  289. constructor() {
  290. // The variable which controls logging of automatic traces and HTTP/S network monitoring.
  291. this.instrumentationEnabled = true;
  292. // The variable which controls logging of custom traces.
  293. this.dataCollectionEnabled = true;
  294. // Configuration flags set through remote config.
  295. this.loggingEnabled = false;
  296. // Sampling rate between 0 and 1.
  297. this.tracesSamplingRate = 1;
  298. this.networkRequestsSamplingRate = 1;
  299. // Address of logging service.
  300. this.logEndPointUrl = 'https://firebaselogging.googleapis.com/v0cc/log?format=json_proto';
  301. // Performance event transport endpoint URL which should be compatible with proto3.
  302. // New Address for transport service, not configurable via Remote Config.
  303. this.flTransportEndpointUrl = mergeStrings('hts/frbslgigp.ogepscmv/ieo/eaylg', 'tp:/ieaeogn-agolai.o/1frlglgc/o');
  304. this.transportKey = mergeStrings('AzSC8r6ReiGqFMyfvgow', 'Iayx0u-XT3vksVM-pIV');
  305. // Source type for performance event logs.
  306. this.logSource = 462;
  307. // Flags which control per session logging of traces and network requests.
  308. this.logTraceAfterSampling = false;
  309. this.logNetworkAfterSampling = false;
  310. // TTL of config retrieved from remote config in hours.
  311. this.configTimeToLive = 12;
  312. }
  313. getFlTransportFullUrl() {
  314. return this.flTransportEndpointUrl.concat('?key=', this.transportKey);
  315. }
  316. static getInstance() {
  317. if (settingsServiceInstance === undefined) {
  318. settingsServiceInstance = new SettingsService();
  319. }
  320. return settingsServiceInstance;
  321. }
  322. }
  323. /**
  324. * @license
  325. * Copyright 2020 Google LLC
  326. *
  327. * Licensed under the Apache License, Version 2.0 (the "License");
  328. * you may not use this file except in compliance with the License.
  329. * You may obtain a copy of the License at
  330. *
  331. * http://www.apache.org/licenses/LICENSE-2.0
  332. *
  333. * Unless required by applicable law or agreed to in writing, software
  334. * distributed under the License is distributed on an "AS IS" BASIS,
  335. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  336. * See the License for the specific language governing permissions and
  337. * limitations under the License.
  338. */
  339. var VisibilityState;
  340. (function (VisibilityState) {
  341. VisibilityState[VisibilityState["UNKNOWN"] = 0] = "UNKNOWN";
  342. VisibilityState[VisibilityState["VISIBLE"] = 1] = "VISIBLE";
  343. VisibilityState[VisibilityState["HIDDEN"] = 2] = "HIDDEN";
  344. })(VisibilityState || (VisibilityState = {}));
  345. const RESERVED_ATTRIBUTE_PREFIXES = ['firebase_', 'google_', 'ga_'];
  346. const ATTRIBUTE_FORMAT_REGEX = new RegExp('^[a-zA-Z]\\w*$');
  347. const MAX_ATTRIBUTE_NAME_LENGTH = 40;
  348. const MAX_ATTRIBUTE_VALUE_LENGTH = 100;
  349. function getServiceWorkerStatus() {
  350. const navigator = Api.getInstance().navigator;
  351. if (navigator === null || navigator === void 0 ? void 0 : navigator.serviceWorker) {
  352. if (navigator.serviceWorker.controller) {
  353. return 2 /* ServiceWorkerStatus.CONTROLLED */;
  354. }
  355. else {
  356. return 3 /* ServiceWorkerStatus.UNCONTROLLED */;
  357. }
  358. }
  359. else {
  360. return 1 /* ServiceWorkerStatus.UNSUPPORTED */;
  361. }
  362. }
  363. function getVisibilityState() {
  364. const document = Api.getInstance().document;
  365. const visibilityState = document.visibilityState;
  366. switch (visibilityState) {
  367. case 'visible':
  368. return VisibilityState.VISIBLE;
  369. case 'hidden':
  370. return VisibilityState.HIDDEN;
  371. default:
  372. return VisibilityState.UNKNOWN;
  373. }
  374. }
  375. function getEffectiveConnectionType() {
  376. const navigator = Api.getInstance().navigator;
  377. const navigatorConnection = navigator.connection;
  378. const effectiveType = navigatorConnection && navigatorConnection.effectiveType;
  379. switch (effectiveType) {
  380. case 'slow-2g':
  381. return 1 /* EffectiveConnectionType.CONNECTION_SLOW_2G */;
  382. case '2g':
  383. return 2 /* EffectiveConnectionType.CONNECTION_2G */;
  384. case '3g':
  385. return 3 /* EffectiveConnectionType.CONNECTION_3G */;
  386. case '4g':
  387. return 4 /* EffectiveConnectionType.CONNECTION_4G */;
  388. default:
  389. return 0 /* EffectiveConnectionType.UNKNOWN */;
  390. }
  391. }
  392. function isValidCustomAttributeName(name) {
  393. if (name.length === 0 || name.length > MAX_ATTRIBUTE_NAME_LENGTH) {
  394. return false;
  395. }
  396. const matchesReservedPrefix = RESERVED_ATTRIBUTE_PREFIXES.some(prefix => name.startsWith(prefix));
  397. return !matchesReservedPrefix && !!name.match(ATTRIBUTE_FORMAT_REGEX);
  398. }
  399. function isValidCustomAttributeValue(value) {
  400. return value.length !== 0 && value.length <= MAX_ATTRIBUTE_VALUE_LENGTH;
  401. }
  402. /**
  403. * @license
  404. * Copyright 2020 Google LLC
  405. *
  406. * Licensed under the Apache License, Version 2.0 (the "License");
  407. * you may not use this file except in compliance with the License.
  408. * You may obtain a copy of the License at
  409. *
  410. * http://www.apache.org/licenses/LICENSE-2.0
  411. *
  412. * Unless required by applicable law or agreed to in writing, software
  413. * distributed under the License is distributed on an "AS IS" BASIS,
  414. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  415. * See the License for the specific language governing permissions and
  416. * limitations under the License.
  417. */
  418. function getAppId(firebaseApp) {
  419. var _a;
  420. const appId = (_a = firebaseApp.options) === null || _a === void 0 ? void 0 : _a.appId;
  421. if (!appId) {
  422. throw ERROR_FACTORY.create("no app id" /* ErrorCode.NO_APP_ID */);
  423. }
  424. return appId;
  425. }
  426. function getProjectId(firebaseApp) {
  427. var _a;
  428. const projectId = (_a = firebaseApp.options) === null || _a === void 0 ? void 0 : _a.projectId;
  429. if (!projectId) {
  430. throw ERROR_FACTORY.create("no project id" /* ErrorCode.NO_PROJECT_ID */);
  431. }
  432. return projectId;
  433. }
  434. function getApiKey(firebaseApp) {
  435. var _a;
  436. const apiKey = (_a = firebaseApp.options) === null || _a === void 0 ? void 0 : _a.apiKey;
  437. if (!apiKey) {
  438. throw ERROR_FACTORY.create("no api key" /* ErrorCode.NO_API_KEY */);
  439. }
  440. return apiKey;
  441. }
  442. /**
  443. * @license
  444. * Copyright 2020 Google LLC
  445. *
  446. * Licensed under the Apache License, Version 2.0 (the "License");
  447. * you may not use this file except in compliance with the License.
  448. * You may obtain a copy of the License at
  449. *
  450. * http://www.apache.org/licenses/LICENSE-2.0
  451. *
  452. * Unless required by applicable law or agreed to in writing, software
  453. * distributed under the License is distributed on an "AS IS" BASIS,
  454. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  455. * See the License for the specific language governing permissions and
  456. * limitations under the License.
  457. */
  458. const REMOTE_CONFIG_SDK_VERSION = '0.0.1';
  459. // These values will be used if the remote config object is successfully
  460. // retrieved, but the template does not have these fields.
  461. const DEFAULT_CONFIGS = {
  462. loggingEnabled: true
  463. };
  464. const FIS_AUTH_PREFIX = 'FIREBASE_INSTALLATIONS_AUTH';
  465. function getConfig(performanceController, iid) {
  466. const config = getStoredConfig();
  467. if (config) {
  468. processConfig(config);
  469. return Promise.resolve();
  470. }
  471. return getRemoteConfig(performanceController, iid)
  472. .then(processConfig)
  473. .then(config => storeConfig(config),
  474. /** Do nothing for error, use defaults set in settings service. */
  475. () => { });
  476. }
  477. function getStoredConfig() {
  478. const localStorage = Api.getInstance().localStorage;
  479. if (!localStorage) {
  480. return;
  481. }
  482. const expiryString = localStorage.getItem(CONFIG_EXPIRY_LOCAL_STORAGE_KEY);
  483. if (!expiryString || !configValid(expiryString)) {
  484. return;
  485. }
  486. const configStringified = localStorage.getItem(CONFIG_LOCAL_STORAGE_KEY);
  487. if (!configStringified) {
  488. return;
  489. }
  490. try {
  491. const configResponse = JSON.parse(configStringified);
  492. return configResponse;
  493. }
  494. catch (_a) {
  495. return;
  496. }
  497. }
  498. function storeConfig(config) {
  499. const localStorage = Api.getInstance().localStorage;
  500. if (!config || !localStorage) {
  501. return;
  502. }
  503. localStorage.setItem(CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config));
  504. localStorage.setItem(CONFIG_EXPIRY_LOCAL_STORAGE_KEY, String(Date.now() +
  505. SettingsService.getInstance().configTimeToLive * 60 * 60 * 1000));
  506. }
  507. const COULD_NOT_GET_CONFIG_MSG = 'Could not fetch config, will use default configs';
  508. function getRemoteConfig(performanceController, iid) {
  509. // Perf needs auth token only to retrieve remote config.
  510. return getAuthTokenPromise(performanceController.installations)
  511. .then(authToken => {
  512. const projectId = getProjectId(performanceController.app);
  513. const apiKey = getApiKey(performanceController.app);
  514. const configEndPoint = `https://firebaseremoteconfig.googleapis.com/v1/projects/${projectId}/namespaces/fireperf:fetch?key=${apiKey}`;
  515. const request = new Request(configEndPoint, {
  516. method: 'POST',
  517. headers: { Authorization: `${FIS_AUTH_PREFIX} ${authToken}` },
  518. /* eslint-disable camelcase */
  519. body: JSON.stringify({
  520. app_instance_id: iid,
  521. app_instance_id_token: authToken,
  522. app_id: getAppId(performanceController.app),
  523. app_version: SDK_VERSION,
  524. sdk_version: REMOTE_CONFIG_SDK_VERSION
  525. })
  526. /* eslint-enable camelcase */
  527. });
  528. return fetch(request).then(response => {
  529. if (response.ok) {
  530. return response.json();
  531. }
  532. // In case response is not ok. This will be caught by catch.
  533. throw ERROR_FACTORY.create("RC response not ok" /* ErrorCode.RC_NOT_OK */);
  534. });
  535. })
  536. .catch(() => {
  537. consoleLogger.info(COULD_NOT_GET_CONFIG_MSG);
  538. return undefined;
  539. });
  540. }
  541. /**
  542. * Processes config coming either from calling RC or from local storage.
  543. * This method only runs if call is successful or config in storage
  544. * is valid.
  545. */
  546. function processConfig(config) {
  547. if (!config) {
  548. return config;
  549. }
  550. const settingsServiceInstance = SettingsService.getInstance();
  551. const entries = config.entries || {};
  552. if (entries.fpr_enabled !== undefined) {
  553. // TODO: Change the assignment of loggingEnabled once the received type is
  554. // known.
  555. settingsServiceInstance.loggingEnabled =
  556. String(entries.fpr_enabled) === 'true';
  557. }
  558. else {
  559. // Config retrieved successfully, but there is no fpr_enabled in template.
  560. // Use secondary configs value.
  561. settingsServiceInstance.loggingEnabled = DEFAULT_CONFIGS.loggingEnabled;
  562. }
  563. if (entries.fpr_log_source) {
  564. settingsServiceInstance.logSource = Number(entries.fpr_log_source);
  565. }
  566. else if (DEFAULT_CONFIGS.logSource) {
  567. settingsServiceInstance.logSource = DEFAULT_CONFIGS.logSource;
  568. }
  569. if (entries.fpr_log_endpoint_url) {
  570. settingsServiceInstance.logEndPointUrl = entries.fpr_log_endpoint_url;
  571. }
  572. else if (DEFAULT_CONFIGS.logEndPointUrl) {
  573. settingsServiceInstance.logEndPointUrl = DEFAULT_CONFIGS.logEndPointUrl;
  574. }
  575. // Key from Remote Config has to be non-empty string, otherwsie use local value.
  576. if (entries.fpr_log_transport_key) {
  577. settingsServiceInstance.transportKey = entries.fpr_log_transport_key;
  578. }
  579. else if (DEFAULT_CONFIGS.transportKey) {
  580. settingsServiceInstance.transportKey = DEFAULT_CONFIGS.transportKey;
  581. }
  582. if (entries.fpr_vc_network_request_sampling_rate !== undefined) {
  583. settingsServiceInstance.networkRequestsSamplingRate = Number(entries.fpr_vc_network_request_sampling_rate);
  584. }
  585. else if (DEFAULT_CONFIGS.networkRequestsSamplingRate !== undefined) {
  586. settingsServiceInstance.networkRequestsSamplingRate =
  587. DEFAULT_CONFIGS.networkRequestsSamplingRate;
  588. }
  589. if (entries.fpr_vc_trace_sampling_rate !== undefined) {
  590. settingsServiceInstance.tracesSamplingRate = Number(entries.fpr_vc_trace_sampling_rate);
  591. }
  592. else if (DEFAULT_CONFIGS.tracesSamplingRate !== undefined) {
  593. settingsServiceInstance.tracesSamplingRate =
  594. DEFAULT_CONFIGS.tracesSamplingRate;
  595. }
  596. // Set the per session trace and network logging flags.
  597. settingsServiceInstance.logTraceAfterSampling = shouldLogAfterSampling(settingsServiceInstance.tracesSamplingRate);
  598. settingsServiceInstance.logNetworkAfterSampling = shouldLogAfterSampling(settingsServiceInstance.networkRequestsSamplingRate);
  599. return config;
  600. }
  601. function configValid(expiry) {
  602. return Number(expiry) > Date.now();
  603. }
  604. function shouldLogAfterSampling(samplingRate) {
  605. return Math.random() <= samplingRate;
  606. }
  607. /**
  608. * @license
  609. * Copyright 2020 Google LLC
  610. *
  611. * Licensed under the Apache License, Version 2.0 (the "License");
  612. * you may not use this file except in compliance with the License.
  613. * You may obtain a copy of the License at
  614. *
  615. * http://www.apache.org/licenses/LICENSE-2.0
  616. *
  617. * Unless required by applicable law or agreed to in writing, software
  618. * distributed under the License is distributed on an "AS IS" BASIS,
  619. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  620. * See the License for the specific language governing permissions and
  621. * limitations under the License.
  622. */
  623. let initializationStatus = 1 /* InitializationStatus.notInitialized */;
  624. let initializationPromise;
  625. function getInitializationPromise(performanceController) {
  626. initializationStatus = 2 /* InitializationStatus.initializationPending */;
  627. initializationPromise =
  628. initializationPromise || initializePerf(performanceController);
  629. return initializationPromise;
  630. }
  631. function isPerfInitialized() {
  632. return initializationStatus === 3 /* InitializationStatus.initialized */;
  633. }
  634. function initializePerf(performanceController) {
  635. return getDocumentReadyComplete()
  636. .then(() => getIidPromise(performanceController.installations))
  637. .then(iid => getConfig(performanceController, iid))
  638. .then(() => changeInitializationStatus(), () => changeInitializationStatus());
  639. }
  640. /**
  641. * Returns a promise which resolves whenever the document readystate is complete or
  642. * immediately if it is called after page load complete.
  643. */
  644. function getDocumentReadyComplete() {
  645. const document = Api.getInstance().document;
  646. return new Promise(resolve => {
  647. if (document && document.readyState !== 'complete') {
  648. const handler = () => {
  649. if (document.readyState === 'complete') {
  650. document.removeEventListener('readystatechange', handler);
  651. resolve();
  652. }
  653. };
  654. document.addEventListener('readystatechange', handler);
  655. }
  656. else {
  657. resolve();
  658. }
  659. });
  660. }
  661. function changeInitializationStatus() {
  662. initializationStatus = 3 /* InitializationStatus.initialized */;
  663. }
  664. /**
  665. * @license
  666. * Copyright 2020 Google LLC
  667. *
  668. * Licensed under the Apache License, Version 2.0 (the "License");
  669. * you may not use this file except in compliance with the License.
  670. * You may obtain a copy of the License at
  671. *
  672. * http://www.apache.org/licenses/LICENSE-2.0
  673. *
  674. * Unless required by applicable law or agreed to in writing, software
  675. * distributed under the License is distributed on an "AS IS" BASIS,
  676. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  677. * See the License for the specific language governing permissions and
  678. * limitations under the License.
  679. */
  680. const DEFAULT_SEND_INTERVAL_MS = 10 * 1000;
  681. const INITIAL_SEND_TIME_DELAY_MS = 5.5 * 1000;
  682. // If end point does not work, the call will be tried for these many times.
  683. const DEFAULT_REMAINING_TRIES = 3;
  684. const MAX_EVENT_COUNT_PER_REQUEST = 1000;
  685. let remainingTries = DEFAULT_REMAINING_TRIES;
  686. /* eslint-enable camelcase */
  687. let queue = [];
  688. let isTransportSetup = false;
  689. function setupTransportService() {
  690. if (!isTransportSetup) {
  691. processQueue(INITIAL_SEND_TIME_DELAY_MS);
  692. isTransportSetup = true;
  693. }
  694. }
  695. function processQueue(timeOffset) {
  696. setTimeout(() => {
  697. // If there is no remainingTries left, stop retrying.
  698. if (remainingTries === 0) {
  699. return;
  700. }
  701. // If there are no events to process, wait for DEFAULT_SEND_INTERVAL_MS and try again.
  702. if (!queue.length) {
  703. return processQueue(DEFAULT_SEND_INTERVAL_MS);
  704. }
  705. dispatchQueueEvents();
  706. }, timeOffset);
  707. }
  708. function dispatchQueueEvents() {
  709. // Extract events up to the maximum cap of single logRequest from top of "official queue".
  710. // The staged events will be used for current logRequest attempt, remaining events will be kept
  711. // for next attempt.
  712. const staged = queue.splice(0, MAX_EVENT_COUNT_PER_REQUEST);
  713. /* eslint-disable camelcase */
  714. // We will pass the JSON serialized event to the backend.
  715. const log_event = staged.map(evt => ({
  716. source_extension_json_proto3: evt.message,
  717. event_time_ms: String(evt.eventTime)
  718. }));
  719. const data = {
  720. request_time_ms: String(Date.now()),
  721. client_info: {
  722. client_type: 1,
  723. js_client_info: {}
  724. },
  725. log_source: SettingsService.getInstance().logSource,
  726. log_event
  727. };
  728. /* eslint-enable camelcase */
  729. sendEventsToFl(data, staged).catch(() => {
  730. // If the request fails for some reason, add the events that were attempted
  731. // back to the primary queue to retry later.
  732. queue = [...staged, ...queue];
  733. remainingTries--;
  734. consoleLogger.info(`Tries left: ${remainingTries}.`);
  735. processQueue(DEFAULT_SEND_INTERVAL_MS);
  736. });
  737. }
  738. function sendEventsToFl(data, staged) {
  739. return postToFlEndpoint(data)
  740. .then(res => {
  741. if (!res.ok) {
  742. consoleLogger.info('Call to Firebase backend failed.');
  743. }
  744. return res.json();
  745. })
  746. .then(res => {
  747. // Find the next call wait time from the response.
  748. const transportWait = Number(res.nextRequestWaitMillis);
  749. let requestOffset = DEFAULT_SEND_INTERVAL_MS;
  750. if (!isNaN(transportWait)) {
  751. requestOffset = Math.max(transportWait, requestOffset);
  752. }
  753. // Delete request if response include RESPONSE_ACTION_UNKNOWN or DELETE_REQUEST action.
  754. // Otherwise, retry request using normal scheduling if response include RETRY_REQUEST_LATER.
  755. const logResponseDetails = res.logResponseDetails;
  756. if (Array.isArray(logResponseDetails) &&
  757. logResponseDetails.length > 0 &&
  758. logResponseDetails[0].responseAction === 'RETRY_REQUEST_LATER') {
  759. queue = [...staged, ...queue];
  760. consoleLogger.info(`Retry transport request later.`);
  761. }
  762. remainingTries = DEFAULT_REMAINING_TRIES;
  763. // Schedule the next process.
  764. processQueue(requestOffset);
  765. });
  766. }
  767. function postToFlEndpoint(data) {
  768. const flTransportFullUrl = SettingsService.getInstance().getFlTransportFullUrl();
  769. return fetch(flTransportFullUrl, {
  770. method: 'POST',
  771. body: JSON.stringify(data)
  772. });
  773. }
  774. function addToQueue(evt) {
  775. if (!evt.eventTime || !evt.message) {
  776. throw ERROR_FACTORY.create("invalid cc log" /* ErrorCode.INVALID_CC_LOG */);
  777. }
  778. // Add the new event to the queue.
  779. queue = [...queue, evt];
  780. }
  781. /** Log handler for cc service to send the performance logs to the server. */
  782. function transportHandler(
  783. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  784. serializer) {
  785. return (...args) => {
  786. const message = serializer(...args);
  787. addToQueue({
  788. message,
  789. eventTime: Date.now()
  790. });
  791. };
  792. }
  793. /**
  794. * @license
  795. * Copyright 2020 Google LLC
  796. *
  797. * Licensed under the Apache License, Version 2.0 (the "License");
  798. * you may not use this file except in compliance with the License.
  799. * You may obtain a copy of the License at
  800. *
  801. * http://www.apache.org/licenses/LICENSE-2.0
  802. *
  803. * Unless required by applicable law or agreed to in writing, software
  804. * distributed under the License is distributed on an "AS IS" BASIS,
  805. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  806. * See the License for the specific language governing permissions and
  807. * limitations under the License.
  808. */
  809. /* eslint-enble camelcase */
  810. let logger;
  811. // This method is not called before initialization.
  812. function sendLog(resource, resourceType) {
  813. if (!logger) {
  814. logger = transportHandler(serializer);
  815. }
  816. logger(resource, resourceType);
  817. }
  818. function logTrace(trace) {
  819. const settingsService = SettingsService.getInstance();
  820. // Do not log if trace is auto generated and instrumentation is disabled.
  821. if (!settingsService.instrumentationEnabled && trace.isAuto) {
  822. return;
  823. }
  824. // Do not log if trace is custom and data collection is disabled.
  825. if (!settingsService.dataCollectionEnabled && !trace.isAuto) {
  826. return;
  827. }
  828. // Do not log if required apis are not available.
  829. if (!Api.getInstance().requiredApisAvailable()) {
  830. return;
  831. }
  832. // Only log the page load auto traces if page is visible.
  833. if (trace.isAuto && getVisibilityState() !== VisibilityState.VISIBLE) {
  834. return;
  835. }
  836. if (isPerfInitialized()) {
  837. sendTraceLog(trace);
  838. }
  839. else {
  840. // Custom traces can be used before the initialization but logging
  841. // should wait until after.
  842. getInitializationPromise(trace.performanceController).then(() => sendTraceLog(trace), () => sendTraceLog(trace));
  843. }
  844. }
  845. function sendTraceLog(trace) {
  846. if (!getIid()) {
  847. return;
  848. }
  849. const settingsService = SettingsService.getInstance();
  850. if (!settingsService.loggingEnabled ||
  851. !settingsService.logTraceAfterSampling) {
  852. return;
  853. }
  854. setTimeout(() => sendLog(trace, 1 /* ResourceType.Trace */), 0);
  855. }
  856. function logNetworkRequest(networkRequest) {
  857. const settingsService = SettingsService.getInstance();
  858. // Do not log network requests if instrumentation is disabled.
  859. if (!settingsService.instrumentationEnabled) {
  860. return;
  861. }
  862. // Do not log the js sdk's call to transport service domain to avoid unnecessary cycle.
  863. // Need to blacklist both old and new endpoints to avoid migration gap.
  864. const networkRequestUrl = networkRequest.url;
  865. // Blacklist old log endpoint and new transport endpoint.
  866. // Because Performance SDK doesn't instrument requests sent from SDK itself.
  867. const logEndpointUrl = settingsService.logEndPointUrl.split('?')[0];
  868. const flEndpointUrl = settingsService.flTransportEndpointUrl.split('?')[0];
  869. if (networkRequestUrl === logEndpointUrl ||
  870. networkRequestUrl === flEndpointUrl) {
  871. return;
  872. }
  873. if (!settingsService.loggingEnabled ||
  874. !settingsService.logNetworkAfterSampling) {
  875. return;
  876. }
  877. setTimeout(() => sendLog(networkRequest, 0 /* ResourceType.NetworkRequest */), 0);
  878. }
  879. function serializer(resource, resourceType) {
  880. if (resourceType === 0 /* ResourceType.NetworkRequest */) {
  881. return serializeNetworkRequest(resource);
  882. }
  883. return serializeTrace(resource);
  884. }
  885. function serializeNetworkRequest(networkRequest) {
  886. const networkRequestMetric = {
  887. url: networkRequest.url,
  888. http_method: networkRequest.httpMethod || 0,
  889. http_response_code: 200,
  890. response_payload_bytes: networkRequest.responsePayloadBytes,
  891. client_start_time_us: networkRequest.startTimeUs,
  892. time_to_response_initiated_us: networkRequest.timeToResponseInitiatedUs,
  893. time_to_response_completed_us: networkRequest.timeToResponseCompletedUs
  894. };
  895. const perfMetric = {
  896. application_info: getApplicationInfo(networkRequest.performanceController.app),
  897. network_request_metric: networkRequestMetric
  898. };
  899. return JSON.stringify(perfMetric);
  900. }
  901. function serializeTrace(trace) {
  902. const traceMetric = {
  903. name: trace.name,
  904. is_auto: trace.isAuto,
  905. client_start_time_us: trace.startTimeUs,
  906. duration_us: trace.durationUs
  907. };
  908. if (Object.keys(trace.counters).length !== 0) {
  909. traceMetric.counters = trace.counters;
  910. }
  911. const customAttributes = trace.getAttributes();
  912. if (Object.keys(customAttributes).length !== 0) {
  913. traceMetric.custom_attributes = customAttributes;
  914. }
  915. const perfMetric = {
  916. application_info: getApplicationInfo(trace.performanceController.app),
  917. trace_metric: traceMetric
  918. };
  919. return JSON.stringify(perfMetric);
  920. }
  921. function getApplicationInfo(firebaseApp) {
  922. return {
  923. google_app_id: getAppId(firebaseApp),
  924. app_instance_id: getIid(),
  925. web_app_info: {
  926. sdk_version: SDK_VERSION,
  927. page_url: Api.getInstance().getUrl(),
  928. service_worker_status: getServiceWorkerStatus(),
  929. visibility_state: getVisibilityState(),
  930. effective_connection_type: getEffectiveConnectionType()
  931. },
  932. application_process_state: 0
  933. };
  934. }
  935. /**
  936. * @license
  937. * Copyright 2020 Google LLC
  938. *
  939. * Licensed under the Apache License, Version 2.0 (the "License");
  940. * you may not use this file except in compliance with the License.
  941. * You may obtain a copy of the License at
  942. *
  943. * http://www.apache.org/licenses/LICENSE-2.0
  944. *
  945. * Unless required by applicable law or agreed to in writing, software
  946. * distributed under the License is distributed on an "AS IS" BASIS,
  947. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  948. * See the License for the specific language governing permissions and
  949. * limitations under the License.
  950. */
  951. const MAX_METRIC_NAME_LENGTH = 100;
  952. const RESERVED_AUTO_PREFIX = '_';
  953. const oobMetrics = [
  954. FIRST_PAINT_COUNTER_NAME,
  955. FIRST_CONTENTFUL_PAINT_COUNTER_NAME,
  956. FIRST_INPUT_DELAY_COUNTER_NAME
  957. ];
  958. /**
  959. * Returns true if the metric is custom and does not start with reserved prefix, or if
  960. * the metric is one of out of the box page load trace metrics.
  961. */
  962. function isValidMetricName(name, traceName) {
  963. if (name.length === 0 || name.length > MAX_METRIC_NAME_LENGTH) {
  964. return false;
  965. }
  966. return ((traceName &&
  967. traceName.startsWith(OOB_TRACE_PAGE_LOAD_PREFIX) &&
  968. oobMetrics.indexOf(name) > -1) ||
  969. !name.startsWith(RESERVED_AUTO_PREFIX));
  970. }
  971. /**
  972. * Converts the provided value to an integer value to be used in case of a metric.
  973. * @param providedValue Provided number value of the metric that needs to be converted to an integer.
  974. *
  975. * @returns Converted integer number to be set for the metric.
  976. */
  977. function convertMetricValueToInteger(providedValue) {
  978. const valueAsInteger = Math.floor(providedValue);
  979. if (valueAsInteger < providedValue) {
  980. consoleLogger.info(`Metric value should be an Integer, setting the value as : ${valueAsInteger}.`);
  981. }
  982. return valueAsInteger;
  983. }
  984. /**
  985. * @license
  986. * Copyright 2020 Google LLC
  987. *
  988. * Licensed under the Apache License, Version 2.0 (the "License");
  989. * you may not use this file except in compliance with the License.
  990. * You may obtain a copy of the License at
  991. *
  992. * http://www.apache.org/licenses/LICENSE-2.0
  993. *
  994. * Unless required by applicable law or agreed to in writing, software
  995. * distributed under the License is distributed on an "AS IS" BASIS,
  996. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  997. * See the License for the specific language governing permissions and
  998. * limitations under the License.
  999. */
  1000. class Trace {
  1001. /**
  1002. * @param performanceController The performance controller running.
  1003. * @param name The name of the trace.
  1004. * @param isAuto If the trace is auto-instrumented.
  1005. * @param traceMeasureName The name of the measure marker in user timing specification. This field
  1006. * is only set when the trace is built for logging when the user directly uses the user timing
  1007. * api (performance.mark and performance.measure).
  1008. */
  1009. constructor(performanceController, name, isAuto = false, traceMeasureName) {
  1010. this.performanceController = performanceController;
  1011. this.name = name;
  1012. this.isAuto = isAuto;
  1013. this.state = 1 /* TraceState.UNINITIALIZED */;
  1014. this.customAttributes = {};
  1015. this.counters = {};
  1016. this.api = Api.getInstance();
  1017. this.randomId = Math.floor(Math.random() * 1000000);
  1018. if (!this.isAuto) {
  1019. this.traceStartMark = `${TRACE_START_MARK_PREFIX}-${this.randomId}-${this.name}`;
  1020. this.traceStopMark = `${TRACE_STOP_MARK_PREFIX}-${this.randomId}-${this.name}`;
  1021. this.traceMeasure =
  1022. traceMeasureName ||
  1023. `${TRACE_MEASURE_PREFIX}-${this.randomId}-${this.name}`;
  1024. if (traceMeasureName) {
  1025. // For the case of direct user timing traces, no start stop will happen. The measure object
  1026. // is already available.
  1027. this.calculateTraceMetrics();
  1028. }
  1029. }
  1030. }
  1031. /**
  1032. * Starts a trace. The measurement of the duration starts at this point.
  1033. */
  1034. start() {
  1035. if (this.state !== 1 /* TraceState.UNINITIALIZED */) {
  1036. throw ERROR_FACTORY.create("trace started" /* ErrorCode.TRACE_STARTED_BEFORE */, {
  1037. traceName: this.name
  1038. });
  1039. }
  1040. this.api.mark(this.traceStartMark);
  1041. this.state = 2 /* TraceState.RUNNING */;
  1042. }
  1043. /**
  1044. * Stops the trace. The measurement of the duration of the trace stops at this point and trace
  1045. * is logged.
  1046. */
  1047. stop() {
  1048. if (this.state !== 2 /* TraceState.RUNNING */) {
  1049. throw ERROR_FACTORY.create("trace stopped" /* ErrorCode.TRACE_STOPPED_BEFORE */, {
  1050. traceName: this.name
  1051. });
  1052. }
  1053. this.state = 3 /* TraceState.TERMINATED */;
  1054. this.api.mark(this.traceStopMark);
  1055. this.api.measure(this.traceMeasure, this.traceStartMark, this.traceStopMark);
  1056. this.calculateTraceMetrics();
  1057. logTrace(this);
  1058. }
  1059. /**
  1060. * Records a trace with predetermined values. If this method is used a trace is created and logged
  1061. * directly. No need to use start and stop methods.
  1062. * @param startTime Trace start time since epoch in millisec
  1063. * @param duration The duraction of the trace in millisec
  1064. * @param options An object which can optionally hold maps of custom metrics and custom attributes
  1065. */
  1066. record(startTime, duration, options) {
  1067. if (startTime <= 0) {
  1068. throw ERROR_FACTORY.create("nonpositive trace startTime" /* ErrorCode.NONPOSITIVE_TRACE_START_TIME */, {
  1069. traceName: this.name
  1070. });
  1071. }
  1072. if (duration <= 0) {
  1073. throw ERROR_FACTORY.create("nonpositive trace duration" /* ErrorCode.NONPOSITIVE_TRACE_DURATION */, {
  1074. traceName: this.name
  1075. });
  1076. }
  1077. this.durationUs = Math.floor(duration * 1000);
  1078. this.startTimeUs = Math.floor(startTime * 1000);
  1079. if (options && options.attributes) {
  1080. this.customAttributes = Object.assign({}, options.attributes);
  1081. }
  1082. if (options && options.metrics) {
  1083. for (const metricName of Object.keys(options.metrics)) {
  1084. if (!isNaN(Number(options.metrics[metricName]))) {
  1085. this.counters[metricName] = Math.floor(Number(options.metrics[metricName]));
  1086. }
  1087. }
  1088. }
  1089. logTrace(this);
  1090. }
  1091. /**
  1092. * Increments a custom metric by a certain number or 1 if number not specified. Will create a new
  1093. * custom metric if one with the given name does not exist. The value will be floored down to an
  1094. * integer.
  1095. * @param counter Name of the custom metric
  1096. * @param numAsInteger Increment by value
  1097. */
  1098. incrementMetric(counter, numAsInteger = 1) {
  1099. if (this.counters[counter] === undefined) {
  1100. this.putMetric(counter, numAsInteger);
  1101. }
  1102. else {
  1103. this.putMetric(counter, this.counters[counter] + numAsInteger);
  1104. }
  1105. }
  1106. /**
  1107. * Sets a custom metric to a specified value. Will create a new custom metric if one with the
  1108. * given name does not exist. The value will be floored down to an integer.
  1109. * @param counter Name of the custom metric
  1110. * @param numAsInteger Set custom metric to this value
  1111. */
  1112. putMetric(counter, numAsInteger) {
  1113. if (isValidMetricName(counter, this.name)) {
  1114. this.counters[counter] = convertMetricValueToInteger(numAsInteger !== null && numAsInteger !== void 0 ? numAsInteger : 0);
  1115. }
  1116. else {
  1117. throw ERROR_FACTORY.create("invalid custom metric name" /* ErrorCode.INVALID_CUSTOM_METRIC_NAME */, {
  1118. customMetricName: counter
  1119. });
  1120. }
  1121. }
  1122. /**
  1123. * Returns the value of the custom metric by that name. If a custom metric with that name does
  1124. * not exist will return zero.
  1125. * @param counter
  1126. */
  1127. getMetric(counter) {
  1128. return this.counters[counter] || 0;
  1129. }
  1130. /**
  1131. * Sets a custom attribute of a trace to a certain value.
  1132. * @param attr
  1133. * @param value
  1134. */
  1135. putAttribute(attr, value) {
  1136. const isValidName = isValidCustomAttributeName(attr);
  1137. const isValidValue = isValidCustomAttributeValue(value);
  1138. if (isValidName && isValidValue) {
  1139. this.customAttributes[attr] = value;
  1140. return;
  1141. }
  1142. // Throw appropriate error when the attribute name or value is invalid.
  1143. if (!isValidName) {
  1144. throw ERROR_FACTORY.create("invalid attribute name" /* ErrorCode.INVALID_ATTRIBUTE_NAME */, {
  1145. attributeName: attr
  1146. });
  1147. }
  1148. if (!isValidValue) {
  1149. throw ERROR_FACTORY.create("invalid attribute value" /* ErrorCode.INVALID_ATTRIBUTE_VALUE */, {
  1150. attributeValue: value
  1151. });
  1152. }
  1153. }
  1154. /**
  1155. * Retrieves the value a custom attribute of a trace is set to.
  1156. * @param attr
  1157. */
  1158. getAttribute(attr) {
  1159. return this.customAttributes[attr];
  1160. }
  1161. removeAttribute(attr) {
  1162. if (this.customAttributes[attr] === undefined) {
  1163. return;
  1164. }
  1165. delete this.customAttributes[attr];
  1166. }
  1167. getAttributes() {
  1168. return Object.assign({}, this.customAttributes);
  1169. }
  1170. setStartTime(startTime) {
  1171. this.startTimeUs = startTime;
  1172. }
  1173. setDuration(duration) {
  1174. this.durationUs = duration;
  1175. }
  1176. /**
  1177. * Calculates and assigns the duration and start time of the trace using the measure performance
  1178. * entry.
  1179. */
  1180. calculateTraceMetrics() {
  1181. const perfMeasureEntries = this.api.getEntriesByName(this.traceMeasure);
  1182. const perfMeasureEntry = perfMeasureEntries && perfMeasureEntries[0];
  1183. if (perfMeasureEntry) {
  1184. this.durationUs = Math.floor(perfMeasureEntry.duration * 1000);
  1185. this.startTimeUs = Math.floor((perfMeasureEntry.startTime + this.api.getTimeOrigin()) * 1000);
  1186. }
  1187. }
  1188. /**
  1189. * @param navigationTimings A single element array which contains the navigationTIming object of
  1190. * the page load
  1191. * @param paintTimings A array which contains paintTiming object of the page load
  1192. * @param firstInputDelay First input delay in millisec
  1193. */
  1194. static createOobTrace(performanceController, navigationTimings, paintTimings, firstInputDelay) {
  1195. const route = Api.getInstance().getUrl();
  1196. if (!route) {
  1197. return;
  1198. }
  1199. const trace = new Trace(performanceController, OOB_TRACE_PAGE_LOAD_PREFIX + route, true);
  1200. const timeOriginUs = Math.floor(Api.getInstance().getTimeOrigin() * 1000);
  1201. trace.setStartTime(timeOriginUs);
  1202. // navigationTimings includes only one element.
  1203. if (navigationTimings && navigationTimings[0]) {
  1204. trace.setDuration(Math.floor(navigationTimings[0].duration * 1000));
  1205. trace.putMetric('domInteractive', Math.floor(navigationTimings[0].domInteractive * 1000));
  1206. trace.putMetric('domContentLoadedEventEnd', Math.floor(navigationTimings[0].domContentLoadedEventEnd * 1000));
  1207. trace.putMetric('loadEventEnd', Math.floor(navigationTimings[0].loadEventEnd * 1000));
  1208. }
  1209. const FIRST_PAINT = 'first-paint';
  1210. const FIRST_CONTENTFUL_PAINT = 'first-contentful-paint';
  1211. if (paintTimings) {
  1212. const firstPaint = paintTimings.find(paintObject => paintObject.name === FIRST_PAINT);
  1213. if (firstPaint && firstPaint.startTime) {
  1214. trace.putMetric(FIRST_PAINT_COUNTER_NAME, Math.floor(firstPaint.startTime * 1000));
  1215. }
  1216. const firstContentfulPaint = paintTimings.find(paintObject => paintObject.name === FIRST_CONTENTFUL_PAINT);
  1217. if (firstContentfulPaint && firstContentfulPaint.startTime) {
  1218. trace.putMetric(FIRST_CONTENTFUL_PAINT_COUNTER_NAME, Math.floor(firstContentfulPaint.startTime * 1000));
  1219. }
  1220. if (firstInputDelay) {
  1221. trace.putMetric(FIRST_INPUT_DELAY_COUNTER_NAME, Math.floor(firstInputDelay * 1000));
  1222. }
  1223. }
  1224. logTrace(trace);
  1225. }
  1226. static createUserTimingTrace(performanceController, measureName) {
  1227. const trace = new Trace(performanceController, measureName, false, measureName);
  1228. logTrace(trace);
  1229. }
  1230. }
  1231. /**
  1232. * @license
  1233. * Copyright 2020 Google LLC
  1234. *
  1235. * Licensed under the Apache License, Version 2.0 (the "License");
  1236. * you may not use this file except in compliance with the License.
  1237. * You may obtain a copy of the License at
  1238. *
  1239. * http://www.apache.org/licenses/LICENSE-2.0
  1240. *
  1241. * Unless required by applicable law or agreed to in writing, software
  1242. * distributed under the License is distributed on an "AS IS" BASIS,
  1243. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1244. * See the License for the specific language governing permissions and
  1245. * limitations under the License.
  1246. */
  1247. function createNetworkRequestEntry(performanceController, entry) {
  1248. const performanceEntry = entry;
  1249. if (!performanceEntry || performanceEntry.responseStart === undefined) {
  1250. return;
  1251. }
  1252. const timeOrigin = Api.getInstance().getTimeOrigin();
  1253. const startTimeUs = Math.floor((performanceEntry.startTime + timeOrigin) * 1000);
  1254. const timeToResponseInitiatedUs = performanceEntry.responseStart
  1255. ? Math.floor((performanceEntry.responseStart - performanceEntry.startTime) * 1000)
  1256. : undefined;
  1257. const timeToResponseCompletedUs = Math.floor((performanceEntry.responseEnd - performanceEntry.startTime) * 1000);
  1258. // Remove the query params from logged network request url.
  1259. const url = performanceEntry.name && performanceEntry.name.split('?')[0];
  1260. const networkRequest = {
  1261. performanceController,
  1262. url,
  1263. responsePayloadBytes: performanceEntry.transferSize,
  1264. startTimeUs,
  1265. timeToResponseInitiatedUs,
  1266. timeToResponseCompletedUs
  1267. };
  1268. logNetworkRequest(networkRequest);
  1269. }
  1270. /**
  1271. * @license
  1272. * Copyright 2020 Google LLC
  1273. *
  1274. * Licensed under the Apache License, Version 2.0 (the "License");
  1275. * you may not use this file except in compliance with the License.
  1276. * You may obtain a copy of the License at
  1277. *
  1278. * http://www.apache.org/licenses/LICENSE-2.0
  1279. *
  1280. * Unless required by applicable law or agreed to in writing, software
  1281. * distributed under the License is distributed on an "AS IS" BASIS,
  1282. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1283. * See the License for the specific language governing permissions and
  1284. * limitations under the License.
  1285. */
  1286. const FID_WAIT_TIME_MS = 5000;
  1287. function setupOobResources(performanceController) {
  1288. // Do not initialize unless iid is available.
  1289. if (!getIid()) {
  1290. return;
  1291. }
  1292. // The load event might not have fired yet, and that means performance navigation timing
  1293. // object has a duration of 0. The setup should run after all current tasks in js queue.
  1294. setTimeout(() => setupOobTraces(performanceController), 0);
  1295. setTimeout(() => setupNetworkRequests(performanceController), 0);
  1296. setTimeout(() => setupUserTimingTraces(performanceController), 0);
  1297. }
  1298. function setupNetworkRequests(performanceController) {
  1299. const api = Api.getInstance();
  1300. const resources = api.getEntriesByType('resource');
  1301. for (const resource of resources) {
  1302. createNetworkRequestEntry(performanceController, resource);
  1303. }
  1304. api.setupObserver('resource', entry => createNetworkRequestEntry(performanceController, entry));
  1305. }
  1306. function setupOobTraces(performanceController) {
  1307. const api = Api.getInstance();
  1308. const navigationTimings = api.getEntriesByType('navigation');
  1309. const paintTimings = api.getEntriesByType('paint');
  1310. // If First Input Desly polyfill is added to the page, report the fid value.
  1311. // https://github.com/GoogleChromeLabs/first-input-delay
  1312. if (api.onFirstInputDelay) {
  1313. // If the fid call back is not called for certain time, continue without it.
  1314. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  1315. let timeoutId = setTimeout(() => {
  1316. Trace.createOobTrace(performanceController, navigationTimings, paintTimings);
  1317. timeoutId = undefined;
  1318. }, FID_WAIT_TIME_MS);
  1319. api.onFirstInputDelay((fid) => {
  1320. if (timeoutId) {
  1321. clearTimeout(timeoutId);
  1322. Trace.createOobTrace(performanceController, navigationTimings, paintTimings, fid);
  1323. }
  1324. });
  1325. }
  1326. else {
  1327. Trace.createOobTrace(performanceController, navigationTimings, paintTimings);
  1328. }
  1329. }
  1330. function setupUserTimingTraces(performanceController) {
  1331. const api = Api.getInstance();
  1332. // Run through the measure performance entries collected up to this point.
  1333. const measures = api.getEntriesByType('measure');
  1334. for (const measure of measures) {
  1335. createUserTimingTrace(performanceController, measure);
  1336. }
  1337. // Setup an observer to capture the measures from this point on.
  1338. api.setupObserver('measure', entry => createUserTimingTrace(performanceController, entry));
  1339. }
  1340. function createUserTimingTrace(performanceController, measure) {
  1341. const measureName = measure.name;
  1342. // Do not create a trace, if the user timing marks and measures are created by the sdk itself.
  1343. if (measureName.substring(0, TRACE_MEASURE_PREFIX.length) ===
  1344. TRACE_MEASURE_PREFIX) {
  1345. return;
  1346. }
  1347. Trace.createUserTimingTrace(performanceController, measureName);
  1348. }
  1349. /**
  1350. * @license
  1351. * Copyright 2020 Google LLC
  1352. *
  1353. * Licensed under the Apache License, Version 2.0 (the "License");
  1354. * you may not use this file except in compliance with the License.
  1355. * You may obtain a copy of the License at
  1356. *
  1357. * http://www.apache.org/licenses/LICENSE-2.0
  1358. *
  1359. * Unless required by applicable law or agreed to in writing, software
  1360. * distributed under the License is distributed on an "AS IS" BASIS,
  1361. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1362. * See the License for the specific language governing permissions and
  1363. * limitations under the License.
  1364. */
  1365. class PerformanceController {
  1366. constructor(app, installations) {
  1367. this.app = app;
  1368. this.installations = installations;
  1369. this.initialized = false;
  1370. }
  1371. /**
  1372. * This method *must* be called internally as part of creating a
  1373. * PerformanceController instance.
  1374. *
  1375. * Currently it's not possible to pass the settings object through the
  1376. * constructor using Components, so this method exists to be called with the
  1377. * desired settings, to ensure nothing is collected without the user's
  1378. * consent.
  1379. */
  1380. _init(settings) {
  1381. if (this.initialized) {
  1382. return;
  1383. }
  1384. if ((settings === null || settings === void 0 ? void 0 : settings.dataCollectionEnabled) !== undefined) {
  1385. this.dataCollectionEnabled = settings.dataCollectionEnabled;
  1386. }
  1387. if ((settings === null || settings === void 0 ? void 0 : settings.instrumentationEnabled) !== undefined) {
  1388. this.instrumentationEnabled = settings.instrumentationEnabled;
  1389. }
  1390. if (Api.getInstance().requiredApisAvailable()) {
  1391. validateIndexedDBOpenable()
  1392. .then(isAvailable => {
  1393. if (isAvailable) {
  1394. setupTransportService();
  1395. getInitializationPromise(this).then(() => setupOobResources(this), () => setupOobResources(this));
  1396. this.initialized = true;
  1397. }
  1398. })
  1399. .catch(error => {
  1400. consoleLogger.info(`Environment doesn't support IndexedDB: ${error}`);
  1401. });
  1402. }
  1403. else {
  1404. consoleLogger.info('Firebase Performance cannot start if the browser does not support ' +
  1405. '"Fetch" and "Promise", or cookies are disabled.');
  1406. }
  1407. }
  1408. set instrumentationEnabled(val) {
  1409. SettingsService.getInstance().instrumentationEnabled = val;
  1410. }
  1411. get instrumentationEnabled() {
  1412. return SettingsService.getInstance().instrumentationEnabled;
  1413. }
  1414. set dataCollectionEnabled(val) {
  1415. SettingsService.getInstance().dataCollectionEnabled = val;
  1416. }
  1417. get dataCollectionEnabled() {
  1418. return SettingsService.getInstance().dataCollectionEnabled;
  1419. }
  1420. }
  1421. /**
  1422. * Firebase Performance Monitoring
  1423. *
  1424. * @packageDocumentation
  1425. */
  1426. const DEFAULT_ENTRY_NAME = '[DEFAULT]';
  1427. /**
  1428. * Returns a {@link FirebasePerformance} instance for the given app.
  1429. * @param app - The {@link @firebase/app#FirebaseApp} to use.
  1430. * @public
  1431. */
  1432. function getPerformance(app = getApp()) {
  1433. app = getModularInstance(app);
  1434. const provider = _getProvider(app, 'performance');
  1435. const perfInstance = provider.getImmediate();
  1436. return perfInstance;
  1437. }
  1438. /**
  1439. * Returns a {@link FirebasePerformance} instance for the given app. Can only be called once.
  1440. * @param app - The {@link @firebase/app#FirebaseApp} to use.
  1441. * @param settings - Optional settings for the {@link FirebasePerformance} instance.
  1442. * @public
  1443. */
  1444. function initializePerformance(app, settings) {
  1445. app = getModularInstance(app);
  1446. const provider = _getProvider(app, 'performance');
  1447. // throw if an instance was already created.
  1448. // It could happen if initializePerformance() is called more than once, or getPerformance() is called first.
  1449. if (provider.isInitialized()) {
  1450. const existingInstance = provider.getImmediate();
  1451. const initialSettings = provider.getOptions();
  1452. if (deepEqual(initialSettings, settings !== null && settings !== void 0 ? settings : {})) {
  1453. return existingInstance;
  1454. }
  1455. else {
  1456. throw ERROR_FACTORY.create("already initialized" /* ErrorCode.ALREADY_INITIALIZED */);
  1457. }
  1458. }
  1459. const perfInstance = provider.initialize({
  1460. options: settings
  1461. });
  1462. return perfInstance;
  1463. }
  1464. /**
  1465. * Returns a new `PerformanceTrace` instance.
  1466. * @param performance - The {@link FirebasePerformance} instance to use.
  1467. * @param name - The name of the trace.
  1468. * @public
  1469. */
  1470. function trace(performance, name) {
  1471. performance = getModularInstance(performance);
  1472. return new Trace(performance, name);
  1473. }
  1474. const factory = (container, { options: settings }) => {
  1475. // Dependencies
  1476. const app = container.getProvider('app').getImmediate();
  1477. const installations = container
  1478. .getProvider('installations-internal')
  1479. .getImmediate();
  1480. if (app.name !== DEFAULT_ENTRY_NAME) {
  1481. throw ERROR_FACTORY.create("FB not default" /* ErrorCode.FB_NOT_DEFAULT */);
  1482. }
  1483. if (typeof window === 'undefined') {
  1484. throw ERROR_FACTORY.create("no window" /* ErrorCode.NO_WINDOW */);
  1485. }
  1486. setupApi(window);
  1487. const perfInstance = new PerformanceController(app, installations);
  1488. perfInstance._init(settings);
  1489. return perfInstance;
  1490. };
  1491. function registerPerformance() {
  1492. _registerComponent(new Component('performance', factory, "PUBLIC" /* ComponentType.PUBLIC */));
  1493. registerVersion(name, version);
  1494. // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation
  1495. registerVersion(name, version, 'esm2017');
  1496. }
  1497. registerPerformance();
  1498. export { getPerformance, initializePerformance, trace };
  1499. //# sourceMappingURL=index.esm2017.js.map