noibu-react-native 0.1.1 → 0.1.3

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.
Files changed (35) hide show
  1. package/dist/api/clientConfig.d.ts +4 -5
  2. package/dist/api/clientConfig.js +21 -15
  3. package/dist/api/inputManager.js +1 -1
  4. package/dist/api/metroplexSocket.d.ts +155 -0
  5. package/dist/api/metroplexSocket.js +667 -804
  6. package/dist/api/storedMetrics.js +2 -1
  7. package/dist/constants.d.ts +11 -0
  8. package/dist/constants.js +15 -2
  9. package/dist/entry/index.js +2 -0
  10. package/dist/entry/init.d.ts +1 -1
  11. package/dist/entry/init.js +9 -2
  12. package/dist/monitors/appNavigationMonitor.js +3 -2
  13. package/dist/monitors/clickMonitor.js +1 -1
  14. package/dist/monitors/errorMonitor.js +2 -2
  15. package/dist/monitors/httpDataBundler.js +62 -15
  16. package/dist/monitors/inputMonitor.js +5 -0
  17. package/dist/monitors/integrations/react-native-navigation-integration.d.ts +1 -2
  18. package/dist/monitors/keyboardInputMonitor.js +1 -1
  19. package/dist/monitors/requestMonitor.js +47 -310
  20. package/dist/pageVisit/pageVisit.js +5 -0
  21. package/dist/pageVisit/{pageVisitEventError/pageVisitEventError.js → pageVisitEventError.js} +18 -19
  22. package/dist/pageVisit/{pageVisitEventHTTP/pageVisitEventHTTP.js → pageVisitEventHTTP.js} +15 -7
  23. package/dist/pageVisit/{userStep/userStep.js → userStep.js} +2 -2
  24. package/dist/types/NavigationIntegration.d.ts +1 -2
  25. package/dist/types/globals.d.ts +6 -3
  26. package/dist/utils/function.d.ts +2 -3
  27. package/dist/utils/function.js +15 -10
  28. package/dist/utils/log.d.ts +5 -0
  29. package/dist/utils/log.js +17 -0
  30. package/dist/utils/object.d.ts +41 -0
  31. package/dist/utils/object.js +85 -108
  32. package/dist/utils/performance.d.ts +6 -0
  33. package/dist/utils/performance.js +1 -2
  34. package/package.json +5 -4
  35. package/dist/pageVisit/pageVisitEventError/blacklistedDomains.js +0 -9
@@ -7,840 +7,703 @@ import StoredMetrics from './storedMetrics.js';
7
7
  import StoredPageVisit from './storedPageVisit.js';
8
8
  import { safePerformanceNow } from '../utils/performance.js';
9
9
  import { isDateOverwritten } from '../utils/date.js';
10
+ import { unwrapNoibuWrapped } from '../utils/object.js';
11
+ import { noibuLog } from '../utils/log.js';
10
12
 
11
13
  /** @module MetroplexSocket */
12
-
13
14
  /** Manages the socket to Metroplex */
