@@ -23,6 +23,7 @@ export interface SetupOptions {
2323 eventBridge ?: EventBridgeOptions
2424 head ?: string
2525 body ?: string
26+ preInitScript ?: string
2627 baseUrlHooks : UrlHook [ ]
2728 context : {
2829 run_id : string
@@ -70,6 +71,58 @@ export const DEFAULT_SETUPS =
7071 { name : 'bundle' , factory : bundleSetup } ,
7172 ]
7273
74+ /**
75+ * Defines `window.DD_PRE_INIT`, the application script that runs once the SDK is loaded (and
76+ * therefore instrumenting the page) but before `init()` is called. The script body may return a
77+ * promise: `init()` then waits for it to settle, which lets a scenario complete asynchronous work
78+ * — a request, a WebSocket exchange — entirely within the pre-init window.
79+ *
80+ * Setups call it from their init site through {@link afterPreInitScript}, so the definition itself
81+ * can be emitted anywhere in the head. It runs at most once, on the first init site to reach it.
82+ */
83+ function preInitDefinition ( options : SetupOptions ) {
84+ if ( ! options . preInitScript ) {
85+ return ''
86+ }
87+
88+ return html `< script type ="text/javascript ">
89+ window . DD_PRE_INIT = ( function ( ) {
90+ var result
91+ var called = false
92+ return function ( ) {
93+ if ( ! called ) {
94+ called = true
95+ result = ( function ( ) {
96+ ${ options . preInitScript }
97+ } ) ( )
98+ }
99+ return result
100+ }
101+ } ) ( )
102+ </ script > `
103+ }
104+
105+ /**
106+ * Setups that don't control the init call site can't offer the pre-init window. Fail loudly rather
107+ * than silently ignoring the script and letting the scenario pass for the wrong reason.
108+ */
109+ function rejectPreInitScript ( options : SetupOptions , setupName : string ) {
110+ if ( options . preInitScript ) {
111+ throw new Error ( `withPreInitScript() is not supported by the ${ setupName } setup` )
112+ }
113+ }
114+
115+ /** Wraps init code so that it runs after the pre-init script (see {@link preInitDefinition}). */
116+ function afterPreInitScript ( options : SetupOptions , initCode : string ) {
117+ if ( ! options . preInitScript ) {
118+ return initCode
119+ }
120+
121+ return js `Promise.resolve(window.DD_PRE_INIT()).then(function () {
122+ ${ initCode }
123+ })`
124+ }
125+
73126export function asyncSetup ( options : SetupOptions , servers : Servers ) {
74127 let header = options . head || ''
75128 let footer = ''
@@ -82,6 +135,8 @@ export function asyncSetup(options: SetupOptions, servers: Servers) {
82135 header += setupExtension ( options , servers )
83136 }
84137
138+ header += preInitDefinition ( options )
139+
85140 function formatSnippet ( url : string , globalName : string ) {
86141 return `(function(h,o,u,n,d) {
87142h=h[d]=h[d]||{q:[],onReady:function(c){h.q.push(c)}}
@@ -96,8 +151,11 @@ n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,n)
96151 footer += html `< script >
97152 ${ formatSnippet ( logsScriptUrl , 'DD_LOGS' ) }
98153 DD_LOGS . onReady ( function ( ) {
99- DD_LOGS . setGlobalContext ( ${ JSON . stringify ( options . context ) } )
100- ; ( ${ options . logsInit . toString ( ) } ) ( ${ formatConfiguration ( options . logs , servers ) } )
154+ ${ afterPreInitScript (
155+ options ,
156+ js `DD_LOGS . setGlobalContext ( ${ JSON . stringify ( options . context ) } )
157+ ; ( ${ options . logsInit . toString ( ) } ) ( ${ formatConfiguration ( options . logs , servers ) } ) `
158+ ) }
101159 } )
102160 </ script > `
103161 }
@@ -106,8 +164,11 @@ n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,n)
106164 footer += html `< script type ="text/javascript ">
107165 ${ formatSnippet ( rumScriptUrl , 'DD_RUM' ) }
108166 DD_RUM . onReady ( function ( ) {
109- DD_RUM . setGlobalContext ( ${ JSON . stringify ( options . context ) } )
110- ; ( ${ options . rumInit . toString ( ) } ) ( ${ formatConfiguration ( options . rum , servers ) } )
167+ ${ afterPreInitScript (
168+ options ,
169+ js `DD_RUM . setGlobalContext ( ${ JSON . stringify ( options . context ) } )
170+ ; ( ${ options . rumInit . toString ( ) } ) ( ${ formatConfiguration ( options . rum , servers ) } ) `
171+ ) }
111172 } )
112173 </ script > `
113174 }
@@ -141,31 +202,42 @@ export function bundleSetup(options: SetupOptions, servers: Servers) {
141202
142203 const { logsScriptUrl, rumScriptUrl, debuggerScriptUrl } = createCrossOriginScriptUrls ( servers , options )
143204
205+ // Every SDK bundle is loaded before any init() call, so the pre-init script runs with all of
206+ // them instrumenting the page.
207+ let sdkScripts = ''
208+ let initScripts = ''
209+
144210 if ( options . logs ) {
145- header += html `< script type ="text/javascript " src ="${ logsScriptUrl } " crossorigin > </ script > `
146- header += html `< script type ="text/javascript ">
147- DD_LOGS . setGlobalContext ( ${ JSON . stringify ( options . context ) } )
148- ; ( ${ options . logsInit . toString ( ) } ) ( ${ formatConfiguration ( options . logs , servers ) } )
211+ sdkScripts += html `< script type ="text/javascript " src ="${ logsScriptUrl } " crossorigin > </ script > `
212+ initScripts += html `< script type ="text/javascript ">
213+ ${ afterPreInitScript (
214+ options ,
215+ js `DD_LOGS . setGlobalContext ( ${ JSON . stringify ( options . context ) } )
216+ ; ( ${ options . logsInit . toString ( ) } ) ( ${ formatConfiguration ( options . logs , servers ) } ) `
217+ ) }
149218 </ script > `
150219 }
151220
152221 if ( options . rum ) {
153- header += html `< script type ="text/javascript " src ="${ rumScriptUrl } " crossorigin > </ script > `
154- header += html `< script type ="text/javascript ">
155- DD_RUM . setGlobalContext ( ${ JSON . stringify ( options . context ) } )
156- ; ( ${ options . rumInit . toString ( ) } ) ( ${ formatConfiguration ( options . rum , servers ) } )
222+ sdkScripts += html `< script type ="text/javascript " src ="${ rumScriptUrl } " crossorigin > </ script > `
223+ initScripts += html `< script type ="text/javascript ">
224+ ${ afterPreInitScript (
225+ options ,
226+ js `DD_RUM . setGlobalContext ( ${ JSON . stringify ( options . context ) } )
227+ ; ( ${ options . rumInit . toString ( ) } ) ( ${ formatConfiguration ( options . rum , servers ) } ) `
228+ ) }
157229 </ script > `
158230 }
159231
160232 if ( options . debugger ) {
161- header += html `
162- < script type ="text/javascript " src ="${ debuggerScriptUrl } "> </ script >
163- < script type ="text/javascript ">
164- DD_DEBUGGER . init ( ${ formatConfiguration ( options . debugger , servers ) } )
165- </ script >
166- `
233+ sdkScripts += html `< script type ="text/javascript " src ="${ debuggerScriptUrl } "> </ script > `
234+ initScripts += html `< script type ="text/javascript ">
235+ ${ afterPreInitScript ( options , js `DD_DEBUGGER . init ( ${ formatConfiguration ( options . debugger , servers ) } ) ` ) }
236+ </ script > `
167237 }
168238
239+ header += preInitDefinition ( options ) + sdkScripts + initScripts
240+
169241 return basePage ( {
170242 header,
171243 body : options . body ,
@@ -209,6 +281,9 @@ export function npmSetup(options: SetupOptions, servers: Servers) {
209281 </ script > `
210282 }
211283
284+ // The app bundle imports the SDK and then calls the *_INIT globals, so it is the one calling
285+ // DD_PRE_INIT in between.
286+ header += preInitDefinition ( options )
212287 header += html `< script type ="text/javascript " src ="./app.js "> </ script > `
213288
214289 return basePage ( {
@@ -218,6 +293,8 @@ export function npmSetup(options: SetupOptions, servers: Servers) {
218293}
219294
220295export function appSetup ( options : SetupOptions , servers : Servers , appName : string ) {
296+ rejectPreInitScript ( options , 'app' )
297+
221298 let header = options . head || ''
222299
223300 if ( options . eventBridge ) {
@@ -276,6 +353,8 @@ export function workerSetup(setupOptions: SetupOptions, servers: Servers) {
276353}
277354
278355export function microfrontendSetup ( options : SetupOptions , servers : Servers ) {
356+ rejectPreInitScript ( options , 'microfrontend' )
357+
279358 let header = options . head || ''
280359
281360 if ( options . eventBridge ) {
@@ -307,6 +386,8 @@ export function microfrontendSetup(options: SetupOptions, servers: Servers) {
307386// Salesforce apps don't serve a locally-generated page body; this factory only drives the
308387// page-side setup needed to init RUM on the remote Salesforce page.
309388export async function salesforceSetup ( options : SetupOptions , servers : Servers , page : Page ) : Promise < string > {
389+ rejectPreInitScript ( options , 'salesforce' )
390+
310391 const salesforceAppDirectory = options . salesforceApp === 'experience-cloud' ? 'sf-experience-app' : 'sf-lwc-app'
311392 const salesforceBundlePath = resolve (
312393 __dirname ,
0 commit comments