@mparticle/web-sdk 2.56.0 → 2.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mparticle/web-sdk",
3
- "version": "2.56.0",
3
+ "version": "2.58.0",
4
4
  "description": "mParticle core SDK for web applications",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
package/src/constants.ts CHANGED
@@ -139,6 +139,8 @@ const Constants = {
139
139
  identityUrl: 'identity.mparticle.com/v1/',
140
140
  aliasUrl: 'jssdks.mparticle.com/v1/identity/',
141
141
  userAudienceUrl: 'nativesdks.mparticle.com/v1/',
142
+ loggingUrl: 'apps.rokt-api.com/v1/log',
143
+ errorUrl: 'apps.rokt-api.com/v1/errors',
142
144
  },
143
145
  // These are the paths that are used to construct the CNAME urls
144
146
  CNAMEUrlPaths: {
@@ -148,6 +150,8 @@ const Constants = {
148
150
  configUrl: '/tags/JS/v2/',
149
151
  identityUrl: '/identity/v1/',
150
152
  aliasUrl: '/webevents/v1/identity/',
153
+ loggingUrl: '/v1/log',
154
+ errorUrl: '/v1/errors',
151
155
  },
152
156
  Base64CookieKeys: {
153
157
  csm: 1,
@@ -1,4 +1,4 @@
1
- import Constants, { HTTP_ACCEPTED, HTTP_BAD_REQUEST, HTTP_OK } from './constants';
1
+ import Constants, { HTTP_ACCEPTED, HTTP_BAD_REQUEST, HTTP_OK, HTTP_SERVER_ERROR } from './constants';
2
2
  import {
3
3
  AsyncUploader,
4
4
  FetchUploader,
@@ -25,6 +25,7 @@ import {
25
25
  IIdentityResponse,
26
26
  } from './identity-user-interfaces';
27
27
  import { IMParticleWebSDKInstance } from './mp-instance';
28
+ import { ErrorCodes } from './logging/types';
28
29
 
29
30
  const { HTTPCodes, Messages, IdentityMethods } = Constants;
30
31
 
@@ -284,15 +285,26 @@ export default function IdentityAPIClient(
284
285
  break;
285
286
 
286
287
  // Our Identity API will return:
287
- // - 401 if the `x-mp-key` is incorrect or missing
288
- // - 403 if the there is a permission or account issue related to the `x-mp-key`
288
+ // 401 if the `x-mp-key` is incorrect or missing
289
+ // 403 if the there is a permission or account issue related to the `x-mp-key`
289
290
  // 401 and 403 have no response bodies and should be rejected outright
291
+ // 5xx server error throws error but RoktManager continues
290
292
  default: {
291
- throw new Error('Received HTTP Code of ' + response.status);
293
+ if (response.status >= HTTP_SERVER_ERROR) {
294
+ throw new Error('Received HTTP Code of ' + response.status);
295
+ }
296
+
297
+ mpInstance._Store.identityCallInFlight = false;
298
+ mpInstance._Store.identityCallFailed = false;
299
+ const errorMessage = 'Received HTTP Code of ' + response.status;
300
+ Logger.error('Error sending identity request to servers - ' + errorMessage);
301
+ invokeCallback(callback, HTTPCodes.noHttpCoverage, errorMessage);
302
+ return;
292
303
  }
293
304
  }
294
305
 
295
306
  mpInstance._Store.identityCallInFlight = false;
307
+ mpInstance._Store.identityCallFailed = false;
296
308
 
297
309
  Logger.verbose(message);
298
310
  if (mpInstance._RoktManager?.isInitialized) {
@@ -310,19 +322,20 @@ export default function IdentityAPIClient(
310
322
  );
311
323
  } catch (err) {
312
324
  mpInstance._Store.identityCallInFlight = false;
325
+ mpInstance._Store.identityCallFailed = true;
313
326
  if (mpInstance._RoktManager?.isInitialized) {
314
327
  const requestCount = mpInstance._Store.identifyRequestCount;
315
328
  mpInstance.captureTiming(`${requestCount}-identityRequestEnd`);
316
329
  }
317
-
318
- const errorMessage = (err as Error).message || err.toString();
319
330
 
320
- Logger.error('Error sending identity request to servers' + ' - ' + errorMessage);
321
- invokeCallback(
322
- callback,
323
- HTTPCodes.noHttpCoverage,
324
- errorMessage,
331
+ const errorMessage = (err as Error).message || err.toString();
332
+ Logger.error(
333
+ 'Error sending identity request to servers' + ' - ' + errorMessage,
334
+ ErrorCodes.IDENTITY_REQUEST
325
335
  );
336
+
337
+ mpInstance.processQueueOnIdentityFailure?.();
338
+ invokeCallback(callback, HTTPCodes.noHttpCoverage, errorMessage);
326
339
  }
327
340
  };
328
341
 
package/src/logger.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { LogLevelType, SDKInitConfig, SDKLoggerApi } from './sdkRuntimeModels';
2
+ import { ReportingLogger } from './logging/reportingLogger';
3
+ import { ErrorCodes } from './logging/types';
2
4
 
3
5
  export type ILoggerConfig = Pick<SDKInitConfig, 'logLevel' | 'logger'>;
4
6
  export type IConsoleLogger = Partial<Pick<SDKLoggerApi, 'error' | 'warning' | 'verbose'>>;
@@ -6,10 +8,14 @@ export type IConsoleLogger = Partial<Pick<SDKLoggerApi, 'error' | 'warning' | 'v
6
8
  export class Logger {
7
9
  private logLevel: LogLevelType;
8
10
  private logger: IConsoleLogger;
11
+ private reportingLogger: ReportingLogger;
9
12
 
10
- constructor(config: ILoggerConfig) {
13
+ constructor(config: ILoggerConfig,
14
+ reportingLogger?: ReportingLogger,
15
+ ) {
11
16
  this.logLevel = config.logLevel ?? LogLevelType.Warning;
12
17
  this.logger = config.logger ?? new ConsoleLogger();
18
+ this.reportingLogger = reportingLogger;
13
19
  }
14
20
 
15
21
  public verbose(msg: string): void {
@@ -22,21 +28,24 @@ export class Logger {
22
28
  }
23
29
 
24
30
  public warning(msg: string): void {
25
- if(this.logLevel === LogLevelType.None)
31
+ if(this.logLevel === LogLevelType.None)
26
32
  return;
27
33
 
28
- if (this.logger.warning &&
34
+ if (this.logger.warning &&
29
35
  (this.logLevel === LogLevelType.Verbose || this.logLevel === LogLevelType.Warning)) {
30
36
  this.logger.warning(msg);
31
37
  }
32
38
  }
33
39
 
34
- public error(msg: string): void {
35
- if(this.logLevel === LogLevelType.None)
40
+ public error(msg: string, codeForReporting?: ErrorCodes): void {
41
+ if(this.logLevel === LogLevelType.None)
36
42
  return;
37
43
 
38
44
  if (this.logger.error) {
39
45
  this.logger.error(msg);
46
+ if (codeForReporting) {
47
+ this.reportingLogger?.error(msg, codeForReporting);
48
+ }
40
49
  }
41
50
  }
42
51
 
@@ -0,0 +1,192 @@
1
+ import { ErrorCodes, LogRequestBody, WSDKErrorSeverity } from "./types";
2
+ import { FetchUploader, IFetchPayload } from "../uploaders";
3
+ import { IStore, SDKConfig } from "../store";
4
+ import { SDKInitConfig } from "../sdkRuntimeModels";
5
+ import Constants from "../constants";
6
+
7
+ // Header key constants
8
+ const HEADER_ACCEPT = 'Accept' as const;
9
+ const HEADER_CONTENT_TYPE = 'Content-Type' as const;
10
+ const HEADER_ROKT_LAUNCHER_VERSION = 'rokt-launcher-version' as const;
11
+ const HEADER_ROKT_LAUNCHER_INSTANCE_GUID = 'rokt-launcher-instance-guid' as const;
12
+ const HEADER_ROKT_WSDK_VERSION = 'rokt-wsdk-version' as const;
13
+
14
+ interface IReportingLoggerPayload extends IFetchPayload {
15
+ headers: IFetchPayload['headers'] & {
16
+ [HEADER_ROKT_LAUNCHER_INSTANCE_GUID]?: string;
17
+ [HEADER_ROKT_LAUNCHER_VERSION]: string;
18
+ [HEADER_ROKT_WSDK_VERSION]: string;
19
+ };
20
+ body: string;
21
+ }
22
+
23
+ export class ReportingLogger {
24
+ private readonly isEnabled: boolean;
25
+ private readonly reporter: string = 'mp-wsdk';
26
+ private readonly rateLimiter: IRateLimiter;
27
+ private store: IStore | null;
28
+ private readonly loggingUrl: string;
29
+ private readonly errorUrl: string;
30
+ private readonly isLoggingEnabled: boolean;
31
+
32
+ constructor(
33
+ config: SDKConfig | SDKInitConfig | any,
34
+ private readonly sdkVersion: string,
35
+ store?: IStore,
36
+ private readonly launcherInstanceGuid?: string,
37
+ rateLimiter?: IRateLimiter,
38
+ ) {
39
+ this.loggingUrl = `https://${config.loggingUrl || Constants.DefaultBaseUrls.loggingUrl}`;
40
+ this.errorUrl = `https://${config.errorUrl || Constants.DefaultBaseUrls.errorUrl}`;
41
+ this.isLoggingEnabled = config.isLoggingEnabled || false;
42
+ this.store = store ?? null;
43
+ this.isEnabled = this.isReportingEnabled();
44
+ this.rateLimiter = rateLimiter ?? new RateLimiter();
45
+ }
46
+
47
+ public setStore(store: IStore): void {
48
+ this.store = store;
49
+ }
50
+
51
+ public info(msg: string, code?: ErrorCodes) {
52
+ this.sendLog(WSDKErrorSeverity.INFO, msg, code);
53
+ }
54
+
55
+ public error(msg: string, code?: ErrorCodes, stackTrace?: string) {
56
+ this.sendError(WSDKErrorSeverity.ERROR, msg, code, stackTrace);
57
+ }
58
+
59
+ public warning(msg: string, code?: ErrorCodes) {
60
+ this.sendError(WSDKErrorSeverity.WARNING, msg, code);
61
+ }
62
+
63
+ private sendToServer(url: string, severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): void {
64
+ if (!this.canSendLog(severity))
65
+ return;
66
+
67
+ try {
68
+ const logRequest = this.buildLogRequest(severity, msg, code, stackTrace);
69
+ const uploader = new FetchUploader(url);
70
+ const payload: IReportingLoggerPayload = {
71
+ method: 'POST',
72
+ headers: this.getHeaders(),
73
+ body: JSON.stringify(logRequest),
74
+ };
75
+ uploader.upload(payload).catch((error) => {
76
+ console.error('ReportingLogger: Failed to send log', error);
77
+ });
78
+ } catch (error) {
79
+ console.error('ReportingLogger: Failed to send log', error);
80
+ }
81
+ }
82
+
83
+ private sendLog(severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): void {
84
+ this.sendToServer(this.loggingUrl, severity, msg, code, stackTrace);
85
+ }
86
+
87
+ private sendError(severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): void {
88
+ this.sendToServer(this.errorUrl, severity, msg, code, stackTrace);
89
+ }
90
+
91
+ private buildLogRequest(severity: WSDKErrorSeverity, msg: string, code?: ErrorCodes, stackTrace?: string): LogRequestBody {
92
+ return {
93
+ additionalInformation: {
94
+ message: msg,
95
+ version: this.getVersion(),
96
+ },
97
+ severity: severity,
98
+ code: code ?? ErrorCodes.UNKNOWN_ERROR,
99
+ url: this.getUrl(),
100
+ deviceInfo: this.getUserAgent(),
101
+ stackTrace: stackTrace,
102
+ reporter: this.reporter,
103
+ // Integration will be set to integrationName once the kit connects via RoktManager.attachKit()
104
+ integration: this.store?.getIntegrationName() ?? 'mp-wsdk'
105
+ };
106
+ }
107
+
108
+ private getVersion(): string {
109
+ return this.store?.getIntegrationName?.() ?? `mParticle_wsdkv_${this.sdkVersion}`;
110
+ }
111
+
112
+ private isReportingEnabled(): boolean {
113
+ return this.isDebugModeEnabled() ||
114
+ (this.isRoktDomainPresent() && this.isFeatureFlagEnabled());
115
+ }
116
+
117
+ private isRoktDomainPresent(): boolean {
118
+ return typeof window !== 'undefined' && Boolean(window['ROKT_DOMAIN']);
119
+ }
120
+
121
+ private isFeatureFlagEnabled = (): boolean => this.isLoggingEnabled;
122
+
123
+ private isDebugModeEnabled(): boolean {
124
+ return (
125
+ typeof window !== 'undefined' &&
126
+ (window.
127
+ location?.
128
+ search?.
129
+ toLowerCase()?.
130
+ includes('mp_enable_logging=true') ?? false)
131
+ );
132
+ }
133
+
134
+ private canSendLog(severity: WSDKErrorSeverity): boolean {
135
+ return this.isEnabled && !this.isRateLimited(severity);
136
+ }
137
+
138
+ private isRateLimited(severity: WSDKErrorSeverity): boolean {
139
+ return this.rateLimiter.incrementAndCheck(severity);
140
+ }
141
+
142
+ private getUrl(): string | undefined {
143
+ return typeof window !== 'undefined' ? window.location?.href : undefined;
144
+ }
145
+
146
+ private getUserAgent(): string | undefined {
147
+ return typeof window !== 'undefined' ? window.navigator?.userAgent : undefined;
148
+ }
149
+
150
+ private getHeaders(): IReportingLoggerPayload['headers'] {
151
+ const headers: IReportingLoggerPayload['headers'] = {
152
+ [HEADER_ACCEPT]: 'text/plain;charset=UTF-8',
153
+ [HEADER_CONTENT_TYPE]: 'application/json',
154
+ [HEADER_ROKT_LAUNCHER_VERSION]: this.getVersion(),
155
+ [HEADER_ROKT_WSDK_VERSION]: 'joint',
156
+ };
157
+
158
+ if (this.launcherInstanceGuid) {
159
+ headers[HEADER_ROKT_LAUNCHER_INSTANCE_GUID] = this.launcherInstanceGuid;
160
+ }
161
+
162
+ const accountId = this.store?.getRoktAccountId?.();
163
+ if (accountId) {
164
+ headers['rokt-account-id'] = accountId;
165
+ }
166
+
167
+ return headers;
168
+ }
169
+ }
170
+
171
+ export interface IRateLimiter {
172
+ incrementAndCheck(severity: WSDKErrorSeverity): boolean;
173
+ }
174
+
175
+ export class RateLimiter implements IRateLimiter {
176
+ private readonly rateLimits: Map<WSDKErrorSeverity, number> = new Map([
177
+ [WSDKErrorSeverity.ERROR, 10],
178
+ [WSDKErrorSeverity.WARNING, 10],
179
+ [WSDKErrorSeverity.INFO, 10],
180
+ ]);
181
+ private logCount: Map<WSDKErrorSeverity, number> = new Map();
182
+
183
+ public incrementAndCheck(severity: WSDKErrorSeverity): boolean {
184
+ const count = this.logCount.get(severity) || 0;
185
+ const limit = this.rateLimits.get(severity) || 10;
186
+
187
+ const newCount = count + 1;
188
+ this.logCount.set(severity, newCount);
189
+
190
+ return newCount > limit;
191
+ }
192
+ }
@@ -0,0 +1,32 @@
1
+ import { valueof } from '../utils';
2
+
3
+ export const ErrorCodes = {
4
+ UNKNOWN_ERROR: 'UNKNOWN_ERROR',
5
+ UNHANDLED_EXCEPTION: 'UNHANDLED_EXCEPTION',
6
+ IDENTITY_REQUEST: 'IDENTITY_REQUEST',
7
+ } as const;
8
+
9
+ export type ErrorCodes = valueof<typeof ErrorCodes>;
10
+
11
+ export type ErrorCode = ErrorCodes | string;
12
+
13
+ export const WSDKErrorSeverity = {
14
+ ERROR: 'ERROR',
15
+ INFO: 'INFO',
16
+ WARNING: 'WARNING',
17
+ } as const;
18
+
19
+ export type WSDKErrorSeverity = (typeof WSDKErrorSeverity)[keyof typeof WSDKErrorSeverity];
20
+
21
+ export type ErrorsRequestBody = {
22
+ additionalInformation?: Record<string, string>;
23
+ code: ErrorCode;
24
+ severity: WSDKErrorSeverity;
25
+ stackTrace?: string;
26
+ deviceInfo?: string;
27
+ integration?: string;
28
+ reporter?: string;
29
+ url?: string;
30
+ };
31
+
32
+ export type LogRequestBody = ErrorsRequestBody;
@@ -52,6 +52,7 @@ import ForegroundTimer from './foregroundTimeTracker';
52
52
  import RoktManager, { IRoktOptions } from './roktManager';
53
53
  import filteredMparticleUser from './filteredMparticleUser';
54
54
  import CookieConsentManager, { ICookieConsentManager } from './cookieConsentManager';
55
+ import { ReportingLogger } from './logging/reportingLogger';
55
56
 
56
57
  export interface IErrorLogMessage {
57
58
  message?: string;
@@ -84,15 +85,18 @@ export interface IMParticleWebSDKInstance extends MParticleWebSDK {
84
85
  _NativeSdkHelpers: INativeSdkHelpers;
85
86
  _Persistence: IPersistence;
86
87
  _CookieConsentManager: ICookieConsentManager;
88
+ _ReportingLogger: ReportingLogger;
87
89
  _RoktManager: RoktManager;
88
90
  _SessionManager: ISessionManager;
89
91
  _ServerModel: IServerModel;
90
92
  _Store: IStore;
91
93
  _instanceName: string;
92
94
  _preInit: IPreInit;
93
- _timeOnSiteTimer: ForegroundTimer;
95
+ _timeOnSiteTimer: ForegroundTimer;
94
96
  setLauncherInstanceGuid: () => void;
97
+ getLauncherInstanceGuid: () => string;
95
98
  captureTiming(metricName: string);
99
+ processQueueOnIdentityFailure?: () => void;
96
100
  }
97
101
 
98
102
  const { Messages, HTTPCodes, FeatureFlags, CaptureIntegrationSpecificIdsV2Modes } = Constants;
@@ -132,7 +136,26 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
132
136
  integrationDelays: {},
133
137
  forwarderConstructors: [],
134
138
  };
139
+
135
140
  this._RoktManager = new RoktManager();
141
+
142
+ this._RoktManager.setOnReadyCallback(() => {
143
+ self.processQueueOnIdentityFailure();
144
+ });
145
+
146
+ /**
147
+ * Processes message and ready queue when identity requests fails but Rokt is ready.
148
+ */
149
+ this.processQueueOnIdentityFailure = function() {
150
+ if (self._Store?.isInitialized) {
151
+ return;
152
+ }
153
+
154
+ if (self._Store?.identityCallFailed && self._RoktManager?.isReady()) {
155
+ self._RoktManager.processMessageQueue();
156
+ self._preInit.readyQueue = processReadyQueue(self._preInit.readyQueue);
157
+ }
158
+ };
136
159
 
137
160
  // required for forwarders once they reference the mparticle instance
138
161
  this.IdentityType = IdentityType;
@@ -225,12 +248,14 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
225
248
  }
226
249
  };
227
250
 
228
- this._resetForTests = function(config, keepPersistence, instance) {
251
+ this._resetForTests = function(config, keepPersistence, instance, reportingLogger?: ReportingLogger) {
229
252
  if (instance._Store) {
230
253
  delete instance._Store;
231
254
  }
232
- instance.Logger = new Logger(config);
255
+ instance.Logger = new Logger(config, reportingLogger);
233
256
  instance._Store = new Store(config, instance);
257
+ // Update ReportingLogger with the new Store reference to avoid stale data
258
+ reportingLogger?.setStore(instance._Store);
234
259
  instance._Store.isLocalStorageAvailable = instance._Persistence.determineLocalStorageAvailability(
235
260
  window.localStorage
236
261
  );
@@ -249,12 +274,18 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
249
274
  };
250
275
  };
251
276
  /**
252
- * A callback method that is invoked after mParticle is initialized.
277
+ * Executes callback when the SDK is ready, or Rokt is ready but identify requests fail due to server errors.
278
+ * This ensures Rokt methods like selectPlacements continue to work during backend outages.
253
279
  * @method ready
254
- * @param {Function} function A function to be called after mParticle is initialized
280
+ * @param {Function} f Callback to execute
255
281
  */
256
282
  this.ready = function(f) {
257
- if (self.isInitialized() && typeof f === 'function') {
283
+ const shouldExecute = isFunction(f) && (
284
+ self._Store?.isInitialized ||
285
+ (self._Store?.identityCallFailed && self._RoktManager.isReady())
286
+ );
287
+
288
+ if (shouldExecute) {
258
289
  f();
259
290
  } else {
260
291
  self._preInit.readyQueue.push(f);
@@ -1346,7 +1377,7 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
1346
1377
  };
1347
1378
  }
1348
1379
  };
1349
-
1380
+
1350
1381
  const launcherInstanceGuidKey = Constants.Rokt.LauncherInstanceGuidKey;
1351
1382
  this.setLauncherInstanceGuid = function() {
1352
1383
  if (!window[launcherInstanceGuidKey]
@@ -1355,6 +1386,10 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
1355
1386
  }
1356
1387
  };
1357
1388
 
1389
+ this.getLauncherInstanceGuid = function() {
1390
+ return window[launcherInstanceGuidKey];
1391
+ };
1392
+
1358
1393
  this.captureTiming = function(metricsName) {
1359
1394
  if (typeof window !== 'undefined' && window.performance?.mark) {
1360
1395
  window.performance.mark(metricsName);
@@ -1420,6 +1455,8 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
1420
1455
  // Configure Rokt Manager with user and filtered user
1421
1456
  const roktConfig: IKitConfigs = parseConfig(config, 'Rokt', 181);
1422
1457
  if (roktConfig) {
1458
+ const accountId = roktConfig.settings?.accountId;
1459
+ mpInstance._Store.setRoktAccountId(accountId);
1423
1460
  const { userAttributeFilters } = roktConfig;
1424
1461
  const roktFilteredUser = filteredMparticleUser(
1425
1462
  currentUserMPID,
@@ -1553,9 +1590,16 @@ function createIdentityCache(mpInstance) {
1553
1590
  }
1554
1591
 
1555
1592
  function runPreConfigFetchInitialization(mpInstance, apiKey, config) {
1556
- mpInstance.Logger = new Logger(config);
1593
+ mpInstance._ReportingLogger = new ReportingLogger(
1594
+ config,
1595
+ Constants.sdkVersion,
1596
+ undefined,
1597
+ mpInstance.getLauncherInstanceGuid(),
1598
+ );
1599
+ mpInstance.Logger = new Logger(config, mpInstance._ReportingLogger);
1557
1600
  mpInstance._Store = new Store(config, mpInstance, apiKey);
1558
1601
  window.mParticle.Store = mpInstance._Store;
1602
+ mpInstance._ReportingLogger.setStore(mpInstance._Store);
1559
1603
  mpInstance.Logger.verbose(StartingInitialization);
1560
1604
 
1561
1605
  // Initialize CookieConsentManager with privacy flags from launcherOptions
@@ -1620,9 +1664,12 @@ function processIdentityCallback(
1620
1664
  }
1621
1665
 
1622
1666
  function queueIfNotInitialized(func, self) {
1623
- if (!self.isInitialized()) {
1624
- self.ready(function() {
1625
- func();
1667
+ // Core SDK methods must wait for Store initialization
1668
+ if (!self._Store?.isInitialized) {
1669
+ self._preInit.readyQueue.push(function() {
1670
+ if (self._Store?.isInitialized) {
1671
+ func();
1672
+ }
1626
1673
  });
1627
1674
  return true;
1628
1675
  }
@@ -59,6 +59,12 @@ export interface RoktKitFilterSettings {
59
59
  filteredUser?: IMParticleUser | null;
60
60
  }
61
61
 
62
+ export interface IRoktKitSettings {
63
+ accountId?: string;
64
+ placementAttributesMapping?: string;
65
+ hashedEmailUserIdentityType?: string;
66
+ }
67
+
62
68
  export interface IRoktKit {
63
69
  filters: RoktKitFilterSettings;
64
70
  filteredUser: IMParticleUser | null;
@@ -69,6 +75,8 @@ export interface IRoktKit {
69
75
  setExtensionData<T>(extensionData: IRoktPartnerExtensionData<T>): void;
70
76
  use: <T>(name: string) => Promise<T>;
71
77
  launcherOptions?: Dictionary<any>;
78
+ settings?: IRoktKitSettings;
79
+ integrationName?: string;
72
80
  }
73
81
 
74
82
  export interface IRoktOptions {
@@ -102,7 +110,16 @@ export default class RoktManager {
102
110
  private domain?: string;
103
111
  private mappedEmailShaIdentityType?: string | null;
104
112
  private captureTiming?: (metricsName: string) => void;
113
+ private onReadyCallback: (() => void) | null = null;
105
114
  private initialized: boolean = false;
115
+
116
+ /**
117
+ * Sets a callback to be invoked when RoktManager becomes ready
118
+ */
119
+ public setOnReadyCallback(callback: () => void): void {
120
+ this.onReadyCallback = callback;
121
+ }
122
+
106
123
  /**
107
124
  * Initializes the RoktManager with configuration settings and user data.
108
125
  *
@@ -133,6 +150,8 @@ export default class RoktManager {
133
150
  this.logger = logger;
134
151
  this.captureTiming = captureTiming;
135
152
 
153
+ this.captureTiming?.(PerformanceMarkType.JointSdkRoktKitInit);
154
+
136
155
  this.filters = {
137
156
  userAttributeFilters,
138
157
  filterUserAttributes: KitFilterHelper.filterUserAttributes,
@@ -170,7 +189,22 @@ export default class RoktManager {
170
189
 
171
190
  public attachKit(kit: IRoktKit): void {
172
191
  this.kit = kit;
192
+
193
+ if (kit.settings?.accountId) {
194
+ this.store.setRoktAccountId(kit.settings.accountId);
195
+ }
196
+
197
+ if (kit.integrationName) {
198
+ this.store?.setIntegrationName(kit.integrationName);
199
+ }
200
+
173
201
  this.processMessageQueue();
202
+
203
+ try {
204
+ this.onReadyCallback?.();
205
+ } catch (e) {
206
+ this.logger?.error('RoktManager: Error in onReadyCallback: ' + e);
207
+ }
174
208
  }
175
209
 
176
210
  /**
@@ -189,9 +223,7 @@ export default class RoktManager {
189
223
  * });
190
224
  */
191
225
  public async selectPlacements(options: IRoktSelectPlacementsOptions): Promise<IRoktSelection> {
192
- if (this.captureTiming) {
193
- this.captureTiming(PerformanceMarkType.JointSdkSelectPlacements);
194
- }
226
+ this.captureTiming?.(PerformanceMarkType.JointSdkSelectPlacements);
195
227
 
196
228
  // Queue if kit isn't ready OR if identity is in flight
197
229
  if (!this.isReady() || this.store?.identityCallInFlight) {
@@ -406,7 +438,7 @@ export default class RoktManager {
406
438
  this.store.setLocalSessionAttribute(key, value);
407
439
  }
408
440
 
409
- private isReady(): boolean {
441
+ public isReady(): boolean {
410
442
  // The Rokt Manager is ready when a kit is attached and has a launcher
411
443
  return Boolean(this.kit && this.kit.launcher);
412
444
  }
@@ -453,7 +485,7 @@ export default class RoktManager {
453
485
  }
454
486
  }
455
487
 
456
- private processMessageQueue(): void {
488
+ public processMessageQueue(): void {
457
489
  if (!this.isReady() || this.messageQueue.size === 0) {
458
490
  return;
459
491
  }
@@ -573,4 +605,4 @@ export default class RoktManager {
573
605
 
574
606
  return false; // Values are the same
575
607
  }
576
- }
608
+ }