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