@cloudcare/browser-core 2.0.4 → 2.0.6

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/cjs/browser/addEventListener.js +57 -0
  2. package/cjs/browser/fetchObservable.js +4 -5
  3. package/cjs/browser/pageExitObservable.js +4 -2
  4. package/cjs/browser/runOnReadyState.js +21 -0
  5. package/cjs/browser/xhrObservable.js +7 -5
  6. package/cjs/configuration/configuration.js +5 -3
  7. package/cjs/console/consoleObservable.js +3 -1
  8. package/cjs/dataMap.js +1 -0
  9. package/cjs/helper/byteUtils.js +26 -0
  10. package/cjs/helper/contextHistory.js +4 -2
  11. package/cjs/helper/createEventRateLimiter.js +3 -1
  12. package/cjs/helper/errorTools.js +4 -2
  13. package/cjs/helper/instrumentMethod.js +2 -4
  14. package/cjs/helper/serialisation/contextManager.js +84 -0
  15. package/cjs/helper/serialisation/heavyCustomerDataWarning.js +34 -0
  16. package/cjs/helper/serialisation/jsonStringify.js +57 -0
  17. package/cjs/helper/serialisation/rowData.js +50 -0
  18. package/cjs/helper/timer.js +29 -0
  19. package/cjs/helper/tools.js +10 -331
  20. package/cjs/index.js +117 -0
  21. package/cjs/init.js +6 -6
  22. package/cjs/report/reportObservable.js +4 -2
  23. package/cjs/session/sessionCookieStore.js +3 -1
  24. package/cjs/session/sessionManagement.js +10 -5
  25. package/cjs/session/sessionStore.js +10 -4
  26. package/cjs/transport/batch.js +40 -91
  27. package/cjs/transport/flushController.js +121 -0
  28. package/cjs/transport/httpRequest.js +6 -2
  29. package/cjs/transport/index.js +9 -1
  30. package/cjs/transport/sendWithRetryStrategy.js +8 -4
  31. package/cjs/transport/startBatchWithReplica.js +10 -2
  32. package/cjs/user/index.js +18 -0
  33. package/cjs/user/user.js +42 -0
  34. package/esm/browser/addEventListener.js +46 -0
  35. package/esm/browser/fetchObservable.js +4 -3
  36. package/esm/browser/pageExitObservable.js +2 -1
  37. package/esm/browser/runOnReadyState.js +12 -0
  38. package/esm/browser/xhrObservable.js +6 -5
  39. package/esm/configuration/configuration.js +2 -1
  40. package/esm/console/consoleObservable.js +2 -1
  41. package/esm/dataMap.js +1 -0
  42. package/esm/helper/byteUtils.js +16 -0
  43. package/esm/helper/contextHistory.js +1 -0
  44. package/esm/helper/createEventRateLimiter.js +1 -0
  45. package/esm/helper/errorTools.js +2 -1
  46. package/esm/helper/instrumentMethod.js +1 -3
  47. package/esm/helper/serialisation/contextManager.js +70 -0
  48. package/esm/helper/serialisation/heavyCustomerDataWarning.js +20 -0
  49. package/esm/helper/serialisation/jsonStringify.js +46 -0
  50. package/esm/helper/serialisation/rowData.js +33 -0
  51. package/esm/helper/timer.js +14 -0
  52. package/esm/helper/tools.js +1 -292
  53. package/esm/index.js +10 -1
  54. package/esm/init.js +6 -6
  55. package/esm/report/reportObservable.js +3 -2
  56. package/esm/session/sessionCookieStore.js +1 -0
  57. package/esm/session/sessionManagement.js +5 -2
  58. package/esm/session/sessionStore.js +7 -2
  59. package/esm/transport/batch.js +31 -85
  60. package/esm/transport/flushController.js +114 -0
  61. package/esm/transport/httpRequest.js +5 -2
  62. package/esm/transport/index.js +2 -1
  63. package/esm/transport/sendWithRetryStrategy.js +3 -1
  64. package/esm/transport/startBatchWithReplica.js +9 -2
  65. package/esm/user/index.js +1 -0
  66. package/esm/user/user.js +32 -0
  67. package/package.json +22 -22
  68. package/src/browser/addEventListener.js +50 -0
  69. package/src/browser/fetchObservable.js +5 -5
  70. package/src/browser/pageExitObservable.js +4 -5
  71. package/src/browser/runOnReadyState.js +16 -0
  72. package/src/browser/xhrObservable.js +57 -32
  73. package/src/configuration/configuration.js +1 -1
  74. package/src/console/consoleObservable.js +22 -12
  75. package/src/dataMap.js +1 -0
  76. package/src/helper/byteUtils.js +17 -0
  77. package/src/helper/contextHistory.js +1 -1
  78. package/src/helper/createEventRateLimiter.js +7 -9
  79. package/src/helper/errorTools.js +2 -1
  80. package/src/helper/instrumentMethod.js +1 -2
  81. package/src/helper/serialisation/contextManager.js +79 -0
  82. package/src/helper/serialisation/heavyCustomerDataWarning.js +28 -0
  83. package/src/helper/serialisation/jsonStringify.js +47 -0
  84. package/src/helper/serialisation/rowData.js +41 -0
  85. package/src/helper/timer.js +21 -0
  86. package/src/helper/tools.js +2 -301
  87. package/src/index.js +11 -0
  88. package/src/init.js +6 -6
  89. package/src/report/reportObservable.js +40 -25
  90. package/src/session/sessionCookieStore.js +24 -14
  91. package/src/session/sessionManagement.js +34 -17
  92. package/src/session/sessionStore.js +7 -2
  93. package/src/transport/batch.js +39 -91
  94. package/src/transport/flushController.js +115 -0
  95. package/src/transport/httpRequest.js +13 -6
  96. package/src/transport/index.js +1 -0
  97. package/src/transport/sendWithRetryStrategy.js +3 -7
  98. package/src/transport/startBatchWithReplica.js +11 -7
  99. package/src/user/index.js +1 -0
  100. package/src/user/user.js +31 -0
