noibu-react-native 0.2.2 → 0.2.4

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 (100) hide show
  1. package/README.md +1 -1
  2. package/dist/api/clientConfig.js +225 -217
  3. package/dist/api/helpCode.js +61 -87
  4. package/dist/api/metroplexSocket.js +460 -463
  5. package/dist/api/storedPageVisit.js +150 -208
  6. package/dist/constants.js +10 -2
  7. package/dist/entry/init.js +65 -63
  8. package/dist/monitors/{appNavigationMonitor.js → AppNavigationMonitor.js} +12 -22
  9. package/dist/monitors/ClickMonitor.js +198 -0
  10. package/dist/monitors/ErrorMonitor.js +206 -0
  11. package/dist/monitors/KeyboardInputMonitor.js +60 -0
  12. package/dist/monitors/PageMonitor.js +98 -0
  13. package/dist/monitors/RequestMonitor.js +390 -0
  14. package/dist/monitors/http-tools/GqlErrorValidator.js +259 -0
  15. package/dist/monitors/http-tools/HTTPDataBundler.js +458 -0
  16. package/dist/monitors/integrations/react-native-navigation-integration.js +4 -2
  17. package/dist/pageVisit/EventDebouncer.js +99 -0
  18. package/dist/pageVisit/pageVisitEventError.js +2 -2
  19. package/dist/pageVisit/pageVisitEventHTTP.js +79 -93
  20. package/dist/react/ErrorBoundary.js +18 -15
  21. package/dist/sessionRecorder/nativeSessionRecorderSubscription.js +3 -2
  22. package/dist/sessionRecorder/sessionRecorder.js +152 -151
  23. package/dist/{api → src/api}/clientConfig.d.ts +2 -2
  24. package/dist/{api → src/api}/helpCode.d.ts +10 -16
  25. package/dist/{api → src/api}/metroplexSocket.d.ts +48 -67
  26. package/dist/{api → src/api}/storedPageVisit.d.ts +12 -21
  27. package/dist/{constants.d.ts → src/constants.d.ts} +45 -0
  28. package/dist/{entry → src/entry}/init.d.ts +1 -1
  29. package/dist/src/monitors/AppNavigationMonitor.d.ts +18 -0
  30. package/dist/src/monitors/ClickMonitor.d.ts +31 -0
  31. package/dist/src/monitors/ErrorMonitor.d.ts +63 -0
  32. package/dist/{monitors/keyboardInputMonitor.d.ts → src/monitors/KeyboardInputMonitor.d.ts} +7 -4
  33. package/dist/{monitors/pageMonitor.d.ts → src/monitors/PageMonitor.d.ts} +6 -8
  34. package/dist/src/monitors/RequestMonitor.d.ts +94 -0
  35. package/dist/src/monitors/http-tools/GqlErrorValidator.d.ts +59 -0
  36. package/dist/src/monitors/http-tools/HTTPDataBundler.d.ts +112 -0
  37. package/dist/{monitors → src/monitors}/integrations/react-native-navigation-integration.d.ts +3 -2
  38. package/dist/src/pageVisit/EventDebouncer.d.ts +24 -0
  39. package/dist/{pageVisit → src/pageVisit}/pageVisit.d.ts +1 -1
  40. package/dist/src/pageVisit/pageVisitEventHTTP.d.ts +25 -0
  41. package/dist/{sessionRecorder → src/sessionRecorder}/types.d.ts +1 -1
  42. package/dist/{storage → src/storage}/rnStorageProvider.d.ts +1 -1
  43. package/dist/{storage → src/storage}/storage.d.ts +2 -2
  44. package/dist/{storage → src/storage}/storageProvider.d.ts +3 -3
  45. package/dist/{utils → src/utils}/function.d.ts +27 -7
  46. package/dist/{utils → src/utils}/object.d.ts +11 -8
  47. package/dist/src/utils/piiRedactor.d.ts +11 -0
  48. package/dist/src/utils/polyfills.d.ts +4 -0
  49. package/dist/storage/rnStorageProvider.js +7 -4
  50. package/dist/storage/storage.js +43 -35
  51. package/dist/storage/storageProvider.js +23 -19
  52. package/dist/types/Config.d.ts +24 -20
  53. package/dist/types/Metroplex.types.d.ts +73 -0
  54. package/dist/types/Monitor.d.ts +11 -0
  55. package/dist/types/Monitor.js +19 -0
  56. package/dist/types/PageVisit.types.d.ts +8 -0
  57. package/dist/types/PageVisitErrors.types.d.ts +114 -0
  58. package/dist/types/PageVisitEvents.types.d.ts +91 -0
  59. package/dist/types/PageVisitMetrics.types.d.ts +27 -0
  60. package/dist/types/Storage.d.ts +1 -1
  61. package/dist/types/StoredPageVisit.types.d.ts +4 -47
  62. package/dist/types/WrappedObjects.d.ts +6 -0
  63. package/dist/utils/function.js +110 -77
  64. package/dist/utils/object.js +59 -6
  65. package/dist/utils/piiRedactor.js +98 -0
  66. package/dist/utils/polyfills.js +24 -0
  67. package/package.json +8 -8
  68. package/dist/monitors/appNavigationMonitor.d.ts +0 -22
  69. package/dist/monitors/clickMonitor.d.ts +0 -44
  70. package/dist/monitors/clickMonitor.js +0 -251
  71. package/dist/monitors/errorMonitor.d.ts +0 -28
  72. package/dist/monitors/errorMonitor.js +0 -180
  73. package/dist/monitors/gqlErrorValidator.d.ts +0 -82
  74. package/dist/monitors/gqlErrorValidator.js +0 -306
  75. package/dist/monitors/httpDataBundler.d.ts +0 -161
  76. package/dist/monitors/httpDataBundler.js +0 -725
  77. package/dist/monitors/inputMonitor.d.ts +0 -34
  78. package/dist/monitors/inputMonitor.js +0 -138
  79. package/dist/monitors/keyboardInputMonitor.js +0 -66
  80. package/dist/monitors/pageMonitor.js +0 -122
  81. package/dist/monitors/requestMonitor.d.ts +0 -10
  82. package/dist/monitors/requestMonitor.js +0 -401
  83. package/dist/pageVisit/pageVisitEventHTTP.d.ts +0 -18
  84. package/dist/types/PageVisit.d.ts +0 -22
  85. package/dist/types/ReactNative.d.ts +0 -4
  86. package/dist/types/globals.d.ts +0 -45
  87. /package/dist/{api → src/api}/inputManager.d.ts +0 -0
  88. /package/dist/{api → src/api}/storedMetrics.d.ts +0 -0
  89. /package/dist/{const_matchers.d.ts → src/const_matchers.d.ts} +0 -0
  90. /package/dist/{entry → src/entry}/index.d.ts +0 -0
  91. /package/dist/{pageVisit → src/pageVisit}/pageVisitEventError.d.ts +0 -0
  92. /package/dist/{pageVisit → src/pageVisit}/userStep.d.ts +0 -0
  93. /package/dist/{react → src/react}/ErrorBoundary.d.ts +0 -0
  94. /package/dist/{sessionRecorder → src/sessionRecorder}/nativeSessionRecorderSubscription.d.ts +0 -0
  95. /package/dist/{sessionRecorder → src/sessionRecorder}/sessionRecorder.d.ts +0 -0
  96. /package/dist/{utils → src/utils}/date.d.ts +0 -0
  97. /package/dist/{utils → src/utils}/eventlistener.d.ts +0 -0
  98. /package/dist/{utils → src/utils}/log.d.ts +0 -0
  99. /package/dist/{utils → src/utils}/performance.d.ts +0 -0
  100. /package/dist/{utils → src/utils}/stacktrace-parser.d.ts +0 -0
