@mparticle/web-sdk 2.57.0 → 2.59.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/dist/mparticle.common.js +98 -69
- package/dist/mparticle.esm.js +98 -69
- package/dist/mparticle.js +532 -218
- package/package.json +1 -1
- package/src/apiClient.ts +42 -4
- package/src/batchUploader.ts +4 -1
- package/src/constants.ts +4 -0
- package/src/cookieSyncManager.ts +6 -0
- package/src/foregroundTimeTracker.ts +5 -3
- package/src/identity-utils.ts +24 -1
- package/src/identityApiClient.ts +7 -3
- package/src/logger.ts +14 -5
- package/src/logging/reportingLogger.ts +192 -0
- package/src/logging/types.ts +32 -0
- package/src/mp-instance.ts +88 -24
- package/src/persistence.js +15 -0
- package/src/roktManager.ts +18 -1
- package/src/sdkRuntimeModels.ts +47 -17
- package/src/sessionManager.ts +11 -2
- package/src/store.ts +31 -11
- package/src/uploaders.ts +1 -0
- package/src/vault.ts +21 -0
package/package.json
CHANGED
package/src/apiClient.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { IMParticleUser, ISDKUserAttributes } from './identity-user-interfaces';
|
|
|
10
10
|
import { AsyncUploader, FetchUploader, XHRUploader } from './uploaders';
|
|
11
11
|
import { IMParticleWebSDKInstance } from './mp-instance';
|
|
12
12
|
import { appendUserInfo } from './user-utils';
|
|
13
|
+
import { hasExplicitIdentifier } from './identity-utils';
|
|
13
14
|
|
|
14
15
|
export interface IAPIClient {
|
|
15
16
|
uploader: BatchUploader | null;
|
|
@@ -88,6 +89,35 @@ export default function APIClient(
|
|
|
88
89
|
});
|
|
89
90
|
};
|
|
90
91
|
|
|
92
|
+
// When noFunctional is set and there are no identities passed, the SDK will not fully initialize.
|
|
93
|
+
// In this case, there will be no MPID, but we still want kits to initialize and forward the event to kits.
|
|
94
|
+
// The original event is queued for the MP server upload path so it can be sent once an MPID is returned.
|
|
95
|
+
// Returns true if the event was handled by this path (caller should return early).
|
|
96
|
+
const handleNoFunctionalPreMpidEvent = (event: SDKEvent, mpid: string | undefined): boolean => {
|
|
97
|
+
const noFunctionalWithoutId =
|
|
98
|
+
mpInstance._CookieConsentManager?.getNoFunctional() &&
|
|
99
|
+
!hasExplicitIdentifier(mpInstance._Store);
|
|
100
|
+
|
|
101
|
+
if (!noFunctionalWithoutId || mpid || !mpInstance._Store.configurationLoaded || mpInstance._Store.requireDelay) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let forwarderEvent = event;
|
|
106
|
+
if (kitBlocker?.kitBlockingEnabled) {
|
|
107
|
+
forwarderEvent = kitBlocker.createBlockedEvent(event);
|
|
108
|
+
}
|
|
109
|
+
if (forwarderEvent) {
|
|
110
|
+
mpInstance._Forwarders.sendEventToForwarders(forwarderEvent);
|
|
111
|
+
event.AlreadySentToForwarders = true;
|
|
112
|
+
}
|
|
113
|
+
mpInstance.Logger.verbose(
|
|
114
|
+
'noFunctional event forwarded to kits and queued for MP server upload when MPID is available.'
|
|
115
|
+
);
|
|
116
|
+
mpInstance._Store.eventQueue.push(event);
|
|
117
|
+
|
|
118
|
+
return true;
|
|
119
|
+
};
|
|
120
|
+
|
|
91
121
|
this.sendEventToServer = function(event, _options) {
|
|
92
122
|
const defaultOptions = {
|
|
93
123
|
shouldUploadEvent: true,
|
|
@@ -112,8 +142,14 @@ export default function APIClient(
|
|
|
112
142
|
mpInstance._Store.integrationDelayTimeoutStart,
|
|
113
143
|
Date.now()
|
|
114
144
|
);
|
|
115
|
-
|
|
116
|
-
|
|
145
|
+
|
|
146
|
+
if (handleNoFunctionalPreMpidEvent(event, mpid)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// We queue events if there is no MPID (MPID is null, or === 0), or there are integrations that
|
|
151
|
+
// require this to stall because integration attributes need to be set, or if we are still
|
|
152
|
+
// fetching the config (self hosted only), and so require delaying events
|
|
117
153
|
if (
|
|
118
154
|
!mpid ||
|
|
119
155
|
mpInstance._Store.requireDelay ||
|
|
@@ -146,8 +182,10 @@ export default function APIClient(
|
|
|
146
182
|
}
|
|
147
183
|
|
|
148
184
|
// We need to check event again, because kitblocking
|
|
149
|
-
// can nullify the event
|
|
150
|
-
if
|
|
185
|
+
// can nullify the event.
|
|
186
|
+
// Skip if forwarders were already called in the noFunctional pre-MPID path
|
|
187
|
+
// to prevent double-sending when the event queue is later flushed.
|
|
188
|
+
if (event && !event.AlreadySentToForwarders) {
|
|
151
189
|
mpInstance._Forwarders.sendEventToForwarders(event);
|
|
152
190
|
}
|
|
153
191
|
}
|
package/src/batchUploader.ts
CHANGED
|
@@ -74,7 +74,10 @@ export class BatchUploader {
|
|
|
74
74
|
// so that we don't have to check it every time
|
|
75
75
|
this.offlineStorageEnabled = this.isOfflineStorageAvailable();
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
// When noFunctional is true, prevent events/batches storage
|
|
78
|
+
const noFunctional = mpInstance._CookieConsentManager?.getNoFunctional();
|
|
79
|
+
|
|
80
|
+
if (this.offlineStorageEnabled && !noFunctional) {
|
|
78
81
|
this.eventVault = new SessionStorageVault<SDKEvent[]>(
|
|
79
82
|
`${mpInstance._Store.storageName}-events`,
|
|
80
83
|
{
|
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,
|
package/src/cookieSyncManager.ts
CHANGED
|
@@ -73,6 +73,12 @@ export default function CookieSyncManager(
|
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// When noFunctional is true, persistence is not saved, so we cannot track cookie sync
|
|
77
|
+
// dates. Skip cookie sync to avoid running it on every page load.
|
|
78
|
+
if (mpInstance._CookieConsentManager?.getNoFunctional()) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
76
82
|
const persistence = mpInstance._Persistence.getPersistence();
|
|
77
83
|
|
|
78
84
|
if (isEmpty(persistence)) {
|
|
@@ -8,10 +8,12 @@ export default class ForegroundTimeTracker {
|
|
|
8
8
|
public startTime: number = 0;
|
|
9
9
|
public totalTime: number = 0;
|
|
10
10
|
|
|
11
|
-
constructor(timerKey: string) {
|
|
11
|
+
constructor(timerKey: string, private noFunctional: boolean = false) {
|
|
12
12
|
this.localStorageName = `mprtcl-tos-${timerKey}`;
|
|
13
13
|
this.timerVault = new LocalStorageVault<number>(this.localStorageName);
|
|
14
|
-
this.
|
|
14
|
+
if (!this.noFunctional) {
|
|
15
|
+
this.loadTimeFromStorage();
|
|
16
|
+
}
|
|
15
17
|
this.addHandlers();
|
|
16
18
|
if (document.hidden === false) {
|
|
17
19
|
this.startTracking();
|
|
@@ -63,7 +65,7 @@ export default class ForegroundTimeTracker {
|
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
public updateTimeInPersistence(): void {
|
|
66
|
-
if (this.isTrackerActive) {
|
|
68
|
+
if (this.isTrackerActive && !this.noFunctional) {
|
|
67
69
|
this.timerVault.store(Math.round(this.totalTime));
|
|
68
70
|
}
|
|
69
71
|
}
|
package/src/identity-utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Constants, { ONE_DAY_IN_SECONDS, MILLIS_IN_ONE_SEC } from './constants';
|
|
2
|
-
import { Dictionary, parseNumber, isObject, generateHash } from './utils';
|
|
2
|
+
import { Dictionary, parseNumber, isObject, generateHash, isEmpty } from './utils';
|
|
3
3
|
import { BaseVault } from './vault';
|
|
4
4
|
import Types from './types';
|
|
5
5
|
import {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
IIdentityResponse,
|
|
14
14
|
IMParticleUser,
|
|
15
15
|
} from './identity-user-interfaces';
|
|
16
|
+
import { IStore } from './store';
|
|
16
17
|
|
|
17
18
|
const { Identify, Modify, Login, Logout } = Constants.IdentityMethods;
|
|
18
19
|
export const CACHE_HEADER = 'x-mp-max-age' as const;
|
|
@@ -298,3 +299,25 @@ export const hasIdentityRequestChanged = (
|
|
|
298
299
|
JSON.stringify(currentUserIdentities) !== JSON.stringify(newIdentities)
|
|
299
300
|
);
|
|
300
301
|
};
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Checks if deviceId or other user identifiers (like email) were explicitly provided
|
|
305
|
+
* by the partner via config.deviceId or config.identifyRequest.userIdentities.
|
|
306
|
+
* When noFunctional is true, then cookies are blocked, so the partner must explicitly
|
|
307
|
+
* pass deviceId or other identifiers to prevent new users from being created on each page load.
|
|
308
|
+
*
|
|
309
|
+
* @param store - The SDK store (provides SDKConfig.deviceId and SDKConfig.identifyRequest.userIdentities)
|
|
310
|
+
* @returns true if deviceId or other identifiers were explicitly provided in config, false otherwise
|
|
311
|
+
*/
|
|
312
|
+
export const hasExplicitIdentifier = (store: IStore | undefined | null): boolean => {
|
|
313
|
+
const userIdentities = store?.SDKConfig?.identifyRequest?.userIdentities;
|
|
314
|
+
if (
|
|
315
|
+
userIdentities &&
|
|
316
|
+
isObject(userIdentities) &&
|
|
317
|
+
!isEmpty(userIdentities) &&
|
|
318
|
+
Object.values(userIdentities).some(Boolean)
|
|
319
|
+
) {
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
return !!store?.SDKConfig?.deviceId;
|
|
323
|
+
};
|
package/src/identityApiClient.ts
CHANGED
|
@@ -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
|
|
|
@@ -326,10 +327,13 @@ export default function IdentityAPIClient(
|
|
|
326
327
|
const requestCount = mpInstance._Store.identifyRequestCount;
|
|
327
328
|
mpInstance.captureTiming(`${requestCount}-identityRequestEnd`);
|
|
328
329
|
}
|
|
329
|
-
|
|
330
|
+
|
|
330
331
|
const errorMessage = (err as Error).message || err.toString();
|
|
331
|
-
Logger.error(
|
|
332
|
-
|
|
332
|
+
Logger.error(
|
|
333
|
+
'Error sending identity request to servers' + ' - ' + errorMessage,
|
|
334
|
+
ErrorCodes.IDENTITY_REQUEST
|
|
335
|
+
);
|
|
336
|
+
|
|
333
337
|
mpInstance.processQueueOnIdentityFailure?.();
|
|
334
338
|
invokeCallback(callback, HTTPCodes.noHttpCoverage, errorMessage);
|
|
335
339
|
}
|
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;
|