@@ -23,6 +23,9 @@ interface RoktKitSettings {
2323 placementEventAttributeMapping ?: string ;
2424 hashedEmailUserIdentityType ?: string ;
2525 onboardingExpProvider ?: string ;
26+ loggingUrl ?: string ;
27+ errorUrl ?: string ;
28+ isLoggingEnabled ?: string | boolean ;
2629}
2730
2831interface EventAttributeCondition {
@@ -123,6 +126,8 @@ interface MParticleExtended {
123126 captureTiming ?( metricName : string ) : void ;
124127 forwarder ?: RoktKit ;
125128 loggedEvents ?: Array < Record < string , unknown > > ;
129+ _registerErrorReportingService ?( service : ErrorReportingService ) : void ;
130+ _registerLoggingService ?( service : LoggingService ) : void ;
126131}
127132
128133interface TestHelpers {
@@ -136,6 +141,12 @@ interface TestHelpers {
136141 createAutoRemovedIframe : ( src : string ) => void ;
137142 djb2 : ( str : string ) => number ;
138143 setAllowedOriginHashes : ( hashes : number [ ] ) => void ;
144+ ReportingTransport : typeof ReportingTransport ;
145+ ErrorReportingService : typeof ErrorReportingService ;
146+ LoggingService : typeof LoggingService ;
147+ RateLimiter : typeof RateLimiter ;
148+ ErrorCodes : typeof ErrorCodes ;
149+ WSDKErrorSeverity : typeof WSDKErrorSeverity ;
139150}
140151
141152interface ForwarderRegistration {
@@ -152,11 +163,30 @@ interface MParticleEvent {
152163 [ key : string ] : unknown ;
153164}
154165
166+ interface ReportingConfig {
167+ loggingUrl ?: string ;
168+ errorUrl ?: string ;
169+ isLoggingEnabled ?: boolean | string ;
170+ }
171+
172+ interface ErrorReport {
173+ message : string ;
174+ code ?: string ;
175+ severity ?: string ;
176+ stackTrace ?: string ;
177+ }
178+
179+ interface LogEntry {
180+ message : string ;
181+ code ?: string ;
182+ }
183+
155184declare global {
156185 interface Window {
157186 Rokt ?: RoktGlobal ;
158187 __rokt_li_guid__ ?: string ;
159188 optimizely ?: OptimizelyGlobal ;
189+ ROKT_DOMAIN ?: string ;
160190 // mParticle is declared as any to avoid conflicts with @mparticle /web-sdk type declarations.
161191 // We use the typed mp() accessor for all internal accesses.
162192 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -175,6 +205,26 @@ const ADBLOCK_CONTROL_DOMAIN = 'apps.roktecommerce.com';
175205const INIT_LOG_SAMPLING_RATE = 0.1 ;
176206const MESSAGE_TYPE_PROFILE = 14 ; // mParticle MessageType.Profile
177207
208+ // ============================================================
209+ // Reporting service constants
210+ // ============================================================
211+
212+ const ErrorCodes = {
213+ UNKNOWN_ERROR : 'UNKNOWN_ERROR' ,
214+ UNHANDLED_EXCEPTION : 'UNHANDLED_EXCEPTION' ,
215+ IDENTITY_REQUEST : 'IDENTITY_REQUEST' ,
216+ } as const ;
217+
218+ const WSDKErrorSeverity = {
219+ ERROR : 'ERROR' ,
220+ INFO : 'INFO' ,
221+ WARNING : 'WARNING' ,
222+ } as const ;
223+
224+ const DEFAULT_LOGGING_URL = 'apps.rokt-api.com/v1/log' ;
225+ const DEFAULT_ERROR_URL = 'apps.rokt-api.com/v1/errors' ;
226+ const RATE_LIMIT_PER_SEVERITY = 10 ;
227+
178228// ============================================================
179229// Helper: typed accessor for window.mParticle
180230// We use an explicit cast here to avoid conflicts with @mparticle /web-sdk
@@ -359,6 +409,177 @@ function sendAdBlockMeasurementSignals(domain: string | undefined, version: stri
359409 ) ;
360410}
361411
412+ // ============================================================
413+ // Reporting helpers
414+ // ============================================================
415+
416+ function _isRoktDomainPresent ( ) : boolean {
417+ return typeof window !== 'undefined' && Boolean ( window . ROKT_DOMAIN ) ;
418+ }
419+
420+ function _isDebugModeEnabled ( ) : boolean {
421+ return typeof window !== 'undefined' && ! ! window . location ?. search ?. toLowerCase ( ) . includes ( 'mp_enable_logging=true' ) ;
422+ }
423+
424+ function _getReportingUrl ( ) : string | undefined {
425+ return typeof window !== 'undefined' ? window . location ?. href : undefined ;
426+ }
427+
428+ function _getUserAgent ( ) : string | undefined {
429+ return typeof window !== 'undefined' ? window . navigator ?. userAgent : undefined ;
430+ }
431+
432+ class RateLimiter {
433+ private _logCount : Record < string , number > = { } ;
434+
435+ incrementAndCheck ( severity : string ) : boolean {
436+ const count = this . _logCount [ severity ] || 0 ;
437+ const newCount = count + 1 ;
438+ this . _logCount [ severity ] = newCount ;
439+ return newCount > RATE_LIMIT_PER_SEVERITY ;
440+ }
441+ }
442+
443+ class ReportingTransport {
444+ private _isEnabled : boolean ;
445+ private _integrationName : string ;
446+ private _launcherInstanceGuid : string | undefined ;
447+ private _accountId : string | null ;
448+ private _rateLimiter : RateLimiter ;
449+ private readonly _reporter = 'mp-wsdk' ;
450+
451+ constructor (
452+ config : ReportingConfig ,
453+ integrationName : string | null | undefined ,
454+ launcherInstanceGuid : string | undefined ,
455+ accountId : string | null | undefined ,
456+ rateLimiter ?: RateLimiter ,
457+ ) {
458+ const isLoggingEnabled = config ?. isLoggingEnabled === true || config ?. isLoggingEnabled === 'true' ;
459+ this . _integrationName = integrationName || '' ;
460+ this . _launcherInstanceGuid = launcherInstanceGuid ;
461+ this . _accountId = accountId || null ;
462+ this . _rateLimiter = rateLimiter || new RateLimiter ( ) ;
463+ this . _isEnabled = _isDebugModeEnabled ( ) || ( _isRoktDomainPresent ( ) && isLoggingEnabled ) ;
464+ }
465+
466+ send (
467+ url : string ,
468+ severity : string ,
469+ msg : string ,
470+ code ?: string ,
471+ stackTrace ?: string ,
472+ onError ?: ( error : Error ) => void ,
473+ ) : void {
474+ if ( ! this . _isEnabled || this . _rateLimiter . incrementAndCheck ( severity ) ) {
475+ return ;
476+ }
477+
478+ try {
479+ const logRequest = {
480+ additionalInformation : {
481+ message : msg ,
482+ version : this . _integrationName ,
483+ } ,
484+ severity,
485+ code : code || ErrorCodes . UNKNOWN_ERROR ,
486+ url : _getReportingUrl ( ) ,
487+ deviceInfo : _getUserAgent ( ) ,
488+ stackTrace,
489+ reporter : this . _reporter ,
490+ integration : this . _integrationName ,
491+ } ;
492+
493+ const headers : Record < string , string > = {
494+ Accept : 'text/plain;charset=UTF-8' ,
495+ 'Content-Type' : 'application/json' ,
496+ 'rokt-launcher-version' : this . _integrationName ,
497+ 'rokt-wsdk-version' : 'joint' ,
498+ } ;
499+
500+ if ( this . _launcherInstanceGuid ) {
501+ headers [ 'rokt-launcher-instance-guid' ] = this . _launcherInstanceGuid ;
502+ }
503+ if ( this . _accountId ) {
504+ headers [ 'rokt-account-id' ] = this . _accountId ;
505+ }
506+
507+ fetch ( url , {
508+ method : 'POST' ,
509+ headers,
510+ body : JSON . stringify ( logRequest ) ,
511+ } ) . catch ( ( error : Error ) => {
512+ console . error ( 'ReportingTransport: Failed to send log' , error ) ;
513+ if ( onError ) onError ( error ) ;
514+ } ) ;
515+ } catch ( error ) {
516+ console . error ( 'ReportingTransport: Failed to send log' , error ) ;
517+ if ( onError ) onError ( error as Error ) ;
518+ }
519+ }
520+ }
521+
522+ class ErrorReportingService {
523+ private _transport : ReportingTransport ;
524+ private _errorUrl : string ;
525+
526+ constructor (
527+ config : ReportingConfig ,
528+ integrationName : string | null | undefined ,
529+ launcherInstanceGuid ?: string ,
530+ accountId ?: string | null ,
531+ rateLimiter ?: RateLimiter ,
532+ ) {
533+ this . _transport = new ReportingTransport ( config , integrationName , launcherInstanceGuid , accountId , rateLimiter ) ;
534+ this . _errorUrl = 'https://' + ( config ?. errorUrl || DEFAULT_ERROR_URL ) ;
535+ }
536+
537+ report ( error : ErrorReport | null | undefined ) : void {
538+ if ( ! error ) return ;
539+ const severity = error . severity || WSDKErrorSeverity . ERROR ;
540+ this . _transport . send ( this . _errorUrl , severity , error . message , error . code , error . stackTrace ) ;
541+ }
542+ }
543+
544+ class LoggingService {
545+ private _transport : ReportingTransport ;
546+ private _loggingUrl : string ;
547+ private _errorReportingService : { report : ( e : ErrorReport ) => void } ;
548+
549+ constructor (
550+ config : ReportingConfig ,
551+ errorReportingService : { report : ( e : ErrorReport ) => void } ,
552+ integrationName : string | null | undefined ,
553+ launcherInstanceGuid ?: string ,
554+ accountId ?: string | null ,
555+ rateLimiter ?: RateLimiter ,
556+ ) {
557+ this . _transport = new ReportingTransport ( config , integrationName , launcherInstanceGuid , accountId , rateLimiter ) ;
558+ this . _loggingUrl = 'https://' + ( config ?. loggingUrl || DEFAULT_LOGGING_URL ) ;
559+ this . _errorReportingService = errorReportingService ;
560+ }
561+
562+ log ( entry : LogEntry | null | undefined ) : void {
563+ if ( ! entry ) return ;
564+ this . _transport . send (
565+ this . _loggingUrl ,
566+ WSDKErrorSeverity . INFO ,
567+ entry . message ,
568+ entry . code ,
569+ undefined ,
570+ ( error : Error ) => {
571+ if ( this . _errorReportingService ) {
572+ this . _errorReportingService . report ( {
573+ message : 'LoggingService: Failed to send log: ' + error . message ,
574+ code : ErrorCodes . UNKNOWN_ERROR ,
575+ severity : WSDKErrorSeverity . ERROR ,
576+ } ) ;
577+ }
578+ } ,
579+ ) ;
580+ }
581+ }
582+
362583// ============================================================
363584// RoktKit class
364585// ============================================================
@@ -387,6 +608,8 @@ class RoktKit {
387608 public eventStreamQueue : MParticleEvent [ ] = [ ] ;
388609 public integrationName : string | null = null ;
389610 public domain ?: string ;
611+ public errorReportingService : ErrorReportingService | null = null ;
612+ public loggingService : LoggingService | null = null ;
390613
391614 // Private fields
392615 private _mappedEmailSha256Key ?: string ;
@@ -738,6 +961,35 @@ class RoktKit {
738961
739962 this . domain = domain ;
740963
964+ const reportingConfig : ReportingConfig = {
965+ loggingUrl : settings . loggingUrl ,
966+ errorUrl : settings . errorUrl ,
967+ isLoggingEnabled : settings . isLoggingEnabled === 'true' || settings . isLoggingEnabled === true ,
968+ } ;
969+ const errorReportingService = new ErrorReportingService (
970+ reportingConfig ,
971+ this . integrationName ,
972+ window . __rokt_li_guid__ ,
973+ settings . accountId ,
974+ ) ;
975+ const loggingService = new LoggingService (
976+ reportingConfig ,
977+ errorReportingService ,
978+ this . integrationName ,
979+ window . __rokt_li_guid__ ,
980+ settings . accountId ,
981+ ) ;
982+
983+ this . errorReportingService = errorReportingService ;
984+ this . loggingService = loggingService ;
985+
986+ if ( mp ( ) . _registerErrorReportingService ) {
987+ mp ( ) . _registerErrorReportingService ! ( errorReportingService ) ;
988+ }
989+ if ( mp ( ) . _registerLoggingService ) {
990+ mp ( ) . _registerLoggingService ! ( loggingService ) ;
991+ }
992+
741993 if ( testMode ) {
742994 this . testHelpers = {
743995 generateLauncherScript : generateLauncherScript ,
@@ -752,6 +1004,12 @@ class RoktKit {
7521004 setAllowedOriginHashes : ( hashes : number [ ] ) => {
7531005 RoktKit . _allowedOriginHashes = hashes ;
7541006 } ,
1007+ ReportingTransport : ReportingTransport ,
1008+ ErrorReportingService : ErrorReportingService ,
1009+ LoggingService : LoggingService ,
1010+ RateLimiter : RateLimiter ,
1011+ ErrorCodes : ErrorCodes ,
1012+ WSDKErrorSeverity : WSDKErrorSeverity ,
7551013 } ;
7561014 this . attachLauncher ( accountId , launcherOptions ) ;
7571015 return ;
0 commit comments