| 1 | /** |
| 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | * |
| 4 | * This source code is licensed under the MIT license found in the |
| 5 | * LICENSE file in the root directory of this source tree. |
| 6 | * |
| 7 | * @flow strict-local |
| 8 | */ |
| 9 | |
| 10 | import type {LoggerEvent} from 'react-devtools-shared/src/Logger'; |
| 11 | |
| 12 | import {registerEventLogger} from 'react-devtools-shared/src/Logger'; |
| 13 | import {enableLogger} from 'react-devtools-feature-flags'; |
| 14 | |
| 15 | let currentLoggingIFrame = null; |
| 16 | let currentSessionId = null; |
| 17 | let missedEvents: Array<LoggerEvent> = []; |
| 18 | let hasRegisteredEventLogger = false; |
| 19 | |
| 20 | type LoggerContext = { |
| 21 | page_url: ?string, |
| 22 | }; |
| 23 | |
| 24 | export function registerDevToolsEventLogger( |
| 25 | surface: string, |
| 26 | fetchAdditionalContext?: |
| 27 | | (() => LoggerContext) |
| 28 | | (() => Promise<LoggerContext>), |
| 29 | ): void { |
| 30 | async function logEvent(event: LoggerEvent) { |
| 31 | if (enableLogger) { |
| 32 | if (currentLoggingIFrame != null && currentSessionId != null) { |
| 33 | const {metadata, ...eventWithoutMetadata} = event; |
| 34 | const additionalContext: LoggerContext | {} = |
| 35 | fetchAdditionalContext != null ? await fetchAdditionalContext() : {}; |
| 36 | |
| 37 | currentLoggingIFrame?.contentWindow?.postMessage( |
| 38 | { |
| 39 | source: 'react-devtools-logging', |
| 40 | event: eventWithoutMetadata, |
| 41 | context: { |
| 42 | ...additionalContext, |
| 43 | metadata: metadata != null ? JSON.stringify(metadata) : '', |
| 44 | session_id: currentSessionId, |
| 45 | surface, |
| 46 | version: process.env.DEVTOOLS_VERSION, |
| 47 | }, |
| 48 | }, |
| 49 | '*', |
| 50 | ); |
| 51 | } else { |
| 52 | missedEvents.push(event); |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | function handleLoggingIFrameLoaded(iframe: HTMLIFrameElement) { |
| 58 | currentLoggingIFrame = iframe; |
| 59 | |
| 60 | if (missedEvents.length > 0) { |
| 61 | missedEvents.forEach(event => logEvent(event)); |
| 62 | missedEvents = []; |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | // If logger is enabled, register a logger that captures logged events |
| 67 | // and render iframe where the logged events will be reported to |
| 68 | if (enableLogger) { |
| 69 | const loggingUrl = process.env.LOGGING_URL; |
| 70 | const body = document.body; |
| 71 | |
| 72 | if ( |
| 73 | typeof loggingUrl === 'string' && |
| 74 | loggingUrl.length > 0 && |
| 75 | body != null && |
| 76 | !hasRegisteredEventLogger |
| 77 | ) { |
| 78 | hasRegisteredEventLogger = true; |
| 79 | registerEventLogger(logEvent); |
| 80 | currentSessionId = window.crypto.randomUUID(); |
| 81 | |
| 82 | const iframe = document.createElement('iframe'); |
| 83 | |
| 84 | iframe.onload = () => handleLoggingIFrameLoaded(iframe); |
| 85 | iframe.src = loggingUrl; |
| 86 | |
| 87 | body.appendChild(iframe); |
| 88 | } |
| 89 | } |
| 90 | } |