14
15
  class MetroplexSocket {
15
- /** @type {Promise<void> | null} */
16
- connectionPromise = null;
17
-
18
- /**
19
- * Creates an instance of metroplex
20
- * @param {} scriptInstanceId id of script, to make sure only a single socket is open
21
- */
22
- constructor(scriptInstanceId) {
23
- const socketBase = GET_METROPLEX_BASE_SOCKET_URL();
24
-
25
- // this flag is set to true when we manually need to close the
26
- // socket. It happens currently during the pagehide event and
27
- // if we havent sent a message to our backend in some time.
28
- this.forceClosed = false;
29
- // the socket used to transmit all data to metroplex
30
- this.socket = null;
31
- // the unique instance id of the socket
32
- this.socketInstanceId = null;
33
-
34
- // previous message type sent to metroplex
35
- this.previousMessageType = '';
36
- // how many times we tried reconnecting to metroplex
37
- this.currentConnectionAttempts = 0;
38
- // how many successful connections to metroplex
39
- this.connectionCount = 0;
40
- // sessiont start time, used to calculate accurate end time
41
- this.sessionStartTime = safePerformanceNow();
42
- // promise that will resolve once the socket is connected and metroplex has acknowledged
43
- this.connectionPromise = null;
44
- // Whether or not we have sent the page visit information after connecting the socket
45
- this.pageVisitInfoSent = false;
46
- // socket connection url
47
- this.connectionURL = `${socketBase}/${METROPLEX_FRAG_ROUTE}`;
48
- // post endpoint for the same events we would send to the socket
49
- this.postURL = GET_METROPLEX_POST_URL();
50
- // sequence number of the message sent to metroplex
51
- this.messageSequenceNum = 0;
52
- // the latest received sequence number from metroplex
53
- this.latestReceivedSeqNumber = -1;
54
- // set to true only when we start running behind on messages in metroplex
55
- this.isRetryLoopDisabled = false;
56
- // messages that need to be resent to metroplex since they are lacking confirmation
57
- this.retryMessageQueue = [];
58
- // this map will hold all types that noibujs should not send to metroplex
59
- this.metroplexTypeLock = {};
60
- // setting initial URL at the start in order to guarentee that the
61
- // current page visit has the real initial onURL. Fragments and SPA's
62
- // can change the URL without reloading the page.
63
- this._initialURL = ClientConfig.getInstance().globalUrl;
64
- this.initialReferingURL = '';
65
- this.sessionTimestamp = new Date();
66
-
67
- // latest time that we received a confirmation message from metroplex
68
- this.latestReceivedSeqNumStoredTime = new Date();
69
-
70
- // unique instance id of Metroplex Socket
71
- this.instanceId = uuid.v4();
72
-
73
- // unique script instance id
74
- this.scriptInstanceId = scriptInstanceId;
75
-
76
- // length of the session based on page visit events
77
- this.sessionLength = 0;
78
-
79
- // track socket closure codes for debug
80
- this.socketCloseCodes = [];
81
-
82
- // track socket open times
83
- this.socketOpens = [];
84
-
85
- // flag to indicate Metroplex has acknowledged at least once
86
- this.ackedOnce = false;
87
-
88
- // retry frequency in ms
89
- this.metroRetryFrequencyMS = METROPLEX_RETRY_FREQUENCY;
90
- }
91
-
92
- /**
93
- * gets the singleton instance
94
- * @param {} [scriptInstanceId]
95
- * @returns {MetroplexSocket}
96
- */
97
- static getInstance(scriptInstanceId) {
98
- if (!this.instance) {
99
- this.instance = new MetroplexSocket(scriptInstanceId);
100
- this.instance.start();
16
+ _initialURL;
17
+ ackedOnce;
18
+ connectionCount;
19
+ connectionPromise = null;
20
+ connectionURL;
21
+ currentConnectionAttempts;
22
+ forceClosed;
23
+ helpCodeCb;
24
+ initialReferingURL;
25
+ static instance;
26
+ instanceId;
27
+ isRetryLoopDisabled;
28
+ latestReceivedSeqNumStoredTime;
29
+ latestReceivedSeqNumber;
30
+ messageSequenceNum;
31
+ metroRetryFrequencyMS;
32
+ metroplexTypeLock;
33
+ pageVisitInfoSent;
34
+ postURL;
35
+ previousMessageType;
36
+ retryMessageQueue;
37
+ retryMetroplexInterval;
38
+ scriptInstanceId;
39
+ sessionLength;
40
+ sessionStartTime;
41
+ sessionTimestamp;
42
+ socket;
43
+ socketInstanceId;
44
+ socketCloseCodes;
45
+ socketOpens;
46
+ /**
47
+ * Creates an instance of metroplex
48
+ * id of script, to make sure only a single socket is open
49
+ */
50
+ constructor(scriptInstanceId) {
51
+ const socketBase = GET_METROPLEX_BASE_SOCKET_URL();
52
+ // this flag is set to true when we manually need to close the
53
+ // socket. It happens currently during the pagehide event and
54
+ // if we havent sent a message to our backend in some time.
55
+ this.forceClosed = false;
56
+ // the socket used to transmit all data to metroplex
57
+ this.socket = null;
58
+ // the unique instance id of the socket
59
+ this.socketInstanceId = null;
60
+ // previous message type sent to metroplex
61
+ this.previousMessageType = '';
62
+ // how many times we tried reconnecting to metroplex
63
+ this.currentConnectionAttempts = 0;
64
+ // how many successful connections to metroplex
65
+ this.connectionCount = 0;
66
+ // sessiont start time, used to calculate accurate end time
67
+ this.sessionStartTime = safePerformanceNow();
68
+ // promise that will resolve once the socket is connected and metroplex has acknowledged
69
+ this.connectionPromise = null;
70
+ // Whether or not we have sent the page visit information after connecting the socket
71
+ this.pageVisitInfoSent = false;
72
+ // socket connection url
73
+ this.connectionURL = `${socketBase}/${METROPLEX_FRAG_ROUTE}`;
74
+ // post endpoint for the same events we would send to the socket
75
+ this.postURL = GET_METROPLEX_POST_URL();
76
+ // sequence number of the message sent to metroplex
77
+ this.messageSequenceNum = 0;
78
+ // the latest received sequence number from metroplex
79
+ this.latestReceivedSeqNumber = -1;
80
+ // set to true only when we start running behind on messages in metroplex
81
+ this.isRetryLoopDisabled = false;
82
+ // messages that need to be resent to metroplex since they are lacking confirmation
83
+ this.retryMessageQueue = [];
84
+ // this map will hold all types that noibujs should not send to metroplex
85
+ this.metroplexTypeLock = {};
86
+ // setting initial URL at the start in order to guarentee that the
87
+ // current page visit has the real initial onURL. Fragments and SPA's
88
+ // can change the URL without reloading the page.
89
+ this._initialURL = ClientConfig.getInstance().globalUrl;
90
+ noibuLog('metroplex constructor', {
91
+ initialURL: this._initialURL,
92
+ });
93
+ this.initialReferingURL = '';
94
+ this.sessionTimestamp = new Date();
95
+ // latest time that we received a confirmation message from metroplex
96
+ this.latestReceivedSeqNumStoredTime = new Date();
97
+ // unique instance id of Metroplex Socket
98
+ this.instanceId = uuid.v4();
99
+ // unique script instance id
100
+ this.scriptInstanceId = scriptInstanceId;
101
+ // length of the session based on page visit events
102
+ this.sessionLength = 0;
103
+ // track socket closure codes for debug
104
+ this.socketCloseCodes = [];
105
+ // track socket open times
106
+ this.socketOpens = [];
107
+ // flag to indicate Metroplex has acknowledged at least once
108
+ this.ackedOnce = false;
109
+ // retry frequency in ms
110
+ this.metroRetryFrequencyMS = METROPLEX_RETRY_FREQUENCY;
111
+ this.helpCodeCb = null;
112
+ this.retryMetroplexInterval = null;
101
113
  }
102
-
103
- return this.instance;
104
- }
105
-
106
- /** Starts off the socket connection */
107
- start() {
108
- // Connect the WS
109
- this.connectSocket();
110
- // Set up the offload events immediately
111
- this._setupOffloadEvents();
112
- }
113
-
114
- /**
115
- * Adds the seq num field to the given payload depending on whether its
116
- * a page visit part or video frag
117
- * @param {} type
118
- * @param {} payload
119
- */
120
- _addSeqNumToPayload(type, payload) {
121
- switch (type) {
122
- case PV_METROPLEX_TYPE:
123
- this._setSeqNumInPayloadAndIncrementSeqNum(
124
- PAGE_VISIT_PART_ATT_NAME,
125
- payload,
126
- );
127
- break;
128
- case VIDEO_METROPLEX_TYPE:
129
- this._setSeqNumInPayloadAndIncrementSeqNum(
130
- PAGE_VISIT_VID_FRAG_ATT_NAME,
131
- payload,
132
- );
133
- break;
134
- case HTTP_DATA_METROPLEX_TYPE:
135
- this._setSeqNumInPayloadAndIncrementSeqNum(
136
- PAGE_VISIT_HTTP_DATA_ATT_NAME,
137
- payload,
138
- );
139
- break;
140
- case META_DATA_METROPLEX_TYPE:
141
- this._setSeqNumInPayloadAndIncrementSeqNum(
142
- PAGE_VISIT_META_DATA_ATT_NAME,
143
- payload,
144
- );
145
- break;
114
+ /**
115
+ * gets the singleton instance
116
+ * @returns {MetroplexSocket}
117
+ */
118
+ static getInstance(scriptInstanceId) {
119
+ if (!this.instance) {
120
+ this.instance = new MetroplexSocket(scriptInstanceId);
121
+ this.instance.start();
122
+ }
123
+ return this.instance;
146
124
  }
147
- }
148
-
149
- /**
150
- * sets the seq num in the payload for the given key and increments the
151
- * global seq number
152
- * @param {} payloadKey
153
- * @param {} payload
154
- */
155
- _setSeqNumInPayloadAndIncrementSeqNum(payloadKey, payload) {
156
- // eslint-disable-next-line no-param-reassign
157
- payload[payloadKey][SEQ_NUM_ATT_NAME] = this.messageSequenceNum;
158
- this.messageSequenceNum += 1;
159
- }
160
-
161
- /** requests help code and saves a callback to be called on response */
162
- requestHelpCode(cb) {
163
- this.helpCodeCb = cb;
164
- return MetroplexSocket.getInstance().sendMessage(WORK_REQUEST_ATT_NAME, {
165
- [WORK_REQUEST_ATT_NAME]: HELP_CODE_ATT_NAME,
166
- });
167
- }
168
-
169
- /**
170
- * Immediately sends a message to Metroplex over the web socket
171
- * Queues the message if the connection isn't open yet.
172
- * @param {} type
173
- * @param {} payload
174
- * @returns {Promise<boolean>} true if message was sent succefully, false otherwise
175
- */
176
- async sendMessage(type, payload) {
177
- // if we have a lock on this specific type, we dont send it
178
- if (
179
- type in this.metroplexTypeLock ||
180
- ClientConfig.getInstance().isClientDisabled
181
- ) {
182
- return false;
125
+ /** Starts off the socket connection */
126
+ start() {
127
+ // Connect the WS
128
+ this.connectSocket();
129
+ // Set up the offload events immediately
130
+ this._setupOffloadEvents();
183
131
  }
184
-
185
- const payloadData = payload;
186
-
187
- if (type !== WORK_REQUEST_ATT_NAME) {
188
- // Increasing the message sequence number for every message we send to metroplex
189
- // and move the data to the retry queue.
190
- this._addSeqNumToPayload(type, payloadData);
191
-
192
- // push the message to the retry queue immediately in case socket isnt connected
193
- this.retryMessageQueue.push({ payload: payloadData, type });
194
- StoredPageVisit.getInstance().checkAndStoreRetryQueue(
195
- this.retryMessageQueue,
196
- await this.getPageInformation(),
197
- );
132
+ /**
133
+ * Adds the seq num field to the given payload depending on whether its
134
+ * a page visit part or video frag
135
+ */
136
+ _addSeqNumToPayload(type, payload) {
137
+ switch (type) {
138
+ case PV_METROPLEX_TYPE:
139
+ this._setSeqNumInPayloadAndIncrementSeqNum(PAGE_VISIT_PART_ATT_NAME, payload);
140
+ break;
141
+ case VIDEO_METROPLEX_TYPE:
142
+ this._setSeqNumInPayloadAndIncrementSeqNum(PAGE_VISIT_VID_FRAG_ATT_NAME, payload);
143
+ break;
144
+ case HTTP_DATA_METROPLEX_TYPE:
145
+ this._setSeqNumInPayloadAndIncrementSeqNum(PAGE_VISIT_HTTP_DATA_ATT_NAME, payload);
146
+ break;
147
+ case META_DATA_METROPLEX_TYPE:
148
+ this._setSeqNumInPayloadAndIncrementSeqNum(PAGE_VISIT_META_DATA_ATT_NAME, payload);
149
+ break;
150
+ }
198
151
  }
199
-
200
- // send the socket message if we are connected and have sent page visit info
201
- if (this.isConnected() && this.pageVisitInfoSent) {
202
- // sending the data to metroplex
203
- this._sendSocketMessage(payloadData);
152
+ /**
153
+ * sets the seq num in the payload for the given key and increments the
154
+ * global seq number
155
+ */
156
+ _setSeqNumInPayloadAndIncrementSeqNum(payloadKey, payload) {
157
+ // eslint-disable-next-line no-param-reassign
158
+ payload[payloadKey][SEQ_NUM_ATT_NAME] = this.messageSequenceNum;
159
+ this.messageSequenceNum += 1;
204
160
  }
205
-
206
- this.previousMessageType = type;
207
-
208
- // Only update the last message send if its a page visit with user action
209
- // ensure this is done regardless of whether socket is connected or not,
210
- // so that pagehide will post PV if user is active but socket is not
211
- if (type === PV_METROPLEX_TYPE && payload[PAGE_VISIT_PART_ATT_NAME]) {
212
- const events = payload[PAGE_VISIT_PART_ATT_NAME][PV_EVENTS_ATT_NAME]
213
- ? payload[PAGE_VISIT_PART_ATT_NAME][PV_EVENTS_ATT_NAME]
214
- : [];
215
-
216
- this._updateLatestPvTimestamp(events);
161
+ /** requests help code and saves a callback to be called on response */
162
+ requestHelpCode(cb) {
163
+ this.helpCodeCb = cb;
164
+ return MetroplexSocket.getInstance().sendMessage(WORK_REQUEST_ATT_NAME, {
165
+ [WORK_REQUEST_ATT_NAME]: HELP_CODE_ATT_NAME,
166
+ });
217
167
  }
218
-
219
- return true;
220
- }
221
-
222
- /** Updates the latest pv message sent timestamp if events contain any user steps
223
- * @param {} events
224
- */
225
- _updateLatestPvTimestamp(events) {
226
- const userstepsEvents = events.filter(
227
- ev => ev[TYPE_ATT_NAME] === USERSTEP_EVENT_TYPE,
228
- );
229
-
230
- if (userstepsEvents.length > 0) {
231
- ClientConfig.getInstance().updateLastActiveTime(new Date());
168
+ /**
169
+ * Immediately sends a message to Metroplex over the web socket
170
+ * Queues the message if the connection isn't open yet.
171
+ * returns true if message was sent succefully, false otherwise
172
+ */
173
+ async sendMessage(type, payload) {
174
+ noibuLog('sendMessage start', { type, payload: JSON.stringify(payload) });
175
+ // if we have a lock on this specific type, we dont send it
176
+ if (type in this.metroplexTypeLock ||
177
+ ClientConfig.getInstance().isClientDisabled) {
178
+ noibuLog('sendMessage quitting due to', {
179
+ typeInLock: type in this.metroplexTypeLock,
180
+ isClientDisabled: ClientConfig.getInstance().isClientDisabled,
181
+ });
182
+ return false;
183
+ }
184
+ const payloadData = payload;
185
+ if (type !== WORK_REQUEST_ATT_NAME) {
186
+ // Increasing the message sequence number for every message we send to metroplex
187
+ // and move the data to the retry queue.
188
+ this._addSeqNumToPayload(type, payloadData);
189
+ // push the message to the retry queue immediately in case socket isnt connected
190
+ this.retryMessageQueue.push({ payload: payloadData, type });
191
+ StoredPageVisit.getInstance().checkAndStoreRetryQueue(this.retryMessageQueue, await this.getPageInformation());
192
+ }
193
+ // send the socket message if we are connected and have sent page visit info
194
+ if (this.isConnected() && this.pageVisitInfoSent) {
195
+ // sending the data to metroplex
196
+ await this._sendSocketMessage(payloadData);
197
+ }
198
+ this.previousMessageType = type;
199
+ // Only update the last message send if its a page visit with user action
200
+ // ensure this is done regardless of whether socket is connected or not,
201
+ // so that pagehide will post PV if user is active but socket is not
202
+ if (type === PV_METROPLEX_TYPE && payload[PAGE_VISIT_PART_ATT_NAME]) {
203
+ const events = payload[PAGE_VISIT_PART_ATT_NAME][PV_EVENTS_ATT_NAME]
204
+ ? payload[PAGE_VISIT_PART_ATT_NAME][PV_EVENTS_ATT_NAME]
205
+ : [];
206
+ await this._updateLatestPvTimestamp(events);
207
+ }
208
+ return true;
232
209
  }
233
- }
234
-
235
- /** returns true if the socket is either connecting or connected to metroplex */
236
- isConnected() {
237
- return this.socket !== null && this.socket.readyState === 1;
238
- }
239
-
240
- /** returns true if we are connecting to the socket */
241
- isConnecting() {
242
- return this.socket !== null && this.socket.readyState === 0;
243
- }
244
-
245
- /** close will close the socket opened for video frag transmission */
246
- close() {
247
- this.forceClosed = true;
248
- // closing the video frag socket only if it was used in the past by
249
- // the sessionRecorder
250
- if (this.isConnected() || this.isConnecting()) {
251
- this.socket.close(1000);
210
+ /** Updates the latest pv message sent timestamp if events contain any user steps
211
+ */
212
+ async _updateLatestPvTimestamp(events) {
213
+ const userstepsEvents = events.filter(ev => ev[TYPE_ATT_NAME] === USERSTEP_EVENT_TYPE);
214
+ if (userstepsEvents.length > 0) {
215
+ await ClientConfig.getInstance().updateLastActiveTime(new Date());
216
+ }
252
217
  }
253
- }
254
-
255
- /** Connects the web socket to metroplex and calls callback upon successfully connecting
256
- * @param {} callback
257
- * @param {} forceOpen
258
- */
259
- async handleConnect(callback, forceOpen) {
260
- // if we are connected or about to connect, we do not reset
261
- // the socket information
262
- if (!forceOpen && (this.isConnected() || this.isConnecting())) {
263
- return;
218
+ /** returns true if the socket is either connecting or connected to metroplex */
219
+ isConnected() {
220
+ return this.socket !== null && this.socket.readyState === 1;
264
221
  }
265
-
266
- this.currentConnectionAttempts += 1;
267
- this.socket = new WebSocket(this.connectionURL, undefined, {
268
- headers: {
269
- 'User-Agent': await getUserAgent(), // must pass useragent explicitly
270
- },
271
- });
272
- this.socketInstanceId = uuid.v4();
273
-
274
- this.socket.onerror = e => {
275
- // use for metrics
276
- // there is no useful error message/description from this event
277
- };
278
-
279
- // once the socket closes
280
- this.socket.onclose = event => {
281
- // Reset this to prevent sending messages before the PVI is sent on reconnect
282
- this.pageVisitInfoSent = false;
283
-
284
- // we do not reconnect if the page is unloading
285
- if (this.forceClosed) {
286
- return;
287
- }
288
-
289
- // track socket closure codes and times
290
- this.socketCloseCodes.push(
291
- `${!isDateOverwritten() ? new Date().toISOString() : ''}:${event.code}`,
292
- );
293
-
294
- // we are already in the process of reconnecting
295
- // we do not attempt to reconnect yet
296
- if (this.isConnecting()) {
297
- return;
298
- }
299
-
300
- // Clear the interval that resends unconfirmed msgs so we don't try send while the socket
301
- // is and starting back up, after which it will be restarted
302
- clearInterval(this.retryMetroplexInterval);
303
-
304
- // if we tried reconnecting too many times we abandon
305
- if (
306
- this.currentConnectionAttempts >=
307
- GET_MAX_METROPLEX_RECONNECTION_NUMBER()
308
- ) {
309
- // if we tried beyond the threshold, we block ourselves a short
310
- // while while fix the issue
311
- ClientConfig.getInstance().lockClientUntilNextPage(
312
- 'Too many reconnection attempts, locking until next page',
313
- );
314
- return;
315
- }
316
-
317
- // if we tried reconnecting too many times we abandon
318
- if (this.connectionCount >= MAX_METROPLEX_CONNECTION_COUNT) {
319
- // if we tried beyond the threshold, we block ourselves a short
320
- // while while fix the issue
321
- ClientConfig.getInstance().lockClientUntilNextPage(
322
- 'Too many connections, locking until next page',
323
- );
324
- return;
325
- }
326
-
327
- // try to reconnect on close but after a certain delay based off unsuccessful connections
328
- setTimeout(() => {
329
- this.handleConnect(callback, false);
330
- }, this.currentConnectionAttempts ** 2 * GET_METROPLEX_CONSECUTIVE_CONNECTION_DELAY());
331
- };
332
- // everytime we get a message from the socket
333
- this.socket.onmessage = event => {
334
- this._onSocketMessage(event, callback);
335
- };
336
- // the moment we open the socket
337
- this.socket.onopen = () => {
338
- // track socket open codes and times
339
- this.socketOpens.push(
340
- `${!isDateOverwritten() ? new Date().toISOString() : ''}`,
341
- );
342
- this._onSocketOpen();
343
- };
344
- }
345
-
346
- /**
347
- * connectSocket will establish a websocket connection to the metroplex
348
- * service
349
- */
350
- connectSocket() {
351
- if (this.isConnected() || this.isConnecting()) {
352
- // self resolving promise since we are already ready to send
353
- // requests, if we did reach state 1, we already sent initial
354
- // request, thus not sending it again.
355
- return this.connectionPromise;
222
+ /** returns true if we are connecting to the socket */
223
+ isConnecting() {
224
+ return this.socket !== null && this.socket.readyState === 0;
356
225
  }
357
- // we return a promisified socket resolver to be able to chain the
358
- // opening of the socket
359
- this.connectionPromise = new Promise(resolve => {
360
- this.handleConnect(resolve, false);
361
- });
362
-
363
- return this.connectionPromise;
364
- }
365
-
366
- /** Calculates and sets the end_at field of the payload
367
- * @param {} payload
368
- * @param {} isPageVisit
369
- */
370
- addEndTimeToPayload(payload, isPageVisit) {
371
- const delta = Math.ceil(safePerformanceNow() - this.sessionStartTime);
372
- // update session length if this is a page visit event
373
- if (isPageVisit) {
374
- this.sessionLength = delta;
226
+ /** close will close the socket opened for video frag transmission */
227
+ close() {
228
+ this.forceClosed = true;
229
+ // closing the video frag socket only if it was used in the past by
230
+ // the sessionRecorder
231
+ if (this.isConnected() || this.isConnecting()) {
232
+ if (this.socket !== null) {
233
+ this.socket.close(1000);
234
+ }
235
+ }
375
236
  }
376
-
377
- // Assigning this value here is much better than copying the whole object.
378
- // eslint-disable-next-line no-param-reassign
379
- payload[END_AT_ATT_NAME] = new Date(
380
- this.sessionTimestamp.getTime() + delta,
381
- ).toISOString();
382
- }
383
-
384
- /** open handler for socket */
385
- async _onSocketOpen() {
386
- // if we are not connected, we do not send any messages
387
- if (!this.isConnected() || ClientConfig.getInstance().isClientDisabled) {
388
- return;
237
+ /** Connects the web socket to metroplex and calls callback upon successfully connecting
238
+ */
239
+ async handleConnect(forceOpen) {
240
+ noibuLog('metroplex handleConnect');
241
+ // if we are connected or about to connect, we do not reset
242
+ // the socket information
243
+ if (!forceOpen && (this.isConnected() || this.isConnecting())) {
244
+ return;
245
+ }
246
+ this.currentConnectionAttempts += 1;
247
+ this.socket = new WebSocket(this.connectionURL, undefined, {
248
+ headers: {
249
+ 'User-Agent': await getUserAgent(), // must pass useragent explicitly
250
+ },
251
+ });
252
+ this.socketInstanceId = uuid.v4();
253
+ this.socket.onerror = error => {
254
+ noibuLog('metroplex handleConnect, onerror', { error });
255
+ // use for metrics
256
+ // there is no useful error message/description from this event
257
+ };
258
+ // once the socket closes
259
+ this.socket.onclose = async (event) => {
260
+ noibuLog('metroplex handleConnect, onclose', { event });
261
+ // Reset this to prevent sending messages before the PVI is sent on reconnect
262
+ this.pageVisitInfoSent = false;
263
+ // we do not reconnect if the page is unloading
264
+ if (this.forceClosed) {
265
+ return;
266
+ }
267
+ // track socket closure codes and times
268
+ this.socketCloseCodes.push(`${!isDateOverwritten() ? new Date().toISOString() : ''}:${event.code}`);
269
+ // we are already in the process of reconnecting
270
+ // we do not attempt to reconnect yet
271
+ if (this.isConnecting()) {
272
+ return;
273
+ }
274
+ // Clear the interval that resends unconfirmed msgs so we don't try send while the socket
275
+ // is and starting back up, after which it will be restarted
276
+ if (this.retryMetroplexInterval) {
277
+ clearInterval(this.retryMetroplexInterval);
278
+ }
279
+ // if we tried reconnecting too many times we abandon
280
+ if (this.currentConnectionAttempts >=
281
+ GET_MAX_METROPLEX_RECONNECTION_NUMBER()) {
282
+ // if we tried beyond the threshold, we block ourselves a short
283
+ // while fix the issue
284
+ await ClientConfig.getInstance().lockClientUntilNextPage('Too many reconnection attempts, locking until next page');
285
+ return;
286
+ }
287
+ // if we tried reconnecting too many times we abandon
288
+ if (this.connectionCount >= MAX_METROPLEX_CONNECTION_COUNT) {
289
+ // if we tried beyond the threshold, we block ourselves a short
290
+ // while while fix the issue
291
+ await ClientConfig.getInstance().lockClientUntilNextPage('Too many connections, locking until next page');
292
+ return;
293
+ }
294
+ // try to reconnect on close but after a certain delay based off unsuccessful connections
295
+ setTimeout(() => {
296
+ this.handleConnect(false);
297
+ }, this.currentConnectionAttempts ** 2 * GET_METROPLEX_CONSECUTIVE_CONNECTION_DELAY());
298
+ };
299
+ // everytime we get a message from the socket
300
+ this.socket.onmessage = event => {
301
+ noibuLog('metroplex handleConnect, onmessage', { event });
302
+ this._onSocketMessage(event);
303
+ };
304
+ // the moment we open the socket
305
+ this.socket.onopen = () => {
306
+ // track socket open codes and times
307
+ this.socketOpens.push(`${!isDateOverwritten() ? new Date().toISOString() : ''}`);
308
+ this._onSocketOpen();
309
+ };
389
310
  }
390
-
391
- this._sendSocketMessage(await this.getPageInformation());
392
- // Set this to allow normal page visit and video frag messages to be sent
393
- this.pageVisitInfoSent = true;
394
- this.currentConnectionAttempts = 0;
395
- // Resend the pagevisit or video message type before sending data so reset the prev type
396
- this.previousMessageType = '';
397
- // attempt to send the unconfirmed messages again
398
- this._sendUnconfirmedMessages(false);
399
- // setting up the socket retry mechanism
400
- this.setupRetryMechanism();
401
- // increasing the connection count everytime we connect
402
- this.connectionCount += 1;
403
- }
404
-
405
- /** message handler for socket
406
- * @param {} event
407
- * @param {} metroAckCallback
408
- */
409
- _onSocketMessage(event, metroAckCallback) {
410
- switch (event.data) {
411
- case STOP_STORING_VID_SOCKET_MESSAGE:
412
- // stopping vids from being sent
413
- this.metroplexTypeLock[VIDEO_METROPLEX_TYPE] = true;
414
- StoredMetrics.getInstance().setDidCutVideo();
415
- break;
416
- case STOP_STORING_PV_SOCKET_MESSAGE:
417
- // stopping pv from being sent
418
- this.metroplexTypeLock[PV_METROPLEX_TYPE] = true;
419
- StoredMetrics.getInstance().setDidCutPv();
420
- break;
421
- case BLOCK_SOCKET_MESSAGE:
422
- // we are disabling collect for 1 day if we get the
423
- // block message from metroplex. We are probably on it
424
- ClientConfig.getInstance().lockClient(1440, 'Metroplex blocked script');
425
- this.close();
426
- break;
427
- case CLOSE_CONNECTION_FORCEFULLY:
428
- this.close();
429
- break;
430
- case OK_SOCKET_MESSAGE:
431
- // we do nothing on an OK
432
- break;
433
- default:
434
- // we now need to check if we receive text that contains
435
- // the text SEQ_NUM. if that is true, then its a seq num
436
- // and we need to clear out the retry queue.
437
- if (event.data.includes(SEQ_NUM_ATT_NAME)) {
438
- const seqNumSplit = event.data.split(`${SEQ_NUM_ATT_NAME}:`);
439
- if (seqNumSplit.length < 2) {
440
- ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
441
- `Invalid message received from metroplex while clearing retry queue ${event.data}`,
442
- false,
443
- SEVERITY.error,
444
- );
445
- break;
446
- }
447
- // the second element in the string split is the sequence number
448
- const seqNum = parseInt(seqNumSplit[1], 10);
449
-
450
- // ignore sequence num if this is just page info, it will always be -1 and we don't want
451
- // to disable the retry loop on this
452
- if (seqNum === -1) {
453
- break;
454
- }
455
-
456
- // we disable the retry loop if we start running behind
457
- if (seqNum <= this.latestReceivedSeqNumber) {
458
- this.isRetryLoopDisabled = true;
459
- } else {
460
- this.isRetryLoopDisabled = false;
461
-
462
- // setting the latest received seqNum
463
- this.latestReceivedSeqNumber = seqNum;
464
-
465
- // clear the retry queue when for this seqNum and below
466
- this._clearRetryQueue(seqNum);
467
- }
468
-
469
- // Metroplex acknowledged at least once, so resolve the promise
470
- if (!this.ackedOnce && metroAckCallback) {
471
- this.ackedOnce = true;
472
- metroAckCallback();
473
- }
311
+ /**
312
+ * connectSocket will establish a websocket connection to the metroplex
313
+ * service
314
+ */
315
+ connectSocket() {
316
+ if (this.isConnected() || this.isConnecting()) {
317
+ // self resolving promise since we are already ready to send
318
+ // requests, if we did reach state 1, we already sent initial
319
+ // request, thus not sending it again.
320
+ return this.connectionPromise;
474
321
  }
475
-
476
- if (this._tryProcessHelpCodeResponse(event.data)) {
477
- break;
322
+ // we return a promisified socket resolver to be able to chain the
323
+ // opening of the socket
324
+ this.connectionPromise = this.handleConnect(false);
325
+ return this.connectionPromise;
326
+ }
327
+ /** Calculates and sets the end_at field of the payload
328
+ * @param {} payload
329
+ * @param {} isPageVisit
330
+ */
331
+ addEndTimeToPayload(payload, isPageVisit) {
332
+ const delta = Math.ceil(safePerformanceNow() - this.sessionStartTime);
333
+ // update session length if this is a page visit event
334
+ if (isPageVisit) {
335
+ this.sessionLength = delta;
478
336
  }
479
-
480
- break;
481
- // still need to collect the id sent back from metroplex
337
+ // Assigning this value here is much better than copying the whole object.
338
+ // eslint-disable-next-line no-param-reassign
339
+ payload[END_AT_ATT_NAME] = new Date(this.sessionTimestamp.getTime() + delta).toISOString();
482
340
  }
483
- }
484
-
485
- /**
486
- * Returns true if the message's payload has the payload type given and has a sequence
487
- * number higher than seqNum
488
- * @param {} message
489
- * @param {} payloadType
490
- * @param {} seqNum
491
- */
492
- _messagePayloadHasLargerSeqNum(message, payloadType, seqNum) {
493
- return (
494
- message.payload[payloadType] &&
495
- message.payload[payloadType][SEQ_NUM_ATT_NAME] &&
496
- message.payload[payloadType][SEQ_NUM_ATT_NAME] > seqNum
497
- );
498
- }
499
-
500
- /**
501
- * removes messages from the retry queue that are smaller than the
502
- * latest stored message in metroplex
503
- * @param {} seqNum
504
- */
505
- _clearRetryQueue(seqNum) {
506
- this.latestReceivedSeqNumStoredTime = new Date();
507
- this.retryMessageQueue = this.retryMessageQueue.filter(
508
- message =>
509
- this._messagePayloadHasLargerSeqNum(
510
- message,
511
- PAGE_VISIT_PART_ATT_NAME,
512
- seqNum,
513
- ) ||
514
- this._messagePayloadHasLargerSeqNum(
515
- message,
516
- PAGE_VISIT_VID_FRAG_ATT_NAME,
517
- seqNum,
518
- ),
519
- );
520
- }
521
-
522
- /** will resend everything that is in the retry queue */
523
- _sendUnconfirmedMessages(socketWasAlreadyOpen) {
524
- // we only check once. If the socket is disconnected midway, the events would
525
- // still be in the retry queue.
526
- if (!this.isConnected() || ClientConfig.getInstance().isClientDisabled) {
527
- return;
341
+ /** open handler for socket */
342
+ async _onSocketOpen() {
343
+ // if we are not connected, we do not send any messages
344
+ if (!this.isConnected() || ClientConfig.getInstance().isClientDisabled) {
345
+ return;
346
+ }
347
+ await this._sendSocketMessage(await this.getPageInformation());
348
+ // Set this to allow normal page visit and video frag messages to be sent
349
+ this.pageVisitInfoSent = true;
350
+ this.currentConnectionAttempts = 0;
351
+ // Resend the pagevisit or video message type before sending data so reset the prev type
352
+ this.previousMessageType = '';
353
+ // attempt to send the unconfirmed messages again
354
+ await this._sendUnconfirmedMessages(false);
355
+ // setting up the socket retry mechanism
356
+ this.setupRetryMechanism();
357
+ // increasing the connection count everytime we connect
358
+ this.connectionCount += 1;
528
359
  }
529
-
530
- // We always send if this is on socket open
531
- if (socketWasAlreadyOpen) {
532
- // we do not send the messages to metroplex if we are falling behind on received
533
- // sequence numbers
534
- const someTimeAgo = new Date();
535
- someTimeAgo.setMilliseconds(
536
- someTimeAgo.getMilliseconds() - this.metroRetryFrequencyMS,
537
- );
538
-
539
- if (someTimeAgo < this.latestReceivedSeqNumStoredTime) {
540
- return;
541
- }
542
-
543
- // we do not resend messages to metroplex if we are running behind
544
- if (this.isRetryLoopDisabled) {
545
- return;
546
- }
360
+ /** message handler for socket
361
+ * @param {} event
362
+ */
363
+ async _onSocketMessage(event) {
364
+ switch (event.data) {
365
+ case STOP_STORING_VID_SOCKET_MESSAGE:
366
+ // stopping vids from being sent
367
+ this.metroplexTypeLock[VIDEO_METROPLEX_TYPE] = true;
368
+ StoredMetrics.getInstance().setDidCutVideo();
369
+ break;
370
+ case STOP_STORING_PV_SOCKET_MESSAGE:
371
+ // stopping pv from being sent
372
+ this.metroplexTypeLock[PV_METROPLEX_TYPE] = true;
373
+ StoredMetrics.getInstance().setDidCutPv();
374
+ break;
375
+ case BLOCK_SOCKET_MESSAGE:
376
+ // we are disabling collect for 1 day if we get the
377
+ // block message from metroplex. We are probably on it
378
+ await ClientConfig.getInstance().lockClient(1440, 'Metroplex blocked script');
379
+ this.close();
380
+ break;
381
+ case CLOSE_CONNECTION_FORCEFULLY:
382
+ this.close();
383
+ break;
384
+ case OK_SOCKET_MESSAGE:
385
+ // we do nothing on an OK
386
+ break;
387
+ default:
388
+ // we now need to check if we receive text that contains
389
+ // the text SEQ_NUM. if that is true, then its a seq num
390
+ // and we need to clear out the retry queue.
391
+ if (event.data.includes(SEQ_NUM_ATT_NAME)) {
392
+ const seqNumSplit = event.data.split(`${SEQ_NUM_ATT_NAME}:`);
393
+ if (seqNumSplit.length < 2) {
394
+ ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`Invalid message received from metroplex while clearing retry queue ${event.data}`, false, SEVERITY.error);
395
+ break;
396
+ }
397
+ // the second element in the string split is the sequence number
398
+ const seqNum = parseInt(seqNumSplit[1], 10);
399
+ // ignore sequence num if this is just page info, it will always be -1 and we don't want
400
+ // to disable the retry loop on this
401
+ if (seqNum === -1) {
402
+ break;
403
+ }
404
+ // we disable the retry loop if we start running behind
405
+ if (seqNum <= this.latestReceivedSeqNumber) {
406
+ this.isRetryLoopDisabled = true;
407
+ }
408
+ else {
409
+ this.isRetryLoopDisabled = false;
410
+ // setting the latest received seqNum
411
+ this.latestReceivedSeqNumber = seqNum;
412
+ // clear the retry queue when for this seqNum and below
413
+ this._clearRetryQueue(seqNum);
414
+ }
415
+ if (!this.ackedOnce) {
416
+ this.ackedOnce = true;
417
+ }
418
+ }
419
+ if (this._tryProcessHelpCodeResponse(event.data)) {
420
+ break;
421
+ }
422
+ break;
423
+ // still need to collect the id sent back from metroplex
424
+ }
547
425
  }
548
-
549
- // removing all the messages with a blocked type from the retry queue
550
- this.retryMessageQueue = this.retryMessageQueue.filter(
551
- message => !(message.type in this.metroplexTypeLock),
552
- );
553
-
554
- // we dont remove any items from this queue in this function
555
- for (let i = 0; i < this.retryMessageQueue.length; i += 1) {
556
- const { type, payload } = this.retryMessageQueue[i];
557
-
558
- // sending the data to metroplex
559
- if (!this._sendSocketMessage(payload)) {
560
- break;
561
- }
562
-
563
- this.previousMessageType = type;
426
+ /**
427
+ * Returns true if the message's payload has the payload type given and has a sequence
428
+ * number higher than seqNum
429
+ */
430
+ _messagePayloadHasLargerSeqNum(message, payloadType, seqNum) {
431
+ return (message.payload[payloadType] &&
432
+ message.payload[payloadType][SEQ_NUM_ATT_NAME] &&
433
+ message.payload[payloadType][SEQ_NUM_ATT_NAME] > seqNum);
564
434
  }
565
- }
566
-
567
- /** sets up the interval to empty the queue as we receive confirmation messages from metroplex */
568
- setupRetryMechanism() {
569
- this.retryMetroplexInterval = setInterval(() => {
570
- this._sendUnconfirmedMessages(true);
571
- }, METROPLEX_RETRY_FREQUENCY);
572
- }
573
-
574
- /** sets up events that will trigger the event queue to be emptied */
575
- _setupOffloadEvents() {
576
- // when the page hides we try getting all the events
577
- // and empty the event and retry queue.
578
- addSafeEventListener(window, 'pagehide', () => {
579
- this._handleUnload();
580
- });
581
- }
582
-
583
- /**
584
- * will handle the final moments of a page being active. It
585
- * will try to empty both the queues with beacons.
586
- */
587
- _handleUnload() {
588
- // closing the socket since the page is unloading
589
- this.close();
590
-
591
- // if we disable the client within the pagevisit, we do not
592
- // post anything during the unloading.
593
- if (ClientConfig.getInstance().isClientDisabled) {
594
- return;
435
+ /**
436
+ * removes messages from the retry queue that are smaller than the
437
+ * latest stored message in metroplex
438
+ */
439
+ _clearRetryQueue(seqNum) {
440
+ this.latestReceivedSeqNumStoredTime = new Date();
441
+ this.retryMessageQueue = this.retryMessageQueue.filter(message => this._messagePayloadHasLargerSeqNum(message, PAGE_VISIT_PART_ATT_NAME, seqNum) ||
442
+ this._messagePayloadHasLargerSeqNum(message, PAGE_VISIT_VID_FRAG_ATT_NAME, seqNum));
595
443
  }
596
-
597
- // Don't send the final complete PV if the session has become inactive
598
- // we already sent the PV when went inactive
599
- if (ClientConfig.getInstance().isInactive()) {
600
- return;
444
+ /** will resend everything that is in the retry queue */
445
+ async _sendUnconfirmedMessages(socketWasAlreadyOpen) {
446
+ noibuLog('_sendUnconfirmedMessages');
447
+ // we only check once. If the socket is disconnected midway, the events would
448
+ // still be in the retry queue.
449
+ if (!this.isConnected() || ClientConfig.getInstance().isClientDisabled) {
450
+ return;
451
+ }
452
+ // We always send if this is on socket open
453
+ if (socketWasAlreadyOpen) {
454
+ // we do not send the messages to metroplex if we are falling behind on received
455
+ // sequence numbers
456
+ const someTimeAgo = new Date();
457
+ someTimeAgo.setMilliseconds(someTimeAgo.getMilliseconds() - this.metroRetryFrequencyMS);
458
+ if (someTimeAgo < this.latestReceivedSeqNumStoredTime) {
459
+ return;
460
+ }
461
+ // we do not resend messages to metroplex if we are running behind
462
+ if (this.isRetryLoopDisabled) {
463
+ return;
464
+ }
465
+ }
466
+ // removing all the messages with a blocked type from the retry queue
467
+ this.retryMessageQueue = this.retryMessageQueue.filter(message => !(message.type in this.metroplexTypeLock));
468
+ // we dont remove any items from this queue in this function
469
+ for (let i = 0; i < this.retryMessageQueue.length; i += 1) {
470
+ const { type, payload } = this.retryMessageQueue[i];
471
+ // sending the data to metroplex
472
+ // eslint-disable-next-line no-await-in-loop
473
+ await this._sendSocketMessage(payload);
474
+ this.previousMessageType = type;
475
+ }
601
476
  }
602
-
603
- this.postFullPageVisit(MAX_BEACON_PAYLOAD_SIZE);
604
- }
605
-
606
- /**
607
- * will post full page visit to metroplex. It
608
- * will try to empty both the queues with beacons.
609
- * @param {} maxMessageSize
610
- */
611
- async postFullPageVisit(maxMessageSize) {
612
- if (this.retryMessageQueue.length === 0) {
613
- return;
477
+ /** sets up the interval to empty the queue as we receive confirmation messages from metroplex */
478
+ setupRetryMechanism() {
479
+ this.retryMetroplexInterval = setInterval(() => {
480
+ this._sendUnconfirmedMessages(true);
481
+ }, METROPLEX_RETRY_FREQUENCY);
614
482
  }
615
-
616
- // debug counters
617
- const postInfo = [];
618
- const numDropped = {
619
- [VIDEO_METROPLEX_TYPE]: 0,
620
- [PV_METROPLEX_TYPE]: 0,
621
- };
622
-
623
- let currentMsgPayloadSize = 0;
624
- let currentCompletePv = {
625
- [PAGE_VISIT_INFORMATION_ATT_NAME]: await this.getPageInformation(),
626
- [PAGE_VISIT_PART_ATT_NAME]: [],
627
- [PAGE_VISIT_VID_FRAG_ATT_NAME]: [],
628
- [PAGE_VISIT_HTTP_DATA_ATT_NAME]: [],
629
- [VIDEO_PART_COUNT_ATT_NAME]: this.connectionCount,
630
- };
631
- currentCompletePv[PAGE_VISIT_INFORMATION_ATT_NAME][IS_LAST_ATT_NAME] = true;
632
- const pageInfo = await this.getPageInformation();
633
- this.retryMessageQueue.forEach(msg => {
634
- // can't use two variables types in obj deconstruction
635
- // eslint-disable-next-line prefer-const
636
- let { type, payload } = msg;
637
-
638
- // If the message is larger than the beacon limit then skip to the next message
639
- const currentPayloadSize = new Blob([stringifyJSON(payload)]).size;
640
- if (currentPayloadSize > maxMessageSize) {
641
- // increment dropped count for this type
642
- numDropped[type] += 1;
643
- return;
644
- }
645
-
646
- currentMsgPayloadSize += currentPayloadSize;
647
- if (currentMsgPayloadSize >= maxMessageSize) {
648
- this.postMessage(currentCompletePv);
649
- // add to post info
650
- let postInfoMessage = `Vid: ${currentCompletePv[PAGE_VISIT_VID_FRAG_ATT_NAME].length}`;
651
- postInfoMessage += ` PV: ${currentCompletePv[PAGE_VISIT_PART_ATT_NAME].length}`;
652
- postInfoMessage += ` HTTP: ${currentCompletePv[PAGE_VISIT_HTTP_DATA_ATT_NAME].length},`;
653
- postInfo.push(postInfoMessage);
654
-
655
- // resetting currentCompletePv, since we need to keep adding
656
- // events to a blank object to send to metroplex.
657
- // retain the video part count, so we don't overwrite anything
658
- currentCompletePv = {
659
- [PAGE_VISIT_INFORMATION_ATT_NAME]: pageInfo,
660
- [PAGE_VISIT_PART_ATT_NAME]: [],
661
- [PAGE_VISIT_VID_FRAG_ATT_NAME]: [],
662
- [PAGE_VISIT_HTTP_DATA_ATT_NAME]: [],
663
- [VIDEO_PART_COUNT_ATT_NAME]:
664
- currentCompletePv[VIDEO_PART_COUNT_ATT_NAME],
483
+ /** sets up events that will trigger the event queue to be emptied */
484
+ _setupOffloadEvents() {
485
+ // when the page hides we try getting all the events
486
+ // and empty the event and retry queue.
487
+ addSafeEventListener(window, 'pagehide', () => this._handleUnload(), false);
488
+ }
489
+ /**
490
+ * will handle the final moments of a page being active. It
491
+ * will try to empty both the queues with beacons.
492
+ */
493
+ async _handleUnload() {
494
+ // closing the socket since the page is unloading
495
+ this.close();
496
+ // if we disable the client within the pagevisit, we do not
497
+ // post anything during the unloading.
498
+ if (ClientConfig.getInstance().isClientDisabled) {
499
+ return;
500
+ }
501
+ // Don't send the final complete PV if the session has become inactive
502
+ // we already sent the PV when went inactive
503
+ if (ClientConfig.getInstance().isInactive()) {
504
+ return;
505
+ }
506
+ await this.postFullPageVisit(MAX_BEACON_PAYLOAD_SIZE);
507
+ }
508
+ /**
509
+ * will post full page visit to metroplex. It
510
+ * will try to empty both the queues with beacons.
511
+ */
512
+ async postFullPageVisit(maxMessageSize) {
513
+ if (this.retryMessageQueue.length === 0) {
514
+ return;
515
+ }
516
+ // debug counters
517
+ const postInfo = [];
518
+ const numDropped = {
519
+ [VIDEO_METROPLEX_TYPE]: 0,
520
+ [PV_METROPLEX_TYPE]: 0,
665
521
  };
666
-
667
- currentCompletePv[PAGE_VISIT_INFORMATION_ATT_NAME][
668
- IS_LAST_ATT_NAME
669
- ] = true;
670
-
671
- // resetting the message size based on what is being added
672
- currentMsgPayloadSize = currentPayloadSize;
673
- }
674
-
675
- switch (type) {
676
- case VIDEO_METROPLEX_TYPE:
677
- currentCompletePv[PAGE_VISIT_VID_FRAG_ATT_NAME].push(
678
- payload[PAGE_VISIT_VID_FRAG_ATT_NAME],
679
- );
680
- break;
681
- case PV_METROPLEX_TYPE:
682
- currentCompletePv[PAGE_VISIT_PART_ATT_NAME].push(
683
- payload[PAGE_VISIT_PART_ATT_NAME],
684
- );
685
- break;
686
- case HTTP_DATA_METROPLEX_TYPE:
687
- currentCompletePv[PAGE_VISIT_HTTP_DATA_ATT_NAME].push(
688
- payload[PAGE_VISIT_HTTP_DATA_ATT_NAME],
689
- );
690
- break;
691
- }
692
- });
693
-
694
- this.postMessage(currentCompletePv);
695
-
696
- // debug log if large retry message queue
697
- if (this.retryMessageQueue.length > 100) {
698
- let postInfoMessage = `Vid: ${currentCompletePv[PAGE_VISIT_VID_FRAG_ATT_NAME].length}`;
699
- postInfoMessage += ` PV: ${currentCompletePv[PAGE_VISIT_PART_ATT_NAME].length}`;
700
- postInfoMessage += ` HTTP: ${currentCompletePv[PAGE_VISIT_HTTP_DATA_ATT_NAME].length},`;
701
- postInfo.push(postInfoMessage);
702
-
703
- // we completed posted the full pv, send the confirmation debug logs
704
- // build the debug message
705
- let message = 'POST Full PV complete';
706
-
707
- // POST counts
708
- message += `, POSTs count: ${postInfo.length}`;
709
- message += `, POSTs info: ${stringifyJSON(postInfo)}`;
710
-
711
- // Initial retry message queue size
712
- message += `, Retry message queue size: ${this.retryMessageQueue.length}`;
713
-
714
- // Num drops
715
- if (numDropped[VIDEO_METROPLEX_TYPE] > 0) {
716
- message += `, Video parts dropped: ${numDropped[VIDEO_METROPLEX_TYPE]}`;
717
- }
718
- if (numDropped[PV_METROPLEX_TYPE] > 0) {
719
- message += `, Page visit parts dropped: ${numDropped[PV_METROPLEX_TYPE]}`;
720
- }
721
- if (numDropped[HTTP_DATA_METROPLEX_TYPE] > 0) {
722
- message += `, HTTP data parts dropped: ${numDropped[HTTP_DATA_METROPLEX_TYPE]}`;
723
- }
724
-
725
- // Sequence info
726
- message += `, Sequence Info: Latest ${this.messageSequenceNum}`;
727
- message += ` Ack'd ${this.latestReceivedSeqNumStoredTime} ${this.latestReceivedSeqNumber}`;
728
-
729
- // if client was disabled (due to inactive or otherwise) enable briefly so the
730
- // debug message gets through
731
- const clientDisabled = ClientConfig.getInstance().isClientDisabled;
732
- ClientConfig.getInstance().isClientDisabled = false;
733
- ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
734
- message,
735
- clientDisabled,
736
- SEVERITY.warn,
737
- );
522
+ let currentMsgPayloadSize = 0;
523
+ let currentCompletePv = {
524
+ [PAGE_VISIT_INFORMATION_ATT_NAME]: await this.getPageInformation(),
525
+ [PAGE_VISIT_PART_ATT_NAME]: [],
526
+ [PAGE_VISIT_VID_FRAG_ATT_NAME]: [],
527
+ [PAGE_VISIT_HTTP_DATA_ATT_NAME]: [],
528
+ [VIDEO_PART_COUNT_ATT_NAME]: this.connectionCount,
529
+ };
530
+ currentCompletePv[PAGE_VISIT_INFORMATION_ATT_NAME][IS_LAST_ATT_NAME] = true;
531
+ const pageInfo = await this.getPageInformation();
532
+ this.retryMessageQueue.forEach(msg => {
533
+ // can't use two variables types in obj deconstruction
534
+ // eslint-disable-next-line prefer-const
535
+ let { type, payload } = msg;
536
+ // If the message is larger than the beacon limit then skip to the next message
537
+ const currentPayloadSize = new Blob([stringifyJSON(payload)]).size;
538
+ if (currentPayloadSize > maxMessageSize) {
539
+ // increment dropped count for this type
540
+ numDropped[type] += 1;
541
+ return;
542
+ }
543
+ currentMsgPayloadSize += currentPayloadSize;
544
+ if (currentMsgPayloadSize >= maxMessageSize) {
545
+ this.postMessage(currentCompletePv);
546
+ // add to post info
547
+ let postInfoMessage = `Vid: ${currentCompletePv[PAGE_VISIT_VID_FRAG_ATT_NAME].length}`;
548
+ postInfoMessage += ` PV: ${currentCompletePv[PAGE_VISIT_PART_ATT_NAME].length}`;
549
+ postInfoMessage += ` HTTP: ${currentCompletePv[PAGE_VISIT_HTTP_DATA_ATT_NAME].length},`;
550
+ postInfo.push(postInfoMessage);
551
+ // resetting currentCompletePv, since we need to keep adding
552
+ // events to a blank object to send to metroplex.
553
+ // retain the video part count, so we don't overwrite anything
554
+ currentCompletePv = {
555
+ [PAGE_VISIT_INFORMATION_ATT_NAME]: pageInfo,
556
+ [PAGE_VISIT_PART_ATT_NAME]: [],
557
+ [PAGE_VISIT_VID_FRAG_ATT_NAME]: [],
558
+ [PAGE_VISIT_HTTP_DATA_ATT_NAME]: [],
559
+ [VIDEO_PART_COUNT_ATT_NAME]: currentCompletePv[VIDEO_PART_COUNT_ATT_NAME],
560
+ };
561
+ currentCompletePv[PAGE_VISIT_INFORMATION_ATT_NAME][IS_LAST_ATT_NAME] =
562
+ true;
563
+ // resetting the message size based on what is being added
564
+ currentMsgPayloadSize = currentPayloadSize;
565
+ }
566
+ switch (type) {
567
+ case VIDEO_METROPLEX_TYPE:
568
+ currentCompletePv[PAGE_VISIT_VID_FRAG_ATT_NAME].push(payload[PAGE_VISIT_VID_FRAG_ATT_NAME]);
569
+ break;
570
+ case PV_METROPLEX_TYPE:
571
+ currentCompletePv[PAGE_VISIT_PART_ATT_NAME].push(payload[PAGE_VISIT_PART_ATT_NAME]);
572
+ break;
573
+ case HTTP_DATA_METROPLEX_TYPE:
574
+ currentCompletePv[PAGE_VISIT_HTTP_DATA_ATT_NAME].push(payload[PAGE_VISIT_HTTP_DATA_ATT_NAME]);
575
+ break;
576
+ }
577
+ });
578
+ this.postMessage(currentCompletePv);
579
+ // debug log if large retry message queue
580
+ if (this.retryMessageQueue.length > 100) {
581
+ let postInfoMessage = `Vid: ${currentCompletePv[PAGE_VISIT_VID_FRAG_ATT_NAME].length}`;
582
+ postInfoMessage += ` PV: ${currentCompletePv[PAGE_VISIT_PART_ATT_NAME].length}`;
583
+ postInfoMessage += ` HTTP: ${currentCompletePv[PAGE_VISIT_HTTP_DATA_ATT_NAME].length},`;
584
+ postInfo.push(postInfoMessage);
585
+ // we completed posted the full pv, send the confirmation debug logs
586
+ // build the debug message
587
+ let message = 'POST Full PV complete';
588
+ // POST counts
589
+ message += `, POSTs count: ${postInfo.length}`;
590
+ message += `, POSTs info: ${stringifyJSON(postInfo)}`;
591
+ // Initial retry message queue size
592
+ message += `, Retry message queue size: ${this.retryMessageQueue.length}`;
593
+ // Num drops
594
+ if (numDropped[VIDEO_METROPLEX_TYPE] > 0) {
595
+ message += `, Video parts dropped: ${numDropped[VIDEO_METROPLEX_TYPE]}`;
596
+ }
597
+ if (numDropped[PV_METROPLEX_TYPE] > 0) {
598
+ message += `, Page visit parts dropped: ${numDropped[PV_METROPLEX_TYPE]}`;
599
+ }
600
+ if (numDropped[HTTP_DATA_METROPLEX_TYPE] > 0) {
601
+ message += `, HTTP data parts dropped: ${numDropped[HTTP_DATA_METROPLEX_TYPE]}`;
602
+ }
603
+ // Sequence info
604
+ message += `, Sequence Info: Latest ${this.messageSequenceNum}`;
605
+ message += ` Ack'd ${this.latestReceivedSeqNumStoredTime} ${this.latestReceivedSeqNumber}`;
606
+ // if client was disabled (due to inactive or otherwise) enable briefly so the
607
+ // debug message gets through
608
+ const clientDisabled = ClientConfig.getInstance().isClientDisabled;
609
+ ClientConfig.getInstance().isClientDisabled = false;
610
+ ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(message, clientDisabled, SEVERITY.warn);
611
+ }
738
612
  }
739
- }
740
-
741
- /**
742
- * will send a message to metroplex via a post request that will outlive the current page
743
- * @param {} msg
744
- */
745
- async postMessage(msg) {
746
- const updatedMsg = msg;
747
-
748
- // ensure a unique video part number each call
749
- updatedMsg[VIDEO_PART_COUNT_ATT_NAME] += 1;
750
-
751
- // we send the remainder elements
752
- global.fetch(this.postURL, {
753
- method: 'POST',
754
- headers: {
755
- 'content-type': 'application/json',
756
- 'User-Agent': await getUserAgent(),
757
- },
758
- body: stringifyJSON(updatedMsg),
759
- // keep alive outlives the current page, its the same as beacon
760
- keepalive: true,
761
- });
762
- }
763
-
764
- /**
765
- * Stringifies the payload into JSON, sends it to the back end if the session
766
- * is active and returns true. If inactive the session and socket are closed
767
- * and this method returns false.
768
- * @param {} payload
769
- */
770
- _sendSocketMessage(payload) {
771
- if (this.closeIfInactive()) {
772
- return false;
613
+ /**
614
+ * will send a message to metroplex via a post request that will outlive the current page
615
+ */
616
+ async postMessage(msg) {
617
+ const updatedMsg = msg;
618
+ // ensure a unique video part number each call
619
+ updatedMsg[VIDEO_PART_COUNT_ATT_NAME] += 1;
620
+ // we send the remainder elements
621
+ unwrapNoibuWrapped(fetch)(this.postURL, {
622
+ method: 'POST',
623
+ headers: {
624
+ 'content-type': 'application/json',
625
+ 'User-Agent': await getUserAgent(),
626
+ },
627
+ body: stringifyJSON(updatedMsg),
628
+ // keep alive outlives the current page, its the same as beacon
629
+ keepalive: true,
630
+ });
773
631
  }
774
- this.socket.send(stringifyJSON(payload));
775
- return true;
776
- }
777
-
778
- /**
779
- * Closes the socket connection if the session is inactive. Returns true if the
780
- * session is inactive
781
- */
782
- closeIfInactive() {
783
- const inactive = ClientConfig.getInstance().isInactive();
784
- // if we weren't already inactive, go to inactive state
785
- if (inactive && !ClientConfig.getInstance().isClientDisabled) {
786
- // lock the client for 1 minute, since the lock expires
787
- // only on new page loads this will lock the client until
788
- // the next page is loaded.
789
- ClientConfig.getInstance().lockClientUntilNextPage(
790
- 'Session is inactive, locking until next page',
791
- );
792
- this.close();
793
-
794
- // post metrics now so we don't include events after going inactive
795
- StoredMetrics.getInstance().postMetrics('inactive');
796
-
797
- // post retry queue now so we don't include event after going inactive
798
- this.postFullPageVisit(MAX_BEACON_PAYLOAD_SIZE);
632
+ /**
633
+ * Stringifies the payload into JSON, sends it to the back end if the session
634
+ * is active and returns true. If inactive the session and socket are closed
635
+ * and this method returns false.
636
+ */
637
+ async _sendSocketMessage(payload) {
638
+ noibuLog('_sendSocketMessage');
639
+ const closeIfInactive = await this.closeIfInactive();
640
+ if (closeIfInactive) {
641
+ noibuLog('_sendSocketMessage dropped due to', { closeIfInactive });
642
+ return;
643
+ }
644
+ const payloadJson = stringifyJSON(payload);
645
+ noibuLog(`_sendSocketMessage sending: ${payloadJson}`);
646
+ if (this.socket) {
647
+ this.socket.send(payloadJson);
648
+ }
799
649
  }
800
-
801
- return inactive;
802
- }
803
-
804
- /** will get page information, calling this will increase the connection count */
805
- async getPageInformation() {
806
- const pvi = {
807
- [BROWSER_ID_ATT_NAME]: ClientConfig.getInstance().browserId,
808
- [PV_ID_ATT_NAME]: ClientConfig.getInstance().pageVisitId,
809
- [VER_ATT_NAME]: CURRENT_PV_VERSION,
810
- [PV_SEQ_ATT_NAME]: await ClientConfig.getInstance().getPageVisitSeq(),
811
- [ON_URL_ATT_NAME]: this._initialURL,
812
- [REF_URL_ATT_NAME]: this.initialReferingURL,
813
- [STARTED_AT_ATT_NAME]: this.sessionTimestamp.toISOString(),
814
- [CONN_COUNT_ATT_NAME]: this.connectionCount,
815
- [COLLECT_VER_ATT_NAME]: CURRENT_NOIBUJS_VERSION,
816
- [IS_LAST_ATT_NAME]: false,
817
- [SCRIPT_ID_ATT_NAME]: GET_SCRIPT_ID(),
818
- [SCRIPT_INSTANCE_ID_ATT_NAME]: this.scriptInstanceId,
819
- [METROPLEX_SOCKET_INSTANCE_ID_ATT_NAME]: this.instanceId,
820
- [SOCKET_INSTANCE_ID_ATT_NAME]: this.socketInstanceId,
821
- };
822
-
823
- return pvi;
824
- }
825
-
826
- /**
827
- * Try to parse help code response and fire custom event
828
- * @param {String} response
829
- */
830
- _tryProcessHelpCodeResponse(response) {
831
- const prefix = `${HELP_CODE_ATT_NAME}:`;
832
-
833
- if (typeof response !== 'string' || !response.startsWith(prefix)) {
834
- return false;
650
+ /**
651
+ * Closes the socket connection if the session is inactive. Returns true if the
652
+ * session is inactive
653
+ */
654
+ async closeIfInactive() {
655
+ const inactive = ClientConfig.getInstance().isInactive();
656
+ // if we weren't already inactive, go to inactive state
657
+ if (inactive && !ClientConfig.getInstance().isClientDisabled) {
658
+ // lock the client for 1 minute, since the lock expires
659
+ // only on new page loads this will lock the client until
660
+ // the next page is loaded.
661
+ await ClientConfig.getInstance().lockClientUntilNextPage('Session is inactive, locking until next page');
662
+ this.close();
663
+ Promise.all([
664
+ // post metrics now so we don't include events after going inactive
665
+ StoredMetrics.getInstance().postMetrics(),
666
+ // post retry queue now so we don't include event after going inactive
667
+ this.postFullPageVisit(MAX_BEACON_PAYLOAD_SIZE),
668
+ ]);
669
+ }
670
+ return inactive;
671
+ }
672
+ /** will get page information, calling this will increase the connection count */
673
+ async getPageInformation() {
674
+ return {
675
+ [BROWSER_ID_ATT_NAME]: ClientConfig.getInstance().browserId,
676
+ [PV_ID_ATT_NAME]: ClientConfig.getInstance().pageVisitId,
677
+ [VER_ATT_NAME]: CURRENT_PV_VERSION,
678
+ [PV_SEQ_ATT_NAME]: await ClientConfig.getInstance().getPageVisitSeq(),
679
+ [ON_URL_ATT_NAME]: this._initialURL,
680
+ [REF_URL_ATT_NAME]: this.initialReferingURL,
681
+ [STARTED_AT_ATT_NAME]: this.sessionTimestamp.toISOString(),
682
+ [CONN_COUNT_ATT_NAME]: this.connectionCount,
683
+ [COLLECT_VER_ATT_NAME]: CURRENT_NOIBUJS_VERSION,
684
+ [IS_LAST_ATT_NAME]: false,
685
+ [SCRIPT_ID_ATT_NAME]: GET_SCRIPT_ID(),
686
+ [SCRIPT_INSTANCE_ID_ATT_NAME]: this.scriptInstanceId,
687
+ [METROPLEX_SOCKET_INSTANCE_ID_ATT_NAME]: this.instanceId,
688
+ [SOCKET_INSTANCE_ID_ATT_NAME]: this.socketInstanceId,
689
+ };
690
+ }
691
+ /**
692
+ * Try to parse help code response and fire custom event
693
+ * @param {String} response
694
+ */
695
+ _tryProcessHelpCodeResponse(response) {
696
+ const prefix = `${HELP_CODE_ATT_NAME}:`;
697
+ if (typeof response !== 'string' || !response.startsWith(prefix)) {
698
+ return false;
699
+ }
700
+ const data = response.substring(prefix.length);
701
+ const success = /^\d{6}$/.test(data);
702
+ if (this.helpCodeCb) {
703
+ this.helpCodeCb({ detail: { success, data } });
704
+ }
705
+ return true;
835
706
  }
836
-
837
- const data = response.substring(prefix.length);
838
- const success = /^\d{6}$/.test(data);
839
-
840
- this.helpCodeCb({ detail: { success, data } });
841
-
842
- return true;
843
- }
844
707
  }
845
708
 
846
709
  export { MetroplexSocket as default };