@@ -1,112 +1,98 @@
1
- import { getMaxSubstringAllowed, asString } from '../utils/function.js';
2
1
  import { timestampWrapper } from '../utils/date.js';
3
- import { InputMonitor } from '../monitors/inputMonitor.js';
4
- import { HTTP_METHOD_ATT_NAME, MAX_HTTP_DATA_EVENT_COUNT, PV_SEQ_ATT_NAME, HTTP_DATA_METROPLEX_TYPE, HTTP_EVENT_TYPE, PAGE_VISIT_HTTP_DATA_ATT_NAME } from '../constants.js';
2
+ import { HTTP_RESP_CODE_ATT_NAME, HTTP_RESP_TIME_ATT_NAME, HTTP_METHOD_ATT_NAME, URL_ATT_NAME, PV_SEQ_ATT_NAME, PAGE_VISIT_HTTP_DATA_ATT_NAME, HTTP_DATA_METROPLEX_TYPE, HTTP_EVENT_TYPE, MAX_HTTP_DATA_EVENT_COUNT, MAX_HTTP_DATA_IF_ERROR_EVENT_COUNT } from '../constants.js';
5
3
  import { PageVisit } from './pageVisit.js';
6
4
  import StoredMetrics from '../api/storedMetrics.js';
7
5
  import MetroplexSocket from '../api/metroplexSocket.js';
8
- import { noibuLog } from '../utils/log.js';
6
+ import { getMaxSubstringAllowed, asString, safeTrim } from '../utils/function.js';
7
+ import { EventDebouncer } from './EventDebouncer.js';
9
8
 
10
9
  /** @module PageVisitEventHTTP */
11
-
12
10
  /**
13
11
  * Determines if a response is a failure
14
- * @param {number} code
15
12
  */
16
13
  function isHttpCodeFailure(code) {
17
- if (typeof code !== 'number') {
18
- return true;
19
- }
20
-
21
- return code >= 400 || code <= 0;
14
+ if (typeof code !== 'number') {
15
+ return true;
16
+ }
17
+ return code >= 400 || code <= 0;
22
18
  }
23
-
24
19
  /** Class representing a PageVisitEventHTTP */
