@@ -14,6 +14,10 @@ import { now } from '../common/timing/now'
1414import { warn } from '../common/util/console'
1515import { findScriptTimings } from '../common/v2/script-tracker'
1616import { parseUrl } from '../common/url/parse-url'
17+ import { IFRAME_TIMING_UPDATE , IFRAME_API , IFRAME_API_RESPONSE , IFRAME_VITALS_UPDATE , IFRAME_AJAX } from '../common/constants/iframe-constants'
18+ import { castErrorEvent , castError } from '../features/jserrors/shared/cast-error'
19+
20+ const REGISTER = 'register' // define it here for now to prevent importing the full list of constants for build size.
1721
1822/**
1923 * @typedef {import('../loaders/api/register-api-types').RegisterAPI } RegisterAPI
@@ -60,13 +64,13 @@ export class RegisteredIframeEntity {
6064 this . metadata . target = this . #targetDescriptor = opts
6165
6266 if ( ! isIFrameWindow ( window ) ) {
63- warn ( 71 , 'Must be in iframe context to use this interface' )
67+ warn ( 72 )
6468 this . blocked = true
6569 return
6670 }
6771
6872 // Store the registration promise so other methods can wait for it
69- this . #registrationPromise = this . #postMethodToAgent( 'register' , [ opts ] )
73+ this . #registrationPromise = this . #postMethodToAgent( REGISTER , [ opts ] )
7074 . then ( response => {
7175 // Merge updated metadata from parent (includes full target info with server-generated IDs)
7276 if ( response ?. metadata ) {
@@ -104,24 +108,24 @@ export class RegisteredIframeEntity {
104108 } )
105109
106110 globalScope . addEventListener ( 'error' , err => {
107- this . noticeError ( err )
108- } )
109-
110- onCLS ( ( { value } ) => {
111- // send message to parent with CLS value and attributes
112- } , { reportAllChanges : true } )
113-
114- onLCP ( ( { value } ) => {
115- // send message to parent with LCP value and attributes
111+ this . noticeError ( castErrorEvent ( err ) )
116112 } )
117113
118- onFCP ( ( { value } ) => {
119- // send message to parent with FCP value and attributes
114+ globalScope . addEventListener ( 'unhandledrejection' , event => {
115+ this . noticeError ( castPromiseRejectionEvent ( event ) )
120116 } )
121117
122- onINP ( ( { value } ) => {
123- // send message to parent with INP value and attributes
118+ const vitals = [ [ onCLS , 'cls' ] , [ onLCP , 'lcp' ] , [ onFCP , 'fcp' ] , [ onINP , 'inp' ] ]
119+ vitals . forEach ( ( [ vitalFn , property ] ) => {
120+ vitalFn ( ( { value } ) => {
121+ // send message to parent with vital value and attributes
122+ this . #postMessageToParent( IFRAME_VITALS_UPDATE , {
123+ property,
124+ value
125+ } )
126+ } , { reportAllChanges : property === 'cls' } )
124127 } )
128+
125129
126130 // Instrument ajax here using buffered resource timing so pre-registration entries are included.
127131 const initiators = { xmlhttprequest : 'xhr' , fetch : 'fetch' , beacon : 'beacon' }
@@ -130,9 +134,13 @@ export class RegisteredIframeEntity {
130134 const params = { status : resource . responseStatus }
131135 const metrics = { rxSize : resource . transferSize , duration : Math . floor ( resource . duration ) , cbTime : 0 }
132136 addUrl ( params , resource . name )
133- // this.handler('xhr', [params, metrics, resource.startTime, resource.responseEnd, initiators[resource.initiatorType]], undefined, FEATURE_NAMES.ajax)
134-
135- // send message to parent with ajax resource timing data HERE
137+ this . #postMessageToParent( IFRAME_AJAX , {
138+ params,
139+ metrics,
140+ start : resource . startTime ,
141+ end : resource . responseEnd ,
142+ initiatorType : initiators [ resource . initiatorType ]
143+ } )
136144 }
137145 }
138146
@@ -145,7 +153,7 @@ export class RegisteredIframeEntity {
145153
146154 globalScope . addEventListener ( 'message' , ( event ) => {
147155 // Validate message structure
148- if ( event . data ?. type !== 'newrelic-iframe-api-response' ) return
156+ if ( event . data ?. type !== IFRAME_API_RESPONSE ) return
149157
150158 // Validate origin if we have specific allowed origins
151159 if ( ! this . #isAllowedOrigin( event . origin ) ) {
@@ -155,7 +163,7 @@ export class RegisteredIframeEntity {
155163
156164 // Validate iframeInterfaceId to prevent spoofing (message must be for this instance)
157165 if ( event . data . iframeInterfaceId !== this . #iframeInterfaceId) {
158- warn ( 75 , 'Rejected message with mismatched iframeInterfaceId' )
166+ warn ( 75 )
159167 return
160168 }
161169
@@ -190,15 +198,18 @@ export class RegisteredIframeEntity {
190198 /**
191199 * Low-level helper to send postMessage to parent window with error handling
192200 * @private
193- * @param {object } message - The message object to send
201+ * @param {string } type - The message type to send
202+ * @param {object } data - The message payload to send
203+ * @param {number } [timestamp=now()] - The relative timestamp of the message trigger. defaults to the postMessage call time if not provided.
204+ * @param {{resolve: Function, reject: Function} } [resolvers] - Optional promise resolvers for handling response
194205 */
195- #postMessageToParent ( type , data , resolvers ) {
206+ #postMessageToParent ( type , data , timestamp = now ( ) , resolvers ) {
196207 try {
197208 this . #openPending( data . messageId , resolvers )
198209 globalScope . parent . postMessage ( {
199210 type,
200211 target : this . #targetDescriptor,
201- timestamp : now ( ) ,
212+ timestamp,
202213 iframeInterfaceId : this . #iframeInterfaceId,
203214 ...data
204215 } , this . #parentOrigin)
@@ -216,7 +227,7 @@ export class RegisteredIframeEntity {
216227 #postTimingToAgent ( property , value ) {
217228 if ( this . blocked ) return
218229
219- this . #postMessageToParent( 'newrelic-iframe-timing-update' , {
230+ this . #postMessageToParent( IFRAME_TIMING_UPDATE , {
220231 property,
221232 value
222233 } )
@@ -232,20 +243,21 @@ export class RegisteredIframeEntity {
232243 */
233244 #postMethodToAgent ( method , args ) {
234245 if ( this . blocked ) {
235- return Promise . reject ( new Error ( 'Iframe interface is blocked' ) )
246+ const msg = 'Iframe interface is blocked'
247+ warn ( 72 , msg )
248+ return Promise . reject ( new Error ( msg ) )
236249 }
237-
238- // For all methods except register itself, wait for registration to complete first
239- const waitPromise = method === 'register' ? Promise . resolve ( ) : this . #registrationPromise
250+ const timestamp = now ( ) // For all methods except register itself, wait for registration to complete first
251+ const waitPromise = method === REGISTER ? Promise . resolve ( ) : this . #registrationPromise
240252
241253 return waitPromise . then ( ( ) => new Promise ( ( resolve , reject ) => {
242254 try {
243255 const messageId = ++ this . #messageIdCounter
244- this . #postMessageToParent( 'newrelic-iframe-api' , {
256+ this . #postMessageToParent( IFRAME_API , {
245257 messageId,
246258 method,
247259 args
248- } , {
260+ } , timestamp , {
249261 resolve,
250262 reject
251263 } )
@@ -291,7 +303,7 @@ export class RegisteredIframeEntity {
291303 @returns {Promise<import('../loaders/api/register-api-types').RegisterAPI> } Returns a promise that resolves with an object that contains the available API methods and configurations to use with the external caller. See loaders/api/api.js for more information.
292304 */
293305 register ( target ) {
294- return this . #postMethodToAgent( 'register' , [ target ] )
306+ return this . #postMethodToAgent( REGISTER , [ target ] )
295307 }
296308
297309 /**
@@ -350,7 +362,7 @@ export class RegisteredIframeEntity {
350362 * @param {object } [customAttributes] An object containing name/value pairs representing custom attributes.
351363 */
352364 noticeError ( error , customAttributes ) {
353- this . #postMethodToAgent( 'noticeError' , [ error , customAttributes ] )
365+ this . #postMethodToAgent( 'noticeError' , [ castError ( error ) , customAttributes ] )
354366 }
355367
356368 /**
0 commit comments