@@ -1,11 +1,12 @@
1
1
  import { toStackTraceString } from '../helper/errorTools'
2
2
  import { mergeObservables, Observable } from '../helper/observable'
3
- import { includes, addEventListener, safeTruncate, filter, each } from '../helper/tools'
3
+ import { includes, safeTruncate, filter, each } from '../helper/tools'
4
+ import { addEventListener } from '../browser/addEventListener'
4
5
  import { DOM_EVENT } from '../helper/enums'
5
6
  export var RawReportType = {
6
7
  intervention: 'intervention',
7
8
  deprecation: 'deprecation',
8
- cspViolation: 'csp_violation',
9
+ cspViolation: 'csp_violation'
9
10
  }
10
11
  export function initReportObservable(apis) {
11
12
  var observables = []
@@ -14,7 +15,9 @@ export function initReportObservable(apis) {
14
15
  observables.push(createCspViolationReportObservable())
15
16
  }
16
17
 
17
- var reportTypes = filter(apis, function(api) { return api !== RawReportType.cspViolation })
18
+ var reportTypes = filter(apis, function (api) {
19
+ return api !== RawReportType.cspViolation
20
+ })
18
21
  if (reportTypes.length) {
19
22
  observables.push(createReportObservable(reportTypes))
20
23
  }
@@ -22,24 +25,24 @@ export function initReportObservable(apis) {
22
25
  }
23
26
 
24
27
  function createReportObservable(reportTypes) {
25
- var observable = new Observable(function() {
28
+ var observable = new Observable(function () {
26
29
  if (!window.ReportingObserver) {
27
30
  return
28
31
  }
29
32
 
30
- var handleReports = function(reports) {
31
- each(reports, function(report) {
33
+ var handleReports = function (reports) {
34
+ each(reports, function (report) {
32
35
  observable.notify(buildRawReportFromReport(report))
33
36
  })
34
37
  }
35
38
 
36
39
  var observer = new window.ReportingObserver(handleReports, {
37
40
  types: reportTypes,
38
- buffered: true,
41
+ buffered: true
39
42
  })
40
43
 
41
44
  observer.observe()
42
- return function() {
45
+ return function () {
43
46
  observer.disconnect()
44
47
  }
45
48
  })
@@ -48,12 +51,16 @@ function createReportObservable(reportTypes) {
48
51
  }
49
52
 
50
53
  function createCspViolationReportObservable() {
51
- var observable = new Observable(function() {
52
- var handleCspViolation = function(event) {
54
+ var observable = new Observable(function () {
55
+ var handleCspViolation = function (event) {
53
56
  observable.notify(buildRawReportFromCspViolation(event))
54
57
  }
55
58
 
56
- var _addEventListener = addEventListener(document, DOM_EVENT.SECURITY_POLICY_VIOLATION, handleCspViolation)
59
+ var _addEventListener = addEventListener(
60
+ document,
61
+ DOM_EVENT.SECURITY_POLICY_VIOLATION,
62
+ handleCspViolation
63
+ )
57
64
 
58
65
  return _addEventListener.stop
59
66
  })
@@ -67,34 +74,42 @@ function buildRawReportFromReport(report) {
67
74
  type: type,
68
75
  subtype: body.id,
69
76
  message: type + ': ' + body.message,
70
- stack: buildStack(body.id, body.message, body.sourceFile, body.lineNumber, body.columnNumber),
77
+ stack: buildStack(
78
+ body.id,
79
+ body.message,
80
+ body.sourceFile,
81
+ body.lineNumber,
82
+ body.columnNumber
83
+ )
71
84
  }
72
85
  }
73
86
 
74
87
  function buildRawReportFromCspViolation(event) {
75
88
  var type = RawReportType.cspViolation
76
- var message = '\''+ event.blockedURI + '\' blocked by \'' + event.effectiveDirective + '\' directive'
89
+ var message =
90
+ "'" +
91
+ event.blockedURI +
92
+ "' blocked by '" +
93
+ event.effectiveDirective +
94
+ "' directive"
77
95
  return {
78
96
  type: RawReportType.cspViolation,
79
97
  subtype: event.effectiveDirective,
80
98
  message: type + ': ' + message,
81
99
  stack: buildStack(
82
100
  event.effectiveDirective,
83
- message + ' of the policy "'+ safeTruncate(event.originalPolicy, 100, '...') + '"',
101
+ message +
102
+ ' of the policy "' +
103
+ safeTruncate(event.originalPolicy, 100, '...') +
104
+ '"',
84
105
  event.sourceFile,
85
106
  event.lineNumber,
86
107
  event.columnNumber
87
- ),
108
+ )
88
109
  }
89
110
  }
90
111
 
91
- function buildStack(
92
- name,
93
- message,
94
- sourceFile,
95
- lineNumber,
96
- columnNumber
97
- ){
112
+ function buildStack(name, message, sourceFile, lineNumber, columnNumber) {
98
113
  return (
99
114
  sourceFile &&
100
115
  toStackTraceString({
@@ -105,9 +120,9 @@ function buildStack(
105
120
  func: '?',
106
121
  url: sourceFile,
107
122
  line: lineNumber,
108
- column: columnNumber,
109
- },
110
- ],
123
+ column: columnNumber
124
+ }
125
+ ]
111
126
  })
112
127
  )
113
128
  }
@@ -1,7 +1,15 @@
1
1
  import { getCookie, setCookie } from '../browser/cookie'
2
- import { isChromium, dateNow, isEmptyObject, UUID, objectEntries,map, each, } from '../helper/tools'
2
+ import {
3
+ isChromium,
4
+ dateNow,
5
+ isEmptyObject,
6
+ UUID,
7
+ objectEntries,
8
+ map,
9
+ each
10
+ } from '../helper/tools'
3
11
  import { SESSION_EXPIRATION_DELAY } from './sessionConstants'
4
-
12
+ import { setTimeout } from '../helper/timer'
5
13
  var SESSION_ENTRY_REGEXP = /^([a-z]+)=([a-z0-9-]+)$/
6
14
  var SESSION_ENTRY_SEPARATOR = '&'
7
15
 
@@ -11,8 +19,7 @@ export var SESSION_COOKIE_NAME = '_dataflux_s'
11
19
  export var LOCK_RETRY_DELAY = 10
12
20
  export var MAX_NUMBER_OF_LOCK_RETRIES = 100
13
21
 
14
-
15
- var bufferedOperations= []
22
+ var bufferedOperations = []
16
23
  var ongoingOperations
17
24
 
18
25
  export function withCookieLockAccess(operations, numberOfRetries) {
@@ -93,12 +100,9 @@ function isCookieLockEnabled() {
93
100
  }
94
101
 
95
102
  function retryLater(operations, currentNumberOfRetries) {
96
- setTimeout(
97
- function() {
98
- withCookieLockAccess(operations, currentNumberOfRetries + 1)
99
- },
100
- LOCK_RETRY_DELAY
101
- )
103
+ setTimeout(function () {
104
+ withCookieLockAccess(operations, currentNumberOfRetries + 1)
105
+ }, LOCK_RETRY_DELAY)
102
106
  }
103
107
 
104
108
  function next() {
@@ -119,11 +123,16 @@ export function persistSession(session, options) {
119
123
  }
120
124
 
121
125
  function setSession(session, options) {
122
- setCookie(SESSION_COOKIE_NAME, toSessionString(session), SESSION_EXPIRATION_DELAY, options)
126
+ setCookie(
127
+ SESSION_COOKIE_NAME,
128
+ toSessionString(session),
129
+ SESSION_EXPIRATION_DELAY,
130
+ options
131
+ )
123
132
  }
124
133
 
125
134
  export function toSessionString(session) {
126
- return map(objectEntries(session), function(item) {
135
+ return map(objectEntries(session), function (item) {
127
136
  return item[0] + '=' + item[1]
128
137
  }).join(SESSION_ENTRY_SEPARATOR)
129
138
  }
@@ -132,7 +141,7 @@ export function retrieveSession() {
132
141
  var sessionString = getCookie(SESSION_COOKIE_NAME)
133
142
  var session = {}
134
143
  if (isValidSessionString(sessionString)) {
135
- each(sessionString.split(SESSION_ENTRY_SEPARATOR), function(entry) {
144
+ each(sessionString.split(SESSION_ENTRY_SEPARATOR), function (entry) {
136
145
  var matches = SESSION_ENTRY_REGEXP.exec(entry)
137
146
  if (matches !== null) {
138
147
  var key = matches[1]
@@ -147,7 +156,8 @@ export function retrieveSession() {
147
156
  function isValidSessionString(sessionString) {
148
157
  return (
149
158
  sessionString !== undefined &&
150
- (sessionString.indexOf(SESSION_ENTRY_SEPARATOR) !== -1 || SESSION_ENTRY_REGEXP.test(sessionString))
159
+ (sessionString.indexOf(SESSION_ENTRY_SEPARATOR) !== -1 ||
160
+ SESSION_ENTRY_REGEXP.test(sessionString))
151
161
  )
152
162
  }
153
163
 
@@ -1,63 +1,68 @@
1
- import { relativeNow, clocksOrigin, ONE_MINUTE, each, addEventListeners, addEventListener } from '../helper/tools'
1
+ import { relativeNow, clocksOrigin, ONE_MINUTE, each } from '../helper/tools'
2
+ import {
3
+ addEventListeners,
4
+ addEventListener
5
+ } from '../browser/addEventListener'
2
6
  import { ContextHistory } from '../helper/contextHistory'
3
7
  import { startSessionStore } from './sessionStore'
4
8
  import { SESSION_TIME_OUT_DELAY } from './sessionConstants'
5
9
  import { DOM_EVENT } from '../helper/enums'
6
-
10
+ import { clearInterval, setInterval } from '../helper/timer'
7
11
  export var VISIBILITY_CHECK_DELAY = ONE_MINUTE
8
12
  var SESSION_CONTEXT_TIMEOUT_DELAY = SESSION_TIME_OUT_DELAY
9
13
  var stopCallbacks = []
10
14
 
11
- export var startSessionManager = function(
15
+ export var startSessionManager = function (
12
16
  options,
13
17
  productKey,
14
18
  computeSessionState
15
19
  ) {
16
20
  var sessionStore = startSessionStore(options, productKey, computeSessionState)
17
- stopCallbacks.push(function() {
21
+ stopCallbacks.push(function () {
18
22
  return sessionStore.stop()
19
23
  })
20
24
 
21
25
  var sessionContextHistory = new ContextHistory(SESSION_CONTEXT_TIMEOUT_DELAY)
22
- stopCallbacks.push(function() {
26
+ stopCallbacks.push(function () {
23
27
  return sessionContextHistory.stop()
24
28
  })
25
29
 
26
- sessionStore.renewObservable.subscribe(function(){
30
+ sessionStore.renewObservable.subscribe(function () {
27
31
  sessionContextHistory.add(buildSessionContext(), relativeNow())
28
32
  })
29
- sessionStore.expireObservable.subscribe(function() {
33
+ sessionStore.expireObservable.subscribe(function () {
30
34
  sessionContextHistory.closeActive(relativeNow())
31
35
  })
32
36
 
33
37
  sessionStore.expandOrRenewSession()
34
38
  sessionContextHistory.add(buildSessionContext(), clocksOrigin().relative)
35
39
 
36
- trackActivity(function() {
40
+ trackActivity(function () {
37
41
  return sessionStore.expandOrRenewSession()
38
42
  })
39
- trackVisibility(function() {
43
+ trackVisibility(function () {
40
44
  return sessionStore.expandSession()
41
45
  })
42
46
 
43
47
  function buildSessionContext() {
44
48
  return {
45
49
  id: sessionStore.getSession().id,
46
- trackingType: sessionStore.getSession()[productKey],
50
+ trackingType: sessionStore.getSession()[productKey]
47
51
  }
48
52
  }
49
53
 
50
54
  return {
51
- findActiveSession: function(startTime) {
55
+ findActiveSession: function (startTime) {
52
56
  return sessionContextHistory.find(startTime)
53
57
  },
54
58
  renewObservable: sessionStore.renewObservable,
55
59
  expireObservable: sessionStore.expireObservable,
60
+ expire: sessionStore.expire
56
61
  }
57
62
  }
58
63
 
59
64
  export function stopSessionManager() {
60
- each(stopCallbacks, function(e) {
65
+ each(stopCallbacks, function (e) {
61
66
  return e()
62
67
  })
63
68
  stopCallbacks = []
@@ -66,7 +71,12 @@ export function stopSessionManager() {
66
71
  function trackActivity(expandOrRenewSession) {
67
72
  var _addEventListeners = addEventListeners(
68
73
  window,
69
- [DOM_EVENT.CLICK,DOM_EVENT.TOUCH_START, DOM_EVENT.KEY_DOWN, DOM_EVENT.SCROLL],
74
+ [
75
+ DOM_EVENT.CLICK,
76
+ DOM_EVENT.TOUCH_START,
77
+ DOM_EVENT.KEY_DOWN,
78
+ DOM_EVENT.SCROLL
79
+ ],
70
80
  expandOrRenewSession,
71
81
  { capture: true, passive: true }
72
82
  )
@@ -74,16 +84,23 @@ function trackActivity(expandOrRenewSession) {
74
84
  }
75
85
 
76
86
  function trackVisibility(expandSession) {
77
- var expandSessionWhenVisible = function() {
87
+ var expandSessionWhenVisible = function () {
78
88
  if (document.visibilityState === 'visible') {
79
89
  expandSession()
80
90
  }
81
91
  }
82
- var _addEventListener= addEventListener(document, DOM_EVENT.VISIBILITY_CHANGE, expandSessionWhenVisible)
92
+ var _addEventListener = addEventListener(
93
+ document,
94
+ DOM_EVENT.VISIBILITY_CHANGE,
95
+ expandSessionWhenVisible
96
+ )
83
97
  stopCallbacks.push(_addEventListener.stop)
84
98
 
85
- var visibilityCheckInterval = setInterval(expandSessionWhenVisible, VISIBILITY_CHECK_DELAY)
86
- stopCallbacks.push(function() {
99
+ var visibilityCheckInterval = setInterval(
100
+ expandSessionWhenVisible,
101
+ VISIBILITY_CHECK_DELAY
102
+ )
103
+ stopCallbacks.push(function () {
87
104
  clearInterval(visibilityCheckInterval)
88
105
  })
89
106
  }
@@ -3,6 +3,7 @@ import { Observable } from '../helper/observable'
3
3
  import { dateNow, UUID, throttle } from '../helper/tools'
4
4
  import { SESSION_TIME_OUT_DELAY } from './sessionConstants'
5
5
  import { retrieveSession, withCookieLockAccess } from './sessionCookieStore'
6
+ import { clearInterval, setInterval } from '../helper/timer'
6
7
 
7
8
  /**
8
9
  * Different session concepts:
@@ -67,7 +68,7 @@ export function startSessionStore(options, productKey, computeSessionState) {
67
68
  }
68
69
  if (hasSessionInCache()) {
69
70
  if (isSessionInCacheOutdated(cookieSession)) {
70
- expireSession()
71
+ expireSessionInCache()
71
72
  } else {
72
73
  sessionCache = cookieSession
73
74
  }
@@ -98,7 +99,7 @@ export function startSessionStore(options, productKey, computeSessionState) {
98
99
  )
99
100
  }
100
101
 
101
- function expireSession() {
102
+ function expireSessionInCache() {
102
103
  sessionCache = {}
103
104
  expireObservable.notify()
104
105
  }
@@ -135,6 +136,10 @@ export function startSessionStore(options, productKey, computeSessionState) {
135
136
  },
136
137
  renewObservable: renewObservable,
137
138
  expireObservable: expireObservable,
139
+ expire: function () {
140
+ deleteSessionCookie(options)
141
+ synchronizeSession({})
142
+ },
138
143
  stop: function () {
139
144
  clearInterval(watchSessionTimeoutId)
140
145
  }
@@ -1,10 +1,7 @@
1
1
  import { display } from '../helper/display'
2
2
  import {
3
- addEventListener,
4
- noop,
5
3
  values,
6
4
  findByPath,
7
- escapeRowData,
8
5
  each,
9
6
  isNumber,
10
7
  isArray,
@@ -13,12 +10,17 @@ import {
13
10
  toServerDuration,
14
11
  isBoolean,
15
12
  isEmptyObject,
16
- isObject,
17
- escapeJsonValue,
18
- escapeRowField
13
+ isObject
19
14
  } from '../helper/tools'
15
+ import {
16
+ escapeJsonValue,
17
+ escapeRowField,
18
+ escapeRowData
19
+ } from '../helper/serialisation/rowData'
20
20
  import { commonTags, dataMap, commonFields } from '../dataMap'
21
- import { DOM_EVENT, RumEventType } from '../helper/enums'
21
+ import { RumEventType } from '../helper/enums'
22
+ import { computeBytesCount } from '../helper/byteUtils'
23
+ import { isPageExitReason } from '../browser/pageExitObservable'
22
24
  // https://en.wikipedia.org/wiki/UTF-8
23
25
  // eslint-disable-next-line no-control-regex
24
26
  var HAS_MULTI_BYTES_CHARACTERS = /[^\u0000-\u007F]/
@@ -133,28 +135,16 @@ export var processedMessageByDataMap = function (message) {
133
135
  rowData: hasFileds ? rowData : undefined
134
136
  }
135
137
  }
136
- var batch = function (
137
- request,
138
- batchMessagesLimit,
139
- batchBytesLimit,
140
- messageBytesLimit,
141
- flushTimeout,
142
- pageExitObservable
143
- ) {
138
+ var batch = function (request, flushController, messageBytesLimit) {
144
139
  this.pushOnlyBuffer = []
145
140
  this.upsertBuffer = {}
146
- this.bufferBytesCount = 0
147
- this.bufferMessagesCount = 0
148
141
  this.request = request
149
- this.batchMessagesLimit = batchMessagesLimit
150
- this.batchBytesLimit = batchBytesLimit
142
+ this.flushController = flushController
151
143
  this.messageBytesLimit = messageBytesLimit
152
- this.flushTimeout = flushTimeout
153
144
  var _this = this
154
- pageExitObservable.subscribe(function () {
155
- _this.flush(_this.request.sendOnExit)
145
+ this.flushController.flushObservable.subscribe(function (event) {
146
+ _this.flush(event)
156
147
  })
157
- this.flushPeriodically()
158
148
  }
159
149
  batch.prototype.add = function (message) {
160
150
  this.addOrUpdate(message)
@@ -162,37 +152,24 @@ batch.prototype.add = function (message) {
162
152
  batch.prototype.upsert = function (message, key) {
163
153
  this.addOrUpdate(message, key)
164
154
  }
165
- batch.prototype.flush = function (sendFn) {
166
- if (typeof sendFn !== 'function') {
167
- sendFn = this.request.send
168
- }
169
- if (this.bufferMessagesCount !== 0) {
170
- var messages = this.pushOnlyBuffer.concat(values(this.upsertBuffer))
171
- var bytesCount = this.bufferBytesCount
172
- this.pushOnlyBuffer = []
173
- this.upsertBuffer = {}
174
- this.bufferBytesCount = 0
175
- this.bufferMessagesCount = 0
176
- if (messages.length > 0) {
177
- sendFn({ data: messages.join('\n'), bytesCount: bytesCount })
155
+ batch.prototype.flush = function (event) {
156
+ var messages = this.pushOnlyBuffer.concat(values(this.upsertBuffer))
157
+ this.pushOnlyBuffer = []
158
+ this.upsertBuffer = {}
159
+ if (messages.length > 0) {
160
+ var payload = {
161
+ data: messages.join('\n'),
162
+ bytesCount: event.bytesCount,
163
+ flushReason: event.reason
164
+ }
165
+ if (isPageExitReason(event.reason)) {
166
+ this.request.sendOnExit(payload)
167
+ } else {
168
+ this.request.send(payload)
178
169
  }
179
170
  }
180
171
  }
181
- batch.prototype.flushOnExit = function () {
182
- this.flush(this.request.sendOnExit)
183
- }
184
- batch.prototype.computeBytesCount = function (candidate) {
185
- // Accurate bytes count computations can degrade performances when there is a lot of events to process
186
- if (!HAS_MULTI_BYTES_CHARACTERS.test(candidate)) {
187
- return candidate.length
188
- }
189
-
190
- if (window.TextEncoder !== undefined) {
191
- return new TextEncoder().encode(candidate).length
192
- }
193
172
 
194
- return new Blob([candidate]).size
195
- }
196
173
  batch.prototype.addOrUpdate = function (message, key) {
197
174
  var _process = this.process(message)
198
175
  var processedMessage = _process.processedMessage
@@ -208,18 +185,11 @@ batch.prototype.addOrUpdate = function (message, key) {
208
185
  if (this.hasMessageFor(key)) {
209
186
  this.remove(key)
210
187
  }
211
- if (this.willReachedBytesLimitWith(messageBytesCount)) {
212
- this.flush()
213
- }
214
-
215
188
  this.push(processedMessage, messageBytesCount, key)
216
- if (this.isFull()) {
217
- this.flush()
218
- }
219
189
  }
220
190
  batch.prototype.process = function (message) {
221
191
  var processedMessage = processedMessageByDataMap(message).rowStr
222
- var messageBytesCount = this.computeBytesCount(processedMessage)
192
+ var messageBytesCount = computeBytesCount(processedMessage)
223
193
  return {
224
194
  processedMessage: processedMessage,
225
195
  messageBytesCount: messageBytesCount
@@ -227,52 +197,30 @@ batch.prototype.process = function (message) {
227
197
  }
228
198
 
229
199
  batch.prototype.push = function (processedMessage, messageBytesCount, key) {
230
- if (this.bufferMessagesCount > 0) {
231
- // \n separator at serialization
232
- this.bufferBytesCount += 1
233
- }
200
+ var separatorBytesCount = this.flushController.getMessagesCount() > 0 ? 1 : 0
201
+ this.flushController.notifyBeforeAddMessage(
202
+ messageBytesCount + separatorBytesCount
203
+ )
234
204
  if (key !== undefined) {
235
205
  this.upsertBuffer[key] = processedMessage
236
206
  } else {
237
207
  this.pushOnlyBuffer.push(processedMessage)
238
208
  }
239
- this.bufferBytesCount += messageBytesCount
240
- this.bufferMessagesCount += 1
209
+ this.flushController.notifyAfterAddMessage()
241
210
  }
242
211
 
243
212
  batch.prototype.remove = function (key) {
244
213
  var removedMessage = this.upsertBuffer[key]
245
214
  delete this.upsertBuffer[key]
246
- var messageBytesCount = this.computeBytesCount(removedMessage)
247
- this.bufferBytesCount -= messageBytesCount
248
- this.bufferMessagesCount -= 1
249
- if (this.bufferMessagesCount > 0) {
250
- this.bufferBytesCount -= 1
251
- }
215
+ var messageBytesCount = computeBytesCount(removedMessage)
216
+ // If there are other messages, a '\n' will be added at serialization
217
+ var separatorBytesCount = this.flushController.getMessagesCount() > 1 ? 1 : 0
218
+ this.flushController.notifyAfterRemoveMessage(
219
+ messageBytesCount + separatorBytesCount
220
+ )
252
221
  }
253
222
 
254
223
  batch.prototype.hasMessageFor = function (key) {
255
224
  return key !== undefined && this.upsertBuffer[key] !== undefined
256
225
  }
257
-
258
- batch.prototype.willReachedBytesLimitWith = function (messageBytesCount) {
259
- // byte of the separator at the end of the message
260
- return this.bufferBytesCount + messageBytesCount + 1 >= this.batchBytesLimit
261
- }
262
-
263
- batch.prototype.isFull = function () {
264
- return (
265
- this.bufferMessagesCount === this.batchMessagesLimit ||
266
- this.bufferBytesCount >= this.batchBytesLimit
267
- )
268
- }
269
-
270
- batch.prototype.flushPeriodically = function () {
271
- var _this = this
272
- setTimeout(function () {
273
- _this.flush()
274
- _this.flushPeriodically()
275
- }, this.flushTimeout)
276
- }
277
-
278
226
  export var Batch = batch
@@ -0,0 +1,115 @@
1
+ import { Observable } from '../helper/observable'
2
+ import { clearTimeout, setTimeout } from '../helper/timer'
3
+
4
+ // type FlushReason = PageExitReason | 'duration_limit' | 'bytes_limit' | 'messages_limit' | 'session_expire'
5
+
6
+ /**
7
+ * Returns a "flush controller", responsible of notifying when flushing a pool of pending data needs
8
+ * to happen. The implementation is designed to support both synchronous and asynchronous usages,
9
+ * but relies on invariants described in each method documentation to keep a coherent state.
10
+ */
11
+ export function createFlushController({
12
+ messagesLimit,
13
+ bytesLimit,
14
+ durationLimit,
15
+ pageExitObservable,
16
+ sessionExpireObservable
17
+ }) {
18
+ var flushObservable = new Observable()
19
+
20
+ pageExitObservable.subscribe(function (event) {
21
+ return flush(event.reason)
22
+ })
23
+ sessionExpireObservable.subscribe(function () {
24
+ return flush('session_expire')
25
+ })
26
+
27
+ var currentBytesCount = 0
28
+ var currentMessagesCount = 0
29
+
30
+ function flush(flushReason) {
31
+ if (currentMessagesCount === 0) {
32
+ return
33
+ }
34
+
35
+ var messagesCount = currentMessagesCount
36
+ var bytesCount = currentBytesCount
37
+
38
+ currentMessagesCount = 0
39
+ currentBytesCount = 0
40
+ cancelDurationLimitTimeout()
41
+
42
+ flushObservable.notify({
43
+ reason: flushReason,
44
+ messagesCount: messagesCount,
45
+ bytesCount: bytesCount
46
+ })
47
+ }
48
+
49
+ var durationLimitTimeoutId
50
+ function scheduleDurationLimitTimeout() {
51
+ if (durationLimitTimeoutId === undefined) {
52
+ durationLimitTimeoutId = setTimeout(function () {
53
+ flush('duration_limit')
54
+ }, durationLimit)
55
+ }
56
+ }
57
+
58
+ function cancelDurationLimitTimeout() {
59
+ clearTimeout(durationLimitTimeoutId)
60
+ durationLimitTimeoutId = undefined
61
+ }
62
+
63
+ return {
64
+ flushObservable: flushObservable,
65
+ getMessagesCount: function () {
66
+ return currentMessagesCount
67
+ },
68
+
69
+ /**
70
+ * Notifies that a message will be added to a pool of pending messages waiting to be flushed.
71
+ *
72
+ * This function needs to be called synchronously, right before adding the message, so no flush
73
+ * event can happen after `notifyBeforeAddMessage` and before adding the message.
74
+ */
75
+ notifyBeforeAddMessage: function (messageBytesCount) {
76
+ if (currentBytesCount + messageBytesCount >= bytesLimit) {
77
+ flush('bytes_limit')
78
+ }
79
+ // Consider the message to be added now rather than in `notifyAfterAddMessage`, because if no
80
+ // message was added yet and `notifyAfterAddMessage` is called asynchronously, we still want
81
+ // to notify when a flush is needed (for example on page exit).
82
+ currentMessagesCount += 1
83
+ currentBytesCount += messageBytesCount
84
+ scheduleDurationLimitTimeout()
85
+ },
86
+
87
+ /**
88
+ * Notifies that a message *was* added to a pool of pending messages waiting to be flushed.
89
+ *
90
+ * This function can be called asynchronously after the message was added, but in this case it
91
+ * should not be called if a flush event occurred in between.
92
+ */
93
+ notifyAfterAddMessage: function () {
94
+ if (currentMessagesCount >= messagesLimit) {
95
+ flush('messages_limit')
96
+ } else if (currentBytesCount >= bytesLimit) {
97
+ flush('bytes_limit')
98
+ }
99
+ },
100
+
101
+ /**
102
+ * Notifies that a message was removed from a pool of pending messages waiting to be flushed.
103
+ *
104
+ * This function needs to be called synchronously, right after removing the message, so no flush
105
+ * event can happen after removing the message and before `notifyAfterRemoveMessage`.
106
+ */
107
+ notifyAfterRemoveMessage: function (messageBytesCount) {
108
+ currentBytesCount -= messageBytesCount
109
+ currentMessagesCount -= 1
110
+ if (currentMessagesCount === 0) {
111
+ cancelDurationLimitTimeout()
112
+ }
113
+ }
114
+ }
115
+ }