25
20
  class PageVisitEventHTTP {
26
- /**
27
- * Creates an instance of the http event for the pv
28
- * @param {} httpEvent
29
- * @param {} httpData
30
- */
31
- constructor(httpEvent, httpData) {
32
- const mutatedHttpEvent = httpEvent;
33
- // TODO: implement attribute checking in this function
34
- // we force the response time to at least be 0 if it is negative or if its not present
35
- if (!mutatedHttpEvent.resp_time || mutatedHttpEvent.resp_time < 0) {
36
- mutatedHttpEvent.resp_time = 0;
37
- }
38
-
39
- // setting the method to be upper case
40
- mutatedHttpEvent[HTTP_METHOD_ATT_NAME] =
41
- httpEvent[HTTP_METHOD_ATT_NAME].toUpperCase();
42
-
43
- mutatedHttpEvent.url = getMaxSubstringAllowed(
44
- asString(mutatedHttpEvent.url),
45
- );
46
-
47
- this.httpEvent = mutatedHttpEvent;
48
- this.httpData = httpData;
49
- }
50
-
51
- /** Saves the HTTP event to the pageVisit Queue */
52
- saveHTTPEvent() {
53
- noibuLog('saveHTTPEvent');
54
- // we do not store http events that have empty urls
55
- if (
56
- !this.httpEvent ||
57
- !this.httpEvent.url ||
58
- this.httpEvent.url.trim() === ''
59
- ) {
60
- noibuLog('saveHTTPEvent dropped due to empty url');
61
- return;
21
+ /**
22
+ * Creates an instance of the http event for the pv
23
+ */
24
+ constructor(httpEvent, httpData, isGqlError = false) {
25
+ /** if no value or it's less than 0, fallback to 0 */
26
+ const validate = (value) => (!value || value < 0 ? 0 : value);
27
+ this.httpEvent = {
28
+ [HTTP_RESP_CODE_ATT_NAME]: validate(httpEvent[HTTP_RESP_CODE_ATT_NAME]),
29
+ [HTTP_RESP_TIME_ATT_NAME]: validate(httpEvent[HTTP_RESP_TIME_ATT_NAME]),
30
+ [HTTP_METHOD_ATT_NAME]: (httpEvent[HTTP_METHOD_ATT_NAME] || 'get').toUpperCase(),
31
+ [URL_ATT_NAME]: getMaxSubstringAllowed(asString(httpEvent[URL_ATT_NAME])),
32
+ };
33
+ this.httpData = httpData;
34
+ this.isGqlError = isGqlError;
62
35
  }
63
- // we register an http event
64
- StoredMetrics.getInstance().addHttpEvent();
65
-
66
- // send http data down to metroplex
67
- if (this.httpData) {
68
- noibuLog('saveHTTPEvent got httpData, working');
69
- // add the sequence number to both events
70
- const sequenceNumber = StoredMetrics.getInstance().httpSequenceNumber;
71
- // restrict total number of events collected per page visit to ensure we don't
72
- // blow up memory and storage usage
73
- if (sequenceNumber < MAX_HTTP_DATA_EVENT_COUNT) {
74
- this.httpData[PV_SEQ_ATT_NAME] = sequenceNumber;
75
- this.httpEvent[PV_SEQ_ATT_NAME] = sequenceNumber;
76
- // increment the count
77
- StoredMetrics.getInstance().addHttpData();
78
-
79
- const metroplexMsg = {};
80
- metroplexMsg[PAGE_VISIT_HTTP_DATA_ATT_NAME] = this.httpData;
81
- MetroplexSocket.getInstance().sendMessage(
82
- HTTP_DATA_METROPLEX_TYPE,
83
- metroplexMsg,
84
- );
85
- } else {
86
- noibuLog(
87
- 'saveHTTPEvent have collected more than the max number of http requests for this page visit, so increment the over request limit count',
88
- );
89
- // have collected more than the max number of http requests for this
90
- // page visit, so increment the over request limit count
91
- StoredMetrics.getInstance().addHttpDataOverLimit();
92
- }
36
+ /** Saves the HTTP event to the pageVisit Queue */
37
+ saveHTTPEvent() {
38
+ // we do not store http events that have empty urls
39
+ if (!this.httpEvent || !safeTrim(this.httpEvent[URL_ATT_NAME])) {
40
+ return;
41
+ }
42
+ // we register an http event
43
+ StoredMetrics.getInstance().addHttpEvent();
44
+ const status = this.httpEvent[HTTP_RESP_CODE_ATT_NAME];
45
+ // send http data down to metroplex
46
+ if (this.httpData) {
47
+ // add the sequence number to both events
48
+ const sequenceNumber = StoredMetrics.getInstance().httpSequenceNumber;
49
+ const isSendAllowed = PageVisitEventHTTP.isSendAllowed(status, sequenceNumber, this.isGqlError);
50
+ // restrict total number of events collected per page visit to ensure we don't
51
+ // blow up memory and storage usage
52
+ if (isSendAllowed) {
53
+ this.httpData[PV_SEQ_ATT_NAME] = sequenceNumber;
54
+ this.httpEvent[PV_SEQ_ATT_NAME] = sequenceNumber;
55
+ // increment the count
56
+ StoredMetrics.getInstance().addHttpData();
57
+ const metroplexMsg = {
58
+ [PAGE_VISIT_HTTP_DATA_ATT_NAME]: this.httpData,
59
+ };
60
+ MetroplexSocket.getInstance().sendMessage(HTTP_DATA_METROPLEX_TYPE, metroplexMsg);
61
+ }
62
+ else {
63
+ // have collected more than the max number of http requests for this
64
+ // page visit, so increment the over request limit count
65
+ StoredMetrics.getInstance().addHttpDataOverLimit();
66
+ }
67
+ }
68
+ // if this was an error, send immediately so we don't lose it
69
+ if (isHttpCodeFailure(status)) {
70
+ PageVisit.getInstance().addPageVisitEvent({
71
+ event: this.httpEvent,
72
+ occurredAt: new Date(timestampWrapper(Date.now())).toISOString(),
73
+ }, HTTP_EVENT_TYPE);
74
+ return;
75
+ }
76
+ // debounce event
77
+ EventDebouncer.getInstance().addEvent(this.httpEvent, HTTP_EVENT_TYPE);
93
78
  }
94
- // if this was an error, send immediately so we don't lose it
95
- if (isHttpCodeFailure(this.httpEvent.code)) {
96
- noibuLog('saveHTTPEvent is an error, sending immediately');
97
- PageVisit.getInstance().addPageVisitEvent(
98
- {
99
- event: this.httpEvent,
100
- occurredAt: new Date(timestampWrapper(Date.now())).toISOString(),
101
- },
102
- HTTP_EVENT_TYPE,
103
- );
104
- return;
79
+ /**
80
+ * Checks if sending data is allowed based on the HTTP status code and count.
81
+ * status - The HTTP status code to evaluate.
82
+ * count - The count of events to consider.
83
+ * isGqlError - Whether the context is considered as a GQL error.
84
+ * Returns `true` if sending data is allowed, `false` otherwise.
85
+ */
86
+ static isSendAllowed(status, count, isGqlError = false) {
87
+ const isFailure = isHttpCodeFailure(status) || isGqlError;
88
+ const isSuccess = !isFailure;
89
+ if (isSuccess) {
90
+ if (count < MAX_HTTP_DATA_EVENT_COUNT) {
91
+ return true;
92
+ }
93
+ }
94
+ return isFailure && count < MAX_HTTP_DATA_IF_ERROR_EVENT_COUNT;
105
95
  }
106
-
107
- // debounce event
108
- InputMonitor.getInstance().addEvent(this.httpEvent, HTTP_EVENT_TYPE);
109
- }
110
96
  }
111
97
 
112
98
  export { PageVisitEventHTTP, isHttpCodeFailure };
@@ -13,7 +13,24 @@ const INITIAL_STATE = {
13
13
  * @extends {Component<ErrorBoundaryProps, ErrorBoundaryState>}
14
14
  */
15
15
  class ErrorBoundary extends React.Component {
16
- state = INITIAL_STATE;
16
+ constructor() {
17
+ super(...arguments);
18
+ this.state = INITIAL_STATE;
19
+ /**
20
+ * Callback from fallback to reset the error boundary
21
+ * @param {} =>void=(
22
+ */
23
+ this.resetErrorBoundary = () => {
24
+ const { onReset } = this.props;
25
+ const { error, componentStack, eventId } = this.state;
26
+ if (onReset) {
27
+ if (typeof componentStack === 'string') {
28
+ onReset(error, componentStack, eventId);
29
+ }
30
+ }
31
+ this.setState(INITIAL_STATE);
32
+ };
33
+ }
17
34
  /**
18
35
  * Lifecycle hook on mount
19
36
  * @returns void
@@ -57,20 +74,6 @@ class ErrorBoundary extends React.Component {
57
74
  }
58
75
  }
59
76
  }
60
- /**
61
- * Callback from fallback to reset the error boundary
62
- * @param {} =>void=(
63
- */
64
- resetErrorBoundary = () => {
65
- const { onReset } = this.props;
66
- const { error, componentStack, eventId } = this.state;
67
- if (onReset) {
68
- if (typeof componentStack === 'string') {
69
- onReset(error, componentStack, eventId);
70
- }
71
- }
72
- this.setState(INITIAL_STATE);
73
- };
74
77
  /**
75
78
  *
76
79
  * Renders the fallback ui
@@ -32,7 +32,7 @@ function initialize(projectId, config) {
32
32
  }
33
33
  nativeModuleEmitter = new NativeEventEmitter(NativeSessionRecorder);
34
34
  // applying default values
35
- const { userId = null, logLevel = LogLevel.None, allowMeteredNetworkUsage = false, enableWebViewCapture = true, allowedDomains = ['*'], disableOnLowEndDevices = false, maximumDailyNetworkUsageInMB = null, } = config ?? {};
35
+ const { userId = null, logLevel = LogLevel.None, allowMeteredNetworkUsage = false, enableWebViewCapture = true, allowedDomains = ['*'], disableOnLowEndDevices = false, maximumDailyNetworkUsageInMB = null, } = config !== null && config !== void 0 ? config : {};
36
36
  if (!SupportedPlatforms.includes(Platform.OS)) {
37
37
  noibuLog(`Noibu - Session recording supports ${SupportedPlatforms.join(', ')} only for now.`);
38
38
  return;
@@ -43,13 +43,14 @@ function initialize(projectId, config) {
43
43
  }
44
44
  // We use two parameters because the react method parameters do not accept nullable primitive types.
45
45
  const enableDailyNetworkUsageLimit = maximumDailyNetworkUsageInMB != null;
46
- const refinedMaximumDailyNetworkUsageInMB = maximumDailyNetworkUsageInMB ?? 0;
46
+ const refinedMaximumDailyNetworkUsageInMB = maximumDailyNetworkUsageInMB !== null && maximumDailyNetworkUsageInMB !== void 0 ? maximumDailyNetworkUsageInMB : 0;
47
47
  NativeSessionRecorder.initialize(projectId, userId, logLevel, allowMeteredNetworkUsage, enableWebViewCapture, allowedDomains, disableOnLowEndDevices, enableDailyNetworkUsageLimit, refinedMaximumDailyNetworkUsageInMB);
48
48
  }
49
49
  function subscribeToNativeEvent(callback) {
50
50
  if (!nativeModuleEmitter) {
51
51
  throw new Error('You have to initialize Noibu Session Recorder first');
52
52
  }
53
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
53
54
  nativeModuleEmitter.addListener('noibuRecordingEvent', callback);
54
55
  // return () => subscription.remove();
55
56
  return () => { };
@@ -1,30 +1,21 @@
1
+ import { __awaiter } from 'tslib';
1
2
  import { strToU8, zlibSync, strFromU8 } from 'fflate';
2
3
  import { LogLevel, initialize, subscribeToNativeEvent } from './nativeSessionRecorderSubscription.js';
3
4
  import StoredMetrics from '../api/storedMetrics.js';
4
5
  import ClientConfig from '../api/clientConfig.js';
5
6
  import { stringifyJSON } from '../utils/function.js';
6
- import { SEVERITY, POST_METRICS_EVENT_NAME, MAX_TIME_FOR_RECORDER_USER_EVENTS, MAX_RECORDER_EVENT_BUFFER, MAX_TIME_FOR_UNSENT_DATA_MILLIS, VIDEO_FRAG_ATT_NAME, PV_SEQ_ATT_NAME, LENGTH_ATT_NAME, CSS_URLS_ATT_NAME, VIDEO_METROPLEX_TYPE, PAGE_VISIT_VID_FRAG_ATT_NAME } from '../constants.js';
7
+ import { MAX_TIME_FOR_RECORDER_USER_EVENTS, SEVERITY, MAX_RECORDER_EVENT_BUFFER, MAX_TIME_FOR_UNSENT_DATA_MILLIS, VIDEO_FRAG_ATT_NAME, PV_SEQ_ATT_NAME, LENGTH_ATT_NAME, CSS_URLS_ATT_NAME, VIDEO_METROPLEX_TYPE, PAGE_VISIT_VID_FRAG_ATT_NAME, POST_METRICS_EVENT_NAME } from '../constants.js';
7
8
  import MetroplexSocket from '../api/metroplexSocket.js';
8
9
  import { addSafeEventListener } from '../utils/eventlistener.js';
9
10
  import { noibuLog } from '../utils/log.js';
10
11
 
11
12
  /** Singleton class to record user sessions */
12
13
  class SessionRecorder {
13
- static instance;
14
- eventBuffer;
15
- vfCounter;
16
- didSetupRecorder;
17
- isVideoLengthNegativeInvalid;
18
- lastFragPostTimestamp;
19
- pauseTimeout;
20
- lastRecordedTimestamp;
21
- firstRecordedTimestamp;
22
- recordStopper;
23
- freezingEvents = false;
24
14
  /**
25
15
  * Creates an instance of the session recorder
26
16
  */
27
17
  constructor() {
18
+ this.freezingEvents = false;
28
19
  this.eventBuffer = [];
29
20
  this.vfCounter = 0;
30
21
  this.didSetupRecorder = false;
@@ -94,21 +85,23 @@ class SessionRecorder {
94
85
  /**
95
86
  * Starts recording the user session
96
87
  */
97
- async recordUserSession() {
98
- // check if inactive before starting any recording
99
- noibuLog('recordUserSession');
100
- if ((await MetroplexSocket.getInstance().closeIfInactive()) ||
101
- StoredMetrics.getInstance().didCutVideo) {
102
- return;
103
- }
104
- // making sure we are not attempting to call this method
105
- // multiple times.
106
- if (this.didSetupRecorder) {
107
- return;
108
- }
109
- StoredMetrics.getInstance().setDidStartVideo();
110
- this.recordStopper = subscribeToNativeEvent(this.handleRecorderEvent.bind(this));
111
- this.didSetupRecorder = true;
88
+ recordUserSession() {
89
+ return __awaiter(this, void 0, void 0, function* () {
90
+ // check if inactive before starting any recording
91
+ noibuLog('recordUserSession');
92
+ if ((yield MetroplexSocket.getInstance().closeIfInactive()) ||
93
+ StoredMetrics.getInstance().didCutVideo) {
94
+ return;
95
+ }
96
+ // making sure we are not attempting to call this method
97
+ // multiple times.
98
+ if (this.didSetupRecorder) {
99
+ return;
100
+ }
101
+ StoredMetrics.getInstance().setDidStartVideo();
102
+ this.recordStopper = subscribeToNativeEvent(this.handleRecorderEvent.bind(this));
103
+ this.didSetupRecorder = true;
104
+ });
112
105
  }
113
106
  /**
114
107
  * handleNewRRwebEvent will process each upcoming.
@@ -116,71 +109,75 @@ class SessionRecorder {
116
109
  * is updated with the latest events and post the contents
117
110
  * of the buffer if it exceeds max size
118
111
  */
119
- async handleRecorderEvent(recorderEvent) {
120
- const timestamp = Date.now();
121
- // check if inactive before any processing
122
- if ((await MetroplexSocket.getInstance().closeIfInactive()) ||
123
- StoredMetrics.getInstance().didCutVideo) {
124
- this.freeze();
125
- return;
126
- }
127
- // determine if timeout should be extended based on event/source type
128
- // event type 3 is an incremental snapshot
129
- // event data source 0 is a mutation (all other data sources are user events)
130
- if (this.pauseTimeout) {
131
- // received a user event, extend the timeout
132
- clearTimeout(this.pauseTimeout);
133
- this.freezingEvents = false;
134
- }
135
- this.pauseTimeout = setTimeout(() => {
136
- // stop recording page mutations after 2s of inactivity
137
- // otherwise sites with many mutations will hit max video size
138
- // in a short amount of time without any user events
139
- this.freezingEvents = true;
140
- // freezePage stops emitting events until the next user event is received
141
- this.freeze();
142
- }, MAX_TIME_FOR_RECORDER_USER_EVENTS);
143
- // Set the first recorded timestamp if it hasn't been set yet.
144
- // We usually only want this to be set once as the first recorded timestamp
145
- // should not change.
146
- if (!this.firstRecordedTimestamp) {
147
- this.firstRecordedTimestamp = timestamp;
148
- }
149
- // Set the last recorded timestamp if it hasn't been set yet or a newly received rrweb
150
- // event has a more recent timestamp than the last recorded timestamp.
151
- if (!this.lastRecordedTimestamp || timestamp > this.lastRecordedTimestamp) {
152
- this.lastRecordedTimestamp = timestamp;
153
- }
154
- // Checks if we've gone back in time for some reason.
155
- // If we have, adjust our data accordingly to ensure we don't mess up
156
- // the metrics 'exp_vid_len' data.
157
- // If we don't adjust for time, we assume that the expected video length is
158
- // the difference between the first recorded timestamp and the last recorded timestamp.
159
- if (this.firstRecordedTimestamp &&
160
- timestamp < this.firstRecordedTimestamp) {
161
- ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`Detected time rewind. Client has been disabled.`, true, SEVERITY.error, true);
162
- return;
163
- }
164
- const packedEvent = await this.pack(recorderEvent.message);
165
- // Buffer the event for sending to metroplex
166
- this.eventBuffer.push(packedEvent);
167
- // Check if the event was a click or a double click. This is true if the root type is
168
- // incremental snapshot (3) and the data source is mouse interaction data (2).
169
- // Finally, we capture a click (2) or double click (4) event.
170
- // todo if there are clicks, call StoredMetrics.getInstance().addVideoClick(); for each click
171
- const now = Date.now();
172
- const delta = now - this.lastFragPostTimestamp;
173
- if (this.eventBuffer.length >= MAX_RECORDER_EVENT_BUFFER ||
174
- delta > MAX_TIME_FOR_UNSENT_DATA_MILLIS) {
175
- this.handleFragPost();
176
- }
112
+ handleRecorderEvent(recorderEvent) {
113
+ return __awaiter(this, void 0, void 0, function* () {
114
+ const timestamp = Date.now();
115
+ // check if inactive before any processing
116
+ if ((yield MetroplexSocket.getInstance().closeIfInactive()) ||
117
+ StoredMetrics.getInstance().didCutVideo) {
118
+ this.freeze();
119
+ return;
120
+ }
121
+ // determine if timeout should be extended based on event/source type
122
+ // event type 3 is an incremental snapshot
123
+ // event data source 0 is a mutation (all other data sources are user events)
124
+ if (this.pauseTimeout) {
125
+ // received a user event, extend the timeout
126
+ clearTimeout(this.pauseTimeout);
127
+ this.freezingEvents = false;
128
+ }
129
+ this.pauseTimeout = setTimeout(() => {
130
+ // stop recording page mutations after 2s of inactivity
131
+ // otherwise sites with many mutations will hit max video size
132
+ // in a short amount of time without any user events
133
+ this.freezingEvents = true;
134
+ // freezePage stops emitting events until the next user event is received
135
+ this.freeze();
136
+ }, MAX_TIME_FOR_RECORDER_USER_EVENTS);
137
+ // Set the first recorded timestamp if it hasn't been set yet.
138
+ // We usually only want this to be set once as the first recorded timestamp
139
+ // should not change.
140
+ if (!this.firstRecordedTimestamp) {
141
+ this.firstRecordedTimestamp = timestamp;
142
+ }
143
+ // Set the last recorded timestamp if it hasn't been set yet or a newly received rrweb
144
+ // event has a more recent timestamp than the last recorded timestamp.
145
+ if (!this.lastRecordedTimestamp || timestamp > this.lastRecordedTimestamp) {
146
+ this.lastRecordedTimestamp = timestamp;
147
+ }
148
+ // Checks if we've gone back in time for some reason.
149
+ // If we have, adjust our data accordingly to ensure we don't mess up
150
+ // the metrics 'exp_vid_len' data.
151
+ // If we don't adjust for time, we assume that the expected video length is
152
+ // the difference between the first recorded timestamp and the last recorded timestamp.
153
+ if (this.firstRecordedTimestamp &&
154
+ timestamp < this.firstRecordedTimestamp) {
155
+ ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`Detected time rewind. Client has been disabled.`, true, SEVERITY.error, true);
156
+ return;
157
+ }
158
+ const packedEvent = yield this.pack(recorderEvent.message);
159
+ // Buffer the event for sending to metroplex
160
+ this.eventBuffer.push(packedEvent);
161
+ // Check if the event was a click or a double click. This is true if the root type is
162
+ // incremental snapshot (3) and the data source is mouse interaction data (2).
163
+ // Finally, we capture a click (2) or double click (4) event.
164
+ // todo if there are clicks, call StoredMetrics.getInstance().addVideoClick(); for each click
165
+ const now = Date.now();
166
+ const delta = now - this.lastFragPostTimestamp;
167
+ if (this.eventBuffer.length >= MAX_RECORDER_EVENT_BUFFER ||
168
+ delta > MAX_TIME_FOR_UNSENT_DATA_MILLIS) {
169
+ this.handleFragPost();
170
+ }
171
+ });
177
172
  }
178
173
  /**
179
174
  * Compress event
180
175
  */
181
- async pack(recorderEvent) {
182
- // return JSON.stringify(recorderEvent);
183
- return SessionRecorder.compress(recorderEvent);
176
+ pack(recorderEvent) {
177
+ return __awaiter(this, void 0, void 0, function* () {
178
+ // return JSON.stringify(recorderEvent);
179
+ return SessionRecorder.compress(recorderEvent);
180
+ });
184
181
  }
185
182
  static compress(snapshot) {
186
183
  const uncompressedString = stringifyJSON(snapshot);
@@ -200,76 +197,80 @@ class SessionRecorder {
200
197
  * necessary management of the buffer and it's related
201
198
  * variables
202
199
  */
203
- async handleFragPost() {
204
- // check if inactive before any processing
205
- if (await MetroplexSocket.getInstance().closeIfInactive()) {
206
- return;
207
- }
208
- if (!this.didSetupRecorder) {
209
- return;
210
- }
211
- if (this.eventBuffer.length === 0) {
212
- return;
213
- }
214
- try {
215
- let totalVideoTime = 0;
216
- // checking if we have those values set in the first place
217
- if (this.firstRecordedTimestamp &&
218
- this.lastRecordedTimestamp &&
219
- !this.isVideoLengthNegativeInvalid) {
220
- totalVideoTime =
221
- this.lastRecordedTimestamp - this.firstRecordedTimestamp;
200
+ handleFragPost() {
201
+ return __awaiter(this, void 0, void 0, function* () {
202
+ // check if inactive before any processing
203
+ if (yield MetroplexSocket.getInstance().closeIfInactive()) {
204
+ return;
222
205
  }
223
- // In the past we have seen the video LengthMS field to be negative
224
- // and bigger than the long limit of scala. Which is less than the
225
- // safe integer limit of js.
226
- if (!this.isVideoLengthNegativeInvalid &&
227
- (totalVideoTime < 0 || totalVideoTime >= Number.MAX_SAFE_INTEGER)) {
228
- // we log an error to know if this is still happening
229
- ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`video lengthMS is invalid: ${totalVideoTime}, ` +
230
- `start time: ${this.firstRecordedTimestamp}, ` +
231
- `end time: ${this.lastRecordedTimestamp}`, false, SEVERITY.error);
232
- this.isVideoLengthNegativeInvalid = true;
233
- totalVideoTime = 0;
206
+ if (!this.didSetupRecorder) {
207
+ return;
234
208
  }
235
- this.vfCounter += 1;
236
- const videoFragment = MetroplexSocket.getInstance().addEndTimeToPayload({
237
- // single string for the video content. This gets converted to bytes
238
- // when being sent to metroplex which we will then unmarshall into
239
- // a struct to parse it's inner urls
240
- // If stringifying this event buffer takes too long consider using a service worker
241
- [VIDEO_FRAG_ATT_NAME]: stringifyJSON(this.eventBuffer),
242
- // Send the sequence number but don't send the expected length since that is sent as
243
- // part of the last stored metrics data
244
- [PV_SEQ_ATT_NAME]: this.vfCounter,
245
- [LENGTH_ATT_NAME]: totalVideoTime,
246
- [CSS_URLS_ATT_NAME]: [],
247
- }, false);
248
- StoredMetrics.getInstance().addVideoFragData(this.vfCounter, totalVideoTime);
249
- // constructing a client message that metroplex knows how to handle.
250
- await MetroplexSocket.getInstance().sendMessage(VIDEO_METROPLEX_TYPE, {
251
- [PAGE_VISIT_VID_FRAG_ATT_NAME]: videoFragment,
252
- });
253
- this.lastFragPostTimestamp = Date.now();
254
- }
255
- catch (err) {
256
- // letting collect know we are closing the rrweb listener
257
- ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`video frag socket closed with err: ${err.message}`, false, SEVERITY.error);
258
- // if we detect an error in the frag posting, we stop recording
259
- // the video
260
- this.freeze();
261
- }
262
- this.eventBuffer = [];
209
+ if (this.eventBuffer.length === 0) {
210
+ return;
211
+ }
212
+ try {
213
+ let totalVideoTime = 0;
214
+ // checking if we have those values set in the first place
215
+ if (this.firstRecordedTimestamp &&
216
+ this.lastRecordedTimestamp &&
217
+ !this.isVideoLengthNegativeInvalid) {
218
+ totalVideoTime =
219
+ this.lastRecordedTimestamp - this.firstRecordedTimestamp;
220
+ }
221
+ // In the past we have seen the video LengthMS field to be negative
222
+ // and bigger than the long limit of scala. Which is less than the
223
+ // safe integer limit of js.
224
+ if (!this.isVideoLengthNegativeInvalid &&
225
+ (totalVideoTime < 0 || totalVideoTime >= Number.MAX_SAFE_INTEGER)) {
226
+ // we log an error to know if this is still happening
227
+ ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`video lengthMS is invalid: ${totalVideoTime}, ` +
228
+ `start time: ${this.firstRecordedTimestamp}, ` +
229
+ `end time: ${this.lastRecordedTimestamp}`, false, SEVERITY.error);
230
+ this.isVideoLengthNegativeInvalid = true;
231
+ totalVideoTime = 0;
232
+ }
233
+ this.vfCounter += 1;
234
+ const videoFragment = MetroplexSocket.getInstance().addEndTimeToPayload({
235
+ // single string for the video content. This gets converted to bytes
236
+ // when being sent to metroplex which we will then unmarshall into
237
+ // a struct to parse it's inner urls
238
+ // If stringifying this event buffer takes too long consider using a service worker
239
+ [VIDEO_FRAG_ATT_NAME]: stringifyJSON(this.eventBuffer),
240
+ // Send the sequence number but don't send the expected length since that is sent as
241
+ // part of the last stored metrics data
242
+ [PV_SEQ_ATT_NAME]: this.vfCounter,
243
+ [LENGTH_ATT_NAME]: totalVideoTime,
244
+ [CSS_URLS_ATT_NAME]: [],
245
+ }, false);
246
+ StoredMetrics.getInstance().addVideoFragData(this.vfCounter, totalVideoTime);
247
+ // constructing a client message that metroplex knows how to handle.
248
+ yield MetroplexSocket.getInstance().sendMessage(VIDEO_METROPLEX_TYPE, {
249
+ [PAGE_VISIT_VID_FRAG_ATT_NAME]: videoFragment,
250
+ });
251
+ this.lastFragPostTimestamp = Date.now();
252
+ }
253
+ catch (err) {
254
+ // letting collect know we are closing the rrweb listener
255
+ ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(`video frag socket closed with err: ${err.message}`, false, SEVERITY.error);
256
+ // if we detect an error in the frag posting, we stop recording
257
+ // the video
258
+ this.freeze();
259
+ }
260
+ this.eventBuffer = [];
261
+ });
263
262
  }
264
263
  /**
265
264
  * unfreeze forcefully resumes recording events in case it was frozen
266
265
  * waiting for user events
267
266
  */
268
- async unfreeze() {
269
- if (this.freezingEvents) {
270
- this.didSetupRecorder = false;
271
- await this.recordUserSession();
272
- }
267
+ unfreeze() {
268
+ return __awaiter(this, void 0, void 0, function* () {
269
+ if (this.freezingEvents) {
270
+ this.didSetupRecorder = false;
271
+ yield this.recordUserSession();
272
+ }
273
+ });
273
274
  }
274
275
  /** stops recording */
275
276
  freeze() {