@mparticle/web-sdk 2.79.3 → 2.80.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 +146 -87
- package/dist/mparticle.esm.js +146 -87
- package/dist/mparticle.js +1427 -1251
- package/dist/types/src/pageViewTracker.d.ts +12 -5
- package/dist/types/src/persistence.d.ts +3 -44
- package/dist/types/src/persistence.interfaces.d.ts +10 -10
- package/dist/types/src/sdkRuntimeModels.d.ts +1 -0
- package/dist/types/src/store.d.ts +1 -0
- package/package.json +1 -1
- package/src/events.ts +9 -1
- package/src/pageViewTracker.ts +176 -42
- package/src/persistence.interfaces.ts +10 -10
- package/src/persistence.ts +1210 -0
- package/src/sdkRuntimeModels.ts +1 -0
- package/src/store.ts +1 -0
- package/src/persistence.js +0 -985
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import { IMParticleWebSDKInstance } from './mp-instance';
|
|
2
2
|
import { BaseEvent } from './sdkRuntimeModels';
|
|
3
|
+
import { Dictionary } from './utils';
|
|
3
4
|
type HistoryMethodName = 'pushState' | 'replaceState';
|
|
4
5
|
type NavigationSource = HistoryMethodName | 'popstate';
|
|
5
6
|
export declare const WIN_APV_KEY = "__mpApv__";
|
|
6
|
-
|
|
7
|
+
export declare const ALLOWED_QUERY_PARAMS: string[];
|
|
8
|
+
interface IPageSnapshot {
|
|
9
|
+
path: string;
|
|
10
|
+
params: Dictionary<string>;
|
|
11
|
+
}
|
|
12
|
+
interface IPageViewData extends IPageSnapshot {
|
|
7
13
|
hostname: string;
|
|
8
14
|
title: string;
|
|
9
|
-
path: string;
|
|
10
15
|
}
|
|
11
|
-
export declare const
|
|
16
|
+
export declare const allowedQueryParams: (href: string) => Dictionary<string>;
|
|
17
|
+
export declare const pageKey: (page: IPageSnapshot) => string;
|
|
18
|
+
export declare const isNewPage: (lastKey: string | null, candidateKey: string) => boolean;
|
|
12
19
|
export declare const supportsHistoryTracking: (win: Window | null) => boolean;
|
|
13
|
-
export declare const buildPageViewEvent: (
|
|
20
|
+
export declare const buildPageViewEvent: ({ params, hostname, title, path, }: IPageViewData) => BaseEvent;
|
|
14
21
|
export declare const getActiveTracker: () => PageViewTracker | undefined;
|
|
15
22
|
export declare const hasInitialPageViewFired: () => boolean;
|
|
16
23
|
export declare const markInitialPageViewFired: () => void;
|
|
@@ -18,7 +25,7 @@ export declare const resetPageViewTracking: () => void;
|
|
|
18
25
|
export declare const patchHistory: (onNavigate: (source: NavigationSource) => void, log: (message: string) => void) => (() => void) | null;
|
|
19
26
|
export declare class PageViewTracker {
|
|
20
27
|
private readonly mpInstance;
|
|
21
|
-
private
|
|
28
|
+
private lastPage;
|
|
22
29
|
private active;
|
|
23
30
|
private pendingNavigations;
|
|
24
31
|
private undoHistoryPatch;
|
|
@@ -1,44 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
useLocalStorage: () => any;
|
|
5
|
-
initializeStorage: () => void;
|
|
6
|
-
update: () => void;
|
|
7
|
-
storeDataInMemory: (obj: any, currentMPID: any) => void;
|
|
8
|
-
determineLocalStorageAvailability: (storage: any) => any;
|
|
9
|
-
setLocalStorage: () => void;
|
|
10
|
-
getLocalStorage: () => {};
|
|
11
|
-
expireCookies: (cookieName: any) => void;
|
|
12
|
-
getCookie: () => any;
|
|
13
|
-
setCookie: () => void;
|
|
14
|
-
reduceAndEncodePersistence: (persistence: any, expires: any, domain: any, maxCookieSize: any) => string;
|
|
15
|
-
findPrevCookiesBasedOnUI: (identityApiData: any) => void;
|
|
16
|
-
encodePersistence: (persistence: any) => string;
|
|
17
|
-
decodePersistence: (persistence: any) => string;
|
|
18
|
-
getCookieDomain: () => any;
|
|
19
|
-
getDomain: (doc: any, locationHostname: any) => any;
|
|
20
|
-
saveUserCookieSyncDatesToPersistence: (mpid: any, csd: any) => void;
|
|
21
|
-
swapCurrentUser: (previousMPID: any, currentMPID: any, currentSessionMPIDs: any) => void;
|
|
22
|
-
savePersistence: (persistence: any) => void;
|
|
23
|
-
getPersistence: () => any;
|
|
24
|
-
getFirstSeenTime: (mpid: any) => any;
|
|
25
|
-
/**
|
|
26
|
-
* set the "first seen" time for a user. the time will only be set once for a given
|
|
27
|
-
* mpid after which subsequent calls will be ignored
|
|
28
|
-
*/
|
|
29
|
-
setFirstSeenTime: (mpid: any, time: any) => void;
|
|
30
|
-
/**
|
|
31
|
-
* returns the "last seen" time for a user. If the mpid represents the current user, the
|
|
32
|
-
* return value will always be the current time, otherwise it will be to stored "last seen"
|
|
33
|
-
* time
|
|
34
|
-
*/
|
|
35
|
-
getLastSeenTime: (mpid: any) => any;
|
|
36
|
-
setLastSeenTime: (mpid: any, time: any) => void;
|
|
37
|
-
getDeviceId: () => any;
|
|
38
|
-
setDeviceId: (guid: any) => void;
|
|
39
|
-
resetPersistence: () => void;
|
|
40
|
-
forwardingStatsBatches: {
|
|
41
|
-
uploadsTable: {};
|
|
42
|
-
forwardingStatsEventQueue: any[];
|
|
43
|
-
};
|
|
44
|
-
}
|
|
1
|
+
import { IMParticleWebSDKInstance } from './mp-instance';
|
|
2
|
+
import { IPersistence } from './persistence.interfaces';
|
|
3
|
+
export default function _Persistence(this: IPersistence, mpInstance: IMParticleWebSDKInstance): void;
|
|
@@ -30,7 +30,7 @@ export interface IGlobalStoreV2MinifiedKeys {
|
|
|
30
30
|
export interface IPersistenceMinified extends Dictionary {
|
|
31
31
|
cu: MPID;
|
|
32
32
|
gs: IGlobalStoreV2MinifiedKeys;
|
|
33
|
-
l: boolean;
|
|
33
|
+
l: boolean | 0 | 1;
|
|
34
34
|
}
|
|
35
35
|
export interface IUserPersistenceMinified extends Dictionary {
|
|
36
36
|
csd: CookieSyncDates;
|
|
@@ -45,8 +45,8 @@ export interface IPersistence {
|
|
|
45
45
|
useLocalStorage(): boolean;
|
|
46
46
|
initializeStorage(): void;
|
|
47
47
|
update(): void;
|
|
48
|
-
storeDataInMemory(obj: IPersistenceMinified, currentMPID
|
|
49
|
-
determineLocalStorageAvailability(storage
|
|
48
|
+
storeDataInMemory(obj: IPersistenceMinified, currentMPID?: MPID): void;
|
|
49
|
+
determineLocalStorageAvailability(storage?: Storage): boolean;
|
|
50
50
|
setLocalStorage(): void;
|
|
51
51
|
getLocalStorage(): IPersistenceMinified | null;
|
|
52
52
|
expireCookies(cookieName: string): void;
|
|
@@ -54,17 +54,17 @@ export interface IPersistence {
|
|
|
54
54
|
setCookie(): void;
|
|
55
55
|
reduceAndEncodePersistence(persistence: IPersistenceMinified, expires: string, domain: string, maxCookieSize: number): string;
|
|
56
56
|
findPrevCookiesBasedOnUI(identityApiData: IdentityApiData): void;
|
|
57
|
-
encodePersistence(persistence:
|
|
58
|
-
decodePersistence(persistenceString: string): string;
|
|
57
|
+
encodePersistence(persistence: string): string;
|
|
58
|
+
decodePersistence(persistenceString: string | null): string | void;
|
|
59
59
|
getCookieDomain(): string;
|
|
60
|
-
getDomain(doc:
|
|
60
|
+
getDomain(doc: Document, locationHostname: string): string;
|
|
61
61
|
saveUserCookieSyncDatesToPersistence(mpid: MPID, csd: CookieSyncDates): void;
|
|
62
62
|
savePersistence(persistance: IPersistenceMinified): void;
|
|
63
|
-
getPersistence(): IPersistenceMinified;
|
|
64
|
-
getFirstSeenTime(mpid: MPID):
|
|
65
|
-
setFirstSeenTime(mpid: MPID, time
|
|
63
|
+
getPersistence(): IPersistenceMinified | null;
|
|
64
|
+
getFirstSeenTime(mpid: MPID): number | null;
|
|
65
|
+
setFirstSeenTime(mpid: MPID, time?: number): void;
|
|
66
66
|
getLastSeenTime(mpid: MPID): number | null;
|
|
67
|
-
setLastSeenTime(mpid: MPID, time
|
|
67
|
+
setLastSeenTime(mpid: MPID, time?: number): void;
|
|
68
68
|
getDeviceId(): string;
|
|
69
69
|
setDeviceId(guid: string): void;
|
|
70
70
|
resetPersistence(): void;
|
|
@@ -197,6 +197,7 @@ export interface IMParticleInstanceManager extends MParticleWebSDK {
|
|
|
197
197
|
_BatchValidator: _BatchValidator;
|
|
198
198
|
_instances: Dictionary<IMParticleWebSDKInstance>;
|
|
199
199
|
_isTestEnv?: boolean;
|
|
200
|
+
_forceNoLocalStorage?: boolean;
|
|
200
201
|
config: SDKInitConfig;
|
|
201
202
|
isIOS?: boolean;
|
|
202
203
|
MPSideloadedKit: typeof MPSideloadedKit;
|
|
@@ -29,6 +29,7 @@ export interface SDKConfig {
|
|
|
29
29
|
kits: Dictionary<UnregisteredKit>;
|
|
30
30
|
logLevel?: LogLevelType;
|
|
31
31
|
cookieDomain?: string;
|
|
32
|
+
cookieExpiration?: number;
|
|
32
33
|
maxCookieSize?: number | undefined;
|
|
33
34
|
minWebviewBridgeVersion: 1 | 2 | undefined;
|
|
34
35
|
identifyRequest: IdentifyRequest;
|
package/package.json
CHANGED
package/src/events.ts
CHANGED
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
SDKEventOptions,
|
|
21
21
|
TransactionAttributes,
|
|
22
22
|
} from '@mparticle/web-sdk';
|
|
23
|
-
import { valueof } from './utils';
|
|
23
|
+
import { getHref, valueof } from './utils';
|
|
24
|
+
import { allowedQueryParams } from './pageViewTracker';
|
|
24
25
|
|
|
25
26
|
interface DOMHandlerElement extends HTMLElement {
|
|
26
27
|
href?: string;
|
|
@@ -144,11 +145,18 @@ export default function Events(
|
|
|
144
145
|
self.logEvent({ messageType: Types.MessageType.AppStateTransition });
|
|
145
146
|
};
|
|
146
147
|
|
|
148
|
+
// The auto page view for the landing page. The SPA navigations that follow it
|
|
149
|
+
// come from PageViewTracker instead, so both emitters attach the same
|
|
150
|
+
// allowlisted query params — and this is the one that matters for campaign
|
|
151
|
+
// attribution, since utm_*/gclid live on the entry URL.
|
|
147
152
|
this.logPageView = function(): void {
|
|
148
153
|
self.logEvent({
|
|
149
154
|
messageType: Types.MessageType.PageView,
|
|
150
155
|
name: 'PageView',
|
|
151
156
|
data: {
|
|
157
|
+
// Params first so the core fields always win. See
|
|
158
|
+
// buildPageViewEvent, which does the same for SPA views.
|
|
159
|
+
...allowedQueryParams(getHref()),
|
|
152
160
|
hostname: window.location.hostname,
|
|
153
161
|
title: window.document.title,
|
|
154
162
|
},
|
package/src/pageViewTracker.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IMParticleWebSDKInstance } from './mp-instance';
|
|
2
2
|
import { BaseEvent } from './sdkRuntimeModels';
|
|
3
3
|
import { EventType, MessageType } from './types';
|
|
4
|
+
import { Dictionary, getHref, queryStringParser } from './utils';
|
|
4
5
|
|
|
5
6
|
type HistoryStateMethod = History['pushState'];
|
|
6
7
|
type HistoryMethodName = 'pushState' | 'replaceState';
|
|
@@ -41,14 +42,71 @@ type WindowWithApv = Window & {
|
|
|
41
42
|
[WIN_APV_KEY]?: IApvState;
|
|
42
43
|
};
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
// The query params an auto page view may carry. An allowlist, not a denylist:
|
|
46
|
+
// partner URLs routinely hold order ids, email addresses and session tokens, and
|
|
47
|
+
// none of those should reach the event stream because someone forgot to exclude
|
|
48
|
+
// them. Anything absent from this list is dropped.
|
|
49
|
+
//
|
|
50
|
+
// SECURITY: `code`, `state` and `nonce` are the OAuth 2.0 / OIDC authorization
|
|
51
|
+
// code and the CSRF/replay tokens. They are credentials until redeemed, and
|
|
52
|
+
// attaching them here persists them in the event store and forwards them to
|
|
53
|
+
// every configured kit. They are on the list by explicit product decision —
|
|
54
|
+
// deleting that line is the whole of the fix if that decision is revisited.
|
|
55
|
+
export const ALLOWED_QUERY_PARAMS: string[] = [
|
|
56
|
+
// Campaign attribution
|
|
57
|
+
'utm_source',
|
|
58
|
+
'utm_medium',
|
|
59
|
+
'utm_campaign',
|
|
60
|
+
'utm_term',
|
|
61
|
+
'utm_content',
|
|
62
|
+
'utm_id',
|
|
63
|
+
|
|
64
|
+
// Ad-network click ids
|
|
65
|
+
'gclid',
|
|
66
|
+
'gbraid',
|
|
67
|
+
'wbraid',
|
|
68
|
+
'fbclid',
|
|
69
|
+
'msclkid',
|
|
70
|
+
'ttclid',
|
|
71
|
+
'twclid',
|
|
72
|
+
'li_fat_id',
|
|
73
|
+
'dclid',
|
|
74
|
+
|
|
75
|
+
// OAuth / OIDC — see the SECURITY note above
|
|
76
|
+
'client_id',
|
|
77
|
+
'redirect_uri',
|
|
78
|
+
'response_type',
|
|
79
|
+
'scope',
|
|
80
|
+
'state',
|
|
81
|
+
'code',
|
|
82
|
+
'nonce',
|
|
83
|
+
|
|
84
|
+
// Pagination and search
|
|
85
|
+
'page',
|
|
86
|
+
'limit',
|
|
87
|
+
'offset',
|
|
88
|
+
'cursor',
|
|
89
|
+
'per_page',
|
|
90
|
+
'q',
|
|
91
|
+
'search',
|
|
92
|
+
|
|
93
|
+
// Referral
|
|
94
|
+
'ref',
|
|
95
|
+
'referrer',
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
interface IPageSnapshot {
|
|
99
|
+
path: string;
|
|
100
|
+
params: Dictionary<string>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface IPageViewData extends IPageSnapshot {
|
|
45
104
|
hostname: string;
|
|
46
105
|
title: string;
|
|
47
|
-
path: string;
|
|
48
106
|
}
|
|
49
107
|
|
|
50
108
|
interface IPendingNavigation {
|
|
51
|
-
|
|
109
|
+
page: IPageSnapshot;
|
|
52
110
|
timeoutId: ReturnType<typeof setTimeout>;
|
|
53
111
|
}
|
|
54
112
|
|
|
@@ -57,12 +115,46 @@ interface IPendingNavigation {
|
|
|
57
115
|
// on their own, which is where the interesting rules live.
|
|
58
116
|
// ---------------------------------------------------------------------------
|
|
59
117
|
|
|
60
|
-
//
|
|
61
|
-
//
|
|
118
|
+
// Pulls the allowlisted query params off a URL. Delegates to the SDK's own
|
|
119
|
+
// parser, which lowercases keys (so `?UTM_Source=` and `?utm_source=` land on one
|
|
120
|
+
// attribute), drops empty values, and carries the fallback for browsers without
|
|
121
|
+
// URLSearchParams. Tolerates an empty href, so SSR yields no params rather than
|
|
122
|
+
// throwing.
|
|
123
|
+
export const allowedQueryParams = (href: string): Dictionary<string> =>
|
|
124
|
+
queryStringParser(href, ALLOWED_QUERY_PARAMS);
|
|
125
|
+
|
|
126
|
+
// The captured params, in allowlist order. Ordering comes from the constant
|
|
127
|
+
// rather than a sort: it is deterministic without needing a comparator, and it
|
|
128
|
+
// does not depend on the object's insertion order, so reordering the query string
|
|
129
|
+
// cannot produce a different key.
|
|
130
|
+
const capturedNames = (params: Dictionary<string>): string[] =>
|
|
131
|
+
ALLOWED_QUERY_PARAMS.filter(name => name in params);
|
|
132
|
+
|
|
133
|
+
// The dedup key: pathname plus the allowlisted params in a fixed order, so that
|
|
134
|
+
// reordering the query string is not a new page. Params outside the allowlist
|
|
135
|
+
// never make it into `page.params` and so cannot key a view — nor can the hash.
|
|
136
|
+
//
|
|
137
|
+
// Values are re-encoded because queryStringParser hands them back DECODED. A
|
|
138
|
+
// value holding the pair delimiters would otherwise serialize exactly like two
|
|
139
|
+
// separate params — `{q: 'a&search=b'}` and `{q: 'a', search: 'b'}` both becoming
|
|
140
|
+
// `q=a&search=b` — and dedup would treat a real navigation between them as the
|
|
141
|
+
// same page and drop the view. `q`, `search` and `redirect_uri` carry `&` and `=`
|
|
142
|
+
// routinely, so this is reachable rather than theoretical.
|
|
143
|
+
export const pageKey = (page: IPageSnapshot): string => {
|
|
144
|
+
const query = capturedNames(page.params)
|
|
145
|
+
.map(name => `${name}=${encodeURIComponent(page.params[name])}`)
|
|
146
|
+
.join('&');
|
|
147
|
+
|
|
148
|
+
return query ? `${page.path}?${query}` : page.path;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Dedup keys on the pageKey above: a change to any captured param is a new page
|
|
152
|
+
// (`?page=2` is a distinct pagination view), while a hash-only change, or a
|
|
153
|
+
// change confined to params we do not capture, is the same page.
|
|
62
154
|
export const isNewPage = (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
): boolean =>
|
|
155
|
+
lastKey: string | null,
|
|
156
|
+
candidateKey: string
|
|
157
|
+
): boolean => candidateKey !== lastKey;
|
|
66
158
|
|
|
67
159
|
export const supportsHistoryTracking = (win: Window | null): boolean =>
|
|
68
160
|
!!win &&
|
|
@@ -71,11 +163,25 @@ export const supportsHistoryTracking = (win: Window | null): boolean =>
|
|
|
71
163
|
typeof win.addEventListener === 'function';
|
|
72
164
|
|
|
73
165
|
// Mirrors the event shape of the public mParticle.logPageView(), but carries the
|
|
74
|
-
// path captured when the navigation was accepted rather than the
|
|
75
|
-
|
|
166
|
+
// path and query params captured when the navigation was accepted rather than the
|
|
167
|
+
// live location.
|
|
168
|
+
export const buildPageViewEvent = ({
|
|
169
|
+
params,
|
|
170
|
+
hostname,
|
|
171
|
+
title,
|
|
172
|
+
path,
|
|
173
|
+
}: IPageViewData): BaseEvent => ({
|
|
76
174
|
messageType: MessageType.PageView,
|
|
77
175
|
name: 'PageView',
|
|
78
|
-
|
|
176
|
+
// Params spread first, then the core fields by name, so a core field always
|
|
177
|
+
// wins. No allowlist entry collides with hostname/title/path today; naming
|
|
178
|
+
// them here is what keeps a later addition from silently overwriting one.
|
|
179
|
+
data: {
|
|
180
|
+
...params,
|
|
181
|
+
hostname,
|
|
182
|
+
title,
|
|
183
|
+
path,
|
|
184
|
+
},
|
|
79
185
|
eventType: EventType.Unknown,
|
|
80
186
|
});
|
|
81
187
|
|
|
@@ -160,7 +266,25 @@ const clearActiveTracker = (tracker: PageViewTracker): void => {
|
|
|
160
266
|
}
|
|
161
267
|
};
|
|
162
268
|
|
|
163
|
-
const
|
|
269
|
+
const currentPage = (): IPageSnapshot => ({
|
|
270
|
+
path: window.location.pathname,
|
|
271
|
+
params: allowedQueryParams(getHref()),
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Log-safe description of a page: the path, plus the NAMES of the captured
|
|
275
|
+
// params. Values are deliberately omitted — verbose logging goes to the console,
|
|
276
|
+
// and session-replay tooling ships console output off-domain, which is not a
|
|
277
|
+
// place for an OAuth code or a consumer's search terms.
|
|
278
|
+
// Only reachable before init() assigns lastPage, which is ahead of any navigation
|
|
279
|
+
// handler being able to run, so this returns nothing rather than a sentinel.
|
|
280
|
+
const describePage = (page: IPageSnapshot | null): string => {
|
|
281
|
+
if (!page) {
|
|
282
|
+
return '';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const names = capturedNames(page.params);
|
|
286
|
+
return names.length ? `${page.path} (params: ${names.join()})` : page.path;
|
|
287
|
+
};
|
|
164
288
|
|
|
165
289
|
// ---------------------------------------------------------------------------
|
|
166
290
|
// History patching
|
|
@@ -250,7 +374,7 @@ export const patchHistory = (
|
|
|
250
374
|
export class PageViewTracker {
|
|
251
375
|
private readonly mpInstance: IMParticleWebSDKInstance;
|
|
252
376
|
|
|
253
|
-
private
|
|
377
|
+
private lastPage: IPageSnapshot | null = null;
|
|
254
378
|
private active = false;
|
|
255
379
|
private pendingNavigations: IPendingNavigation[] = [];
|
|
256
380
|
|
|
@@ -285,14 +409,14 @@ export class PageViewTracker {
|
|
|
285
409
|
// dropped — a route change may have queued a deferred fire that has not
|
|
286
410
|
// flushed yet, and dropping it loses a page view entirely.
|
|
287
411
|
const active = getActiveTracker();
|
|
288
|
-
let
|
|
412
|
+
let inheritedPages = this.retire(active);
|
|
289
413
|
if (this.active && active !== this) {
|
|
290
|
-
|
|
414
|
+
inheritedPages = inheritedPages.concat(this.retire(this));
|
|
291
415
|
}
|
|
292
416
|
|
|
293
417
|
this.active = true;
|
|
294
|
-
this.
|
|
295
|
-
this.log(`[init] seeded
|
|
418
|
+
this.lastPage = currentPage();
|
|
419
|
+
this.log(`[init] seeded lastPage: ${describePage(this.lastPage)}`);
|
|
296
420
|
|
|
297
421
|
this.undoHistoryPatch = patchHistory(
|
|
298
422
|
source => this.safeHandleNavigation(source),
|
|
@@ -307,7 +431,7 @@ export class PageViewTracker {
|
|
|
307
431
|
'[init] patched pushState/replaceState + listening for popstate'
|
|
308
432
|
);
|
|
309
433
|
|
|
310
|
-
|
|
434
|
+
inheritedPages.forEach(page => this.scheduleFire(page));
|
|
311
435
|
}
|
|
312
436
|
|
|
313
437
|
public teardown(): void {
|
|
@@ -330,10 +454,10 @@ export class PageViewTracker {
|
|
|
330
454
|
clearActiveTracker(this);
|
|
331
455
|
}
|
|
332
456
|
|
|
333
|
-
// Stops the outgoing tracker and returns the
|
|
457
|
+
// Stops the outgoing tracker and returns the pages it had queued so this
|
|
334
458
|
// tracker can re-schedule them once it is active. Private, but reachable
|
|
335
459
|
// across instances: the handoff protocol stays internal to the class.
|
|
336
|
-
private retire(outgoing: PageViewTracker | undefined):
|
|
460
|
+
private retire(outgoing: PageViewTracker | undefined): IPageSnapshot[] {
|
|
337
461
|
if (!outgoing) {
|
|
338
462
|
return [];
|
|
339
463
|
}
|
|
@@ -344,17 +468,17 @@ export class PageViewTracker {
|
|
|
344
468
|
: '[init] retiring a tracker from a previous module load — transferring pending navigations and tearing down'
|
|
345
469
|
);
|
|
346
470
|
|
|
347
|
-
const
|
|
471
|
+
const pages = outgoing.takePendingNavigations();
|
|
348
472
|
outgoing.teardown();
|
|
349
|
-
return
|
|
473
|
+
return pages;
|
|
350
474
|
}
|
|
351
475
|
|
|
352
|
-
// Cancels this tracker's deferred fires and hands back their captured
|
|
353
|
-
private takePendingNavigations():
|
|
476
|
+
// Cancels this tracker's deferred fires and hands back their captured pages.
|
|
477
|
+
private takePendingNavigations(): IPageSnapshot[] {
|
|
354
478
|
const pending = this.pendingNavigations;
|
|
355
479
|
this.pendingNavigations = [];
|
|
356
|
-
pending.forEach(
|
|
357
|
-
return pending.map(
|
|
480
|
+
pending.forEach(({ timeoutId }) => clearTimeout(timeoutId));
|
|
481
|
+
return pending.map(({ page }) => page);
|
|
358
482
|
}
|
|
359
483
|
|
|
360
484
|
private safeHandleNavigation(source: NavigationSource): void {
|
|
@@ -377,31 +501,39 @@ export class PageViewTracker {
|
|
|
377
501
|
}
|
|
378
502
|
|
|
379
503
|
private handleNavigation(source: NavigationSource): void {
|
|
380
|
-
const
|
|
504
|
+
const candidate = currentPage();
|
|
505
|
+
const lastKey = this.lastPage ? pageKey(this.lastPage) : null;
|
|
381
506
|
|
|
382
507
|
this.log(
|
|
383
|
-
`[detect] navigation signal (source: ${source},
|
|
508
|
+
`[detect] navigation signal (source: ${source}, candidate: ${describePage(
|
|
509
|
+
candidate
|
|
510
|
+
)}, last: ${describePage(this.lastPage)})`
|
|
384
511
|
);
|
|
385
512
|
|
|
386
|
-
if (!isNewPage(
|
|
513
|
+
if (!isNewPage(lastKey, pageKey(candidate))) {
|
|
387
514
|
this.log(
|
|
388
|
-
`[dedupe]
|
|
515
|
+
`[dedupe] page unchanged, skipping (source: ${source}, page: ${describePage(
|
|
516
|
+
candidate
|
|
517
|
+
)})`
|
|
389
518
|
);
|
|
390
519
|
return;
|
|
391
520
|
}
|
|
392
521
|
|
|
393
522
|
this.log(
|
|
394
|
-
`[accept]
|
|
523
|
+
`[accept] page changed, scheduling fire (source: ${source}, from: ${describePage(
|
|
524
|
+
this.lastPage
|
|
525
|
+
)}, to: ${describePage(candidate)})`
|
|
395
526
|
);
|
|
396
|
-
this.
|
|
397
|
-
this.scheduleFire(
|
|
527
|
+
this.lastPage = candidate;
|
|
528
|
+
this.scheduleFire(candidate);
|
|
398
529
|
}
|
|
399
530
|
|
|
400
531
|
// Deferred by a macrotask so the router's post-navigation render commit has a
|
|
401
|
-
// chance to update document.title before the event is built. The path
|
|
402
|
-
// snapshotted now because
|
|
403
|
-
// navigation would overwrite window.location before the flush reads
|
|
404
|
-
|
|
532
|
+
// chance to update document.title before the event is built. The path and
|
|
533
|
+
// query params are snapshotted now because they are already settled, whereas a
|
|
534
|
+
// same-tick navigation would overwrite window.location before the flush reads
|
|
535
|
+
// it.
|
|
536
|
+
private scheduleFire(page: IPageSnapshot): void {
|
|
405
537
|
const timeoutId = setTimeout(() => {
|
|
406
538
|
this.pendingNavigations = this.pendingNavigations.filter(
|
|
407
539
|
p => p.timeoutId !== timeoutId
|
|
@@ -415,22 +547,24 @@ export class PageViewTracker {
|
|
|
415
547
|
}
|
|
416
548
|
|
|
417
549
|
this.mpInstance._SessionManager.resetSessionTimer();
|
|
418
|
-
this.firePageView(
|
|
550
|
+
this.firePageView(page);
|
|
419
551
|
}, 0);
|
|
420
552
|
|
|
421
|
-
this.pendingNavigations.push({
|
|
553
|
+
this.pendingNavigations.push({ page, timeoutId });
|
|
422
554
|
}
|
|
423
555
|
|
|
424
|
-
private firePageView(
|
|
556
|
+
private firePageView(page: IPageSnapshot): void {
|
|
425
557
|
const title = window.document.title;
|
|
426
558
|
const event = buildPageViewEvent({
|
|
559
|
+
...page,
|
|
427
560
|
hostname: window.location.hostname,
|
|
428
561
|
title,
|
|
429
|
-
path,
|
|
430
562
|
});
|
|
431
563
|
|
|
432
564
|
this.log(
|
|
433
|
-
`[fire] deferred flush -> _Events.logEvent(PageView) (
|
|
565
|
+
`[fire] deferred flush -> _Events.logEvent(PageView) (page: ${describePage(
|
|
566
|
+
page
|
|
567
|
+
)}, title: ${title})`
|
|
434
568
|
);
|
|
435
569
|
|
|
436
570
|
this.mpInstance._Events.logEvent(event);
|
|
@@ -43,7 +43,7 @@ export interface IGlobalStoreV2MinifiedKeys {
|
|
|
43
43
|
export interface IPersistenceMinified extends Dictionary {
|
|
44
44
|
cu: MPID; // Current User MPID
|
|
45
45
|
gs: IGlobalStoreV2MinifiedKeys;
|
|
46
|
-
l: boolean; // IsLoggedIn
|
|
46
|
+
l: boolean | 0 | 1; // IsLoggedIn
|
|
47
47
|
|
|
48
48
|
// Persistence Minified can also store optional dictionaries with
|
|
49
49
|
// an idex of MPID
|
|
@@ -99,8 +99,8 @@ export interface IPersistence {
|
|
|
99
99
|
useLocalStorage(): boolean;
|
|
100
100
|
initializeStorage(): void;
|
|
101
101
|
update(): void;
|
|
102
|
-
storeDataInMemory(obj: IPersistenceMinified, currentMPID
|
|
103
|
-
determineLocalStorageAvailability(storage
|
|
102
|
+
storeDataInMemory(obj: IPersistenceMinified, currentMPID?: MPID): void;
|
|
103
|
+
determineLocalStorageAvailability(storage?: Storage): boolean;
|
|
104
104
|
setLocalStorage(): void;
|
|
105
105
|
getLocalStorage(): IPersistenceMinified | null;
|
|
106
106
|
expireCookies(cookieName: string): void;
|
|
@@ -113,17 +113,17 @@ export interface IPersistence {
|
|
|
113
113
|
maxCookieSize: number
|
|
114
114
|
): string;
|
|
115
115
|
findPrevCookiesBasedOnUI(identityApiData: IdentityApiData): void;
|
|
116
|
-
encodePersistence(persistence:
|
|
117
|
-
decodePersistence(persistenceString: string): string;
|
|
116
|
+
encodePersistence(persistence: string): string;
|
|
117
|
+
decodePersistence(persistenceString: string | null): string | void;
|
|
118
118
|
getCookieDomain(): string;
|
|
119
|
-
getDomain(doc:
|
|
119
|
+
getDomain(doc: Document, locationHostname: string): string;
|
|
120
120
|
saveUserCookieSyncDatesToPersistence(mpid: MPID, csd: CookieSyncDates): void;
|
|
121
121
|
savePersistence(persistance: IPersistenceMinified): void;
|
|
122
|
-
getPersistence(): IPersistenceMinified;
|
|
123
|
-
getFirstSeenTime(mpid: MPID):
|
|
124
|
-
setFirstSeenTime(mpid: MPID, time
|
|
122
|
+
getPersistence(): IPersistenceMinified | null;
|
|
123
|
+
getFirstSeenTime(mpid: MPID): number | null;
|
|
124
|
+
setFirstSeenTime(mpid: MPID, time?: number): void;
|
|
125
125
|
getLastSeenTime(mpid: MPID): number | null;
|
|
126
|
-
setLastSeenTime(mpid: MPID, time
|
|
126
|
+
setLastSeenTime(mpid: MPID, time?: number): void;
|
|
127
127
|
getDeviceId(): string;
|
|
128
128
|
setDeviceId(guid: string): void;
|
|
129
129
|
resetPersistence(): void;
|