@cloudcare/browser-core 1.1.2 → 1.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 (134) hide show
  1. package/cjs/{cookie.js → browser/cookie.js} +6 -37
  2. package/cjs/browser/fetchObservable.js +91 -0
  3. package/cjs/browser/xhrObservable.js +118 -0
  4. package/cjs/configuration/configuration.js +70 -0
  5. package/cjs/configuration/transportConfiguration.js +64 -0
  6. package/cjs/console/consoleObservable.js +84 -0
  7. package/cjs/dataMap.js +26 -21
  8. package/cjs/error/trackRuntimeError.js +27 -0
  9. package/cjs/helper/catchUserErrors.js +20 -0
  10. package/cjs/helper/contextHistory.js +134 -0
  11. package/cjs/helper/createEventRateLimiter.js +47 -0
  12. package/cjs/helper/deviceInfo.js +8 -25
  13. package/cjs/helper/display.js +31 -0
  14. package/cjs/helper/enums.js +28 -2
  15. package/cjs/helper/errorTools.js +63 -2
  16. package/cjs/helper/instrumentMethod.js +93 -0
  17. package/cjs/helper/lifeCycle.js +5 -3
  18. package/cjs/helper/observable.js +40 -2
  19. package/cjs/helper/tools.js +217 -11
  20. package/cjs/index.js +153 -23
  21. package/cjs/init.js +17 -27
  22. package/cjs/report/reportObservable.js +112 -0
  23. package/cjs/session/sessionConstants.js +13 -0
  24. package/cjs/session/sessionCookieStore.js +188 -0
  25. package/cjs/session/sessionManagement.js +96 -0
  26. package/cjs/session/sessionStore.js +150 -0
  27. package/cjs/synthetics/syntheticsWorkerValues.js +32 -0
  28. package/cjs/synthetics/usr.js +32 -0
  29. package/cjs/tracekit/computeStackTrace.js +73 -563
  30. package/cjs/tracekit/index.js +3 -15
  31. package/cjs/tracekit/tracekit.js +113 -0
  32. package/cjs/transport/batch.js +332 -0
  33. package/cjs/transport/httpRequest.js +112 -0
  34. package/cjs/transport/index.js +29 -0
  35. package/cjs/transport/sendWithRetryStrategy.js +177 -0
  36. package/cjs/transport/startBatchWithReplica.js +24 -0
  37. package/esm/{cookie.js → browser/cookie.js} +4 -35
  38. package/esm/browser/fetchObservable.js +77 -0
  39. package/esm/browser/xhrObservable.js +106 -0
  40. package/esm/configuration/configuration.js +55 -0
  41. package/esm/configuration/transportConfiguration.js +54 -0
  42. package/esm/console/consoleObservable.js +70 -0
  43. package/esm/dataMap.js +26 -21
  44. package/esm/error/trackRuntimeError.js +16 -0
  45. package/esm/helper/catchUserErrors.js +12 -0
  46. package/esm/helper/contextHistory.js +106 -0
  47. package/esm/helper/createEventRateLimiter.js +38 -0
  48. package/esm/helper/deviceInfo.js +6 -25
  49. package/esm/helper/display.js +21 -0
  50. package/esm/helper/enums.js +25 -1
  51. package/esm/helper/errorTools.js +58 -3
  52. package/esm/helper/instrumentMethod.js +81 -0
  53. package/esm/helper/lifeCycle.js +5 -3
  54. package/esm/helper/observable.js +39 -3
  55. package/esm/helper/tools.js +173 -3
  56. package/esm/index.js +17 -7
  57. package/esm/init.js +17 -22
  58. package/esm/report/reportObservable.js +98 -0
  59. package/esm/session/sessionConstants.js +3 -0
  60. package/esm/session/sessionCookieStore.js +168 -0
  61. package/esm/session/sessionManagement.js +78 -0
  62. package/esm/session/sessionStore.js +139 -0
  63. package/esm/synthetics/syntheticsWorkerValues.js +15 -0
  64. package/esm/synthetics/usr.js +18 -0
  65. package/esm/tracekit/computeStackTrace.js +73 -557
  66. package/esm/tracekit/index.js +1 -1
  67. package/esm/tracekit/tracekit.js +103 -0
  68. package/esm/transport/batch.js +316 -0
  69. package/esm/transport/httpRequest.js +104 -0
  70. package/esm/transport/index.js +3 -0
  71. package/esm/transport/sendWithRetryStrategy.js +165 -0
  72. package/esm/transport/startBatchWithReplica.js +15 -0
  73. package/package.json +2 -2
  74. package/src/.DS_Store +0 -0
  75. package/src/{cookie.js → browser/cookie.js} +6 -34
  76. package/src/browser/fetchObservable.js +86 -0
  77. package/src/browser/xhrObservable.js +102 -0
  78. package/src/configuration/configuration.js +61 -0
  79. package/src/configuration/transportConfiguration.js +42 -0
  80. package/src/console/consoleObservable.js +65 -0
  81. package/src/dataMap.js +26 -21
  82. package/src/error/trackRuntimeError.js +19 -0
  83. package/src/helper/catchUserErrors.js +12 -0
  84. package/src/helper/contextHistory.js +85 -0
  85. package/src/helper/createEventRateLimiter.js +37 -0
  86. package/src/helper/deviceInfo.js +5 -51
  87. package/src/helper/display.js +21 -0
  88. package/src/helper/enums.js +38 -14
  89. package/src/helper/errorTools.js +76 -12
  90. package/src/helper/instrumentMethod.js +97 -0
  91. package/src/helper/lifeCycle.js +5 -3
  92. package/src/helper/observable.js +33 -2
  93. package/src/helper/tools.js +159 -5
  94. package/src/index.js +18 -7
  95. package/src/init.js +17 -18
  96. package/src/report/reportObservable.js +113 -0
  97. package/src/session/sessionConstants.js +3 -0
  98. package/src/session/sessionCookieStore.js +159 -0
  99. package/src/session/sessionManagement.js +89 -0
  100. package/src/session/sessionStore.js +139 -0
  101. package/src/synthetics/syntheticsWorkerValues.js +22 -0
  102. package/src/synthetics/usr.js +18 -0
  103. package/src/tracekit/computeStackTrace.js +75 -551
  104. package/src/tracekit/index.js +1 -1
  105. package/src/tracekit/tracekit.js +100 -0
  106. package/src/transport/batch.js +289 -0
  107. package/src/transport/httpRequest.js +109 -0
  108. package/src/transport/index.js +3 -0
  109. package/src/transport/sendWithRetryStrategy.js +186 -0
  110. package/src/transport/startBatchWithReplica.js +26 -0
  111. package/cjs/configuration.js +0 -184
  112. package/cjs/errorCollection.js +0 -167
  113. package/cjs/fetchProxy.js +0 -162
  114. package/cjs/helper/errorFilter.js +0 -47
  115. package/cjs/sessionManagement.js +0 -221
  116. package/cjs/tracekit/report.js +0 -289
  117. package/cjs/transport.js +0 -373
  118. package/cjs/xhrProxy.js +0 -122
  119. package/esm/configuration.js +0 -167
  120. package/esm/errorCollection.js +0 -141
  121. package/esm/fetchProxy.js +0 -116
  122. package/esm/helper/errorFilter.js +0 -38
  123. package/esm/sessionManagement.js +0 -195
  124. package/esm/tracekit/report.js +0 -273
  125. package/esm/transport.js +0 -357
  126. package/esm/xhrProxy.js +0 -110
  127. package/src/configuration.js +0 -156
  128. package/src/errorCollection.js +0 -170
  129. package/src/fetchProxy.js +0 -109
  130. package/src/helper/errorFilter.js +0 -38
  131. package/src/sessionManagement.js +0 -224
  132. package/src/tracekit/report.js +0 -284
  133. package/src/transport.js +0 -380
  134. package/src/xhrProxy.js +0 -106
@@ -0,0 +1,100 @@
1
+ import { instrumentMethodAndCallOriginal } from '../helper/instrumentMethod'
2
+ import { computeStackTrace } from './computeStackTrace'
3
+
4
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types
5
+ var ERROR_TYPES_RE =
6
+ /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/
7
+
8
+ /**
9
+ * Cross-browser collection of unhandled errors
10
+ *
11
+ * Supports:
12
+ * - Firefox: full stack trace with line numbers, plus column number
13
+ * on top frame; column number is not guaranteed
14
+ * - Opera: full stack trace with line and column numbers
15
+ * - Chrome: full stack trace with line and column numbers
16
+ * - Safari: line and column number for the top frame only; some frames
17
+ * may be missing, and column number is not guaranteed
18
+ * - IE: line and column number for the top frame only; some frames
19
+ * may be missing, and column number is not guaranteed
20
+ *
21
+ * In theory, TraceKit should work on all of the following versions:
22
+ * - IE5.5+ (only 8.0 tested)
23
+ * - Firefox 0.9+ (only 3.5+ tested)
24
+ * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require
25
+ * Exceptions Have Stacktrace to be enabled in opera:config)
26
+ * - Safari 3+ (only 4+ tested)
27
+ * - Chrome 1+ (only 5+ tested)
28
+ * - Konqueror 3.5+ (untested)
29
+ *
30
+ * Tries to catch all unhandled errors and report them to the
31
+ * callback.
32
+ *
33
+ * Callbacks receive a StackTrace object as described in the
34
+ * computeStackTrace docs.
35
+ *
36
+ * @memberof TraceKit
37
+ * @namespace
38
+ */
39
+
40
+ export function startUnhandledErrorCollection(callback) {
41
+ var _instrumentOnError = instrumentOnError(callback)
42
+ var _instrumentUnhandledRejection = instrumentUnhandledRejection(callback)
43
+ return {
44
+ stop: function() {
45
+ _instrumentOnError.stop()
46
+ _instrumentUnhandledRejection.stop()
47
+ },
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Install a global onerror handler
53
+ */
54
+ function instrumentOnError(callback) {
55
+ return instrumentMethodAndCallOriginal(window, 'onerror', {
56
+ before: function(message, url, lineNo, columnNo, errorObj) {
57
+ var stack
58
+ if (errorObj) {
59
+ stack = computeStackTrace(errorObj)
60
+ callback(stack, errorObj)
61
+ } else {
62
+ var location = {
63
+ url: url,
64
+ column: columnNo,
65
+ line: lineNo,
66
+ }
67
+ var name
68
+ var msg = message
69
+ if ({}.toString.call(message) === '[object String]') {
70
+ var groups = ERROR_TYPES_RE.exec(msg)
71
+ if (groups) {
72
+ name = groups[1]
73
+ msg = groups[2]
74
+ }
75
+ }
76
+
77
+ stack = {
78
+ name: name,
79
+ message: typeof msg === 'string' ? msg : undefined,
80
+ stack: [location],
81
+ }
82
+
83
+ callback(stack, message)
84
+ }
85
+ },
86
+ })
87
+ }
88
+
89
+ /**
90
+ * Install a global onunhandledrejection handler
91
+ */
92
+ function instrumentUnhandledRejection(callback) {
93
+ return instrumentMethodAndCallOriginal(window, 'onunhandledrejection', {
94
+ before: function(e) {
95
+ var reason = e.reason || 'Empty reason'
96
+ var stack = computeStackTrace(reason)
97
+ callback(stack, reason)
98
+ },
99
+ })
100
+ }
@@ -0,0 +1,289 @@
1
+ import { display } from '../helper/display'
2
+ import {
3
+ addEventListener,
4
+ noop,
5
+ values,
6
+ findByPath,
7
+ escapeRowData,
8
+ each,
9
+ isNumber,
10
+ isArray,
11
+ extend,
12
+ isString,
13
+ toServerDuration,
14
+ isBoolean,
15
+ isEmptyObject,
16
+ isObject,
17
+ escapeJsonValue,
18
+ escapeRowField
19
+ } from '../helper/tools'
20
+ import { commonTags, dataMap } from '../dataMap'
21
+ import { DOM_EVENT, RumEventType } from '../helper/enums'
22
+ // https://en.wikipedia.org/wiki/UTF-8
23
+ // eslint-disable-next-line no-control-regex
24
+ var HAS_MULTI_BYTES_CHARACTERS = /[^\u0000-\u007F]/
25
+ var CUSTOM_KEYS = 'custom_keys'
26
+ export var processedMessageByDataMap = function (message) {
27
+ if (!message || !message.type) return {
28
+ rowStr: '',
29
+ rowData: undefined
30
+ }
31
+
32
+ var rowData = { tags: {}, fields: {} }
33
+ var hasFileds = false
34
+ var rowStr = ''
35
+ each(dataMap, function (value, key) {
36
+ if (value.type === message.type) {
37
+ rowStr += key + ','
38
+ rowData.measurement = key
39
+ var tagsStr = []
40
+ var tags = extend({}, commonTags, value.tags)
41
+ var filterFileds = ['date', 'type', CUSTOM_KEYS] // 已经在datamap中定义过的fields和tags
42
+ each(tags, function (value_path, _key) {
43
+ var _value = findByPath(message, value_path)
44
+ filterFileds.push(_key)
45
+ if (_value || isNumber(_value)) {
46
+ rowData.tags[_key] = escapeJsonValue(_value)
47
+ tagsStr.push(escapeRowData(_key) + '=' + escapeRowData(_value))
48
+ }
49
+ })
50
+
51
+ var fieldsStr = []
52
+ each(value.fields, function (_value, _key) {
53
+ if (isArray(_value) && _value.length === 2) {
54
+ var type = _value[0],
55
+ value_path = _value[1]
56
+ var _valueData = findByPath(message, value_path)
57
+ filterFileds.push(_key)
58
+ if (_valueData || isNumber(_valueData)) {
59
+ rowData.fields[_key] = _valueData // 这里不需要转译
60
+ // _valueData =
61
+ // type === 'string'
62
+ // ? '"' +
63
+ // _valueData.replace(/[\\]*"/g, '"').replace(/"/g, '\\"') +
64
+ // '"'
65
+ // : escapeRowData(_valueData)
66
+ fieldsStr.push(escapeRowData(_key) + '=' + escapeRowField(_valueData))
67
+ }
68
+ } else if (isString(_value)) {
69
+ var _valueData = findByPath(message, _value)
70
+ filterFileds.push(_key)
71
+ if (_valueData || isNumber(_valueData)) {
72
+ rowData.fields[_key] = _valueData // 这里不需要转译
73
+ fieldsStr.push(escapeRowData(_key) + '=' + escapeRowField(_valueData))
74
+ }
75
+ }
76
+ })
77
+ if (message.tags && isObject(message.tags) && !isEmptyObject(message.tags)) {
78
+ // 自定义tag, 存储成field
79
+ var _tagKeys = []
80
+ each(message.tags, function (_value, _key) {
81
+ // 如果和之前tag重名,则舍弃
82
+ if (filterFileds.indexOf(_key) > -1) return
83
+ filterFileds.push(_key)
84
+ if (_value || isNumber(_value)) {
85
+ _tagKeys.push(_key)
86
+ rowData.fields[_key] = _value // 这里不需要转译
87
+ fieldsStr.push(escapeRowData(_key) + '=' + escapeRowField(_value))
88
+ }
89
+ })
90
+ if (_tagKeys.length) {
91
+ rowData.fields[CUSTOM_KEYS] = escapeRowField(_tagKeys)
92
+ fieldsStr.push(escapeRowData(CUSTOM_KEYS) + '=' + escapeRowField(_tagKeys))
93
+ }
94
+ }
95
+ if (message.type === RumEventType.LOGGER) {
96
+ // 这里处理日志类型数据自定义字段
97
+ each(message, function (value, key) {
98
+ if (
99
+ filterFileds.indexOf(key) === -1 &&
100
+ (isNumber(value) || isString(value) || isBoolean(value))
101
+ ) {
102
+ rowData.fields[key] = value // 这里不需要转译
103
+ fieldsStr.push(escapeRowData(key) + '=' + escapeRowField(value))
104
+ }
105
+ })
106
+ }
107
+ if (tagsStr.length) {
108
+ rowStr += tagsStr.join(',')
109
+ }
110
+ if (fieldsStr.length) {
111
+ rowStr += ' '
112
+ rowStr += fieldsStr.join(',')
113
+ hasFileds = true
114
+ }
115
+ rowStr = rowStr + ' ' + message.date
116
+ rowData.time = toServerDuration(message.date) // 这里不需要转译
117
+ }
118
+ })
119
+ return {
120
+ rowStr: hasFileds ? rowStr : '',
121
+ rowData: hasFileds ? rowData : undefined
122
+ }
123
+ }
124
+ var batch = function (request, batchMessagesLimit, batchBytesLimit, messageBytesLimit, flushTimeout,beforeUnloadCallback) {
125
+ this.pushOnlyBuffer = []
126
+ this.upsertBuffer = {}
127
+ this.bufferBytesCount = 0
128
+ this.bufferMessagesCount = 0
129
+ this.request = request
130
+ this.batchMessagesLimit = batchMessagesLimit
131
+ this.batchBytesLimit = batchBytesLimit
132
+ this.messageBytesLimit = messageBytesLimit
133
+ this.flushTimeout = flushTimeout
134
+ if (typeof beforeUnloadCallback === 'function') {
135
+ this.beforeUnloadCallback = beforeUnloadCallback
136
+ } else {
137
+ this.beforeUnloadCallback = noop
138
+ }
139
+ this.setupFlushOnExit()
140
+ this.flushPeriodically()
141
+ }
142
+ batch.prototype.add = function(message) {
143
+ this.addOrUpdate(message)
144
+ }
145
+ batch.prototype.upsert = function(message, key) {
146
+ this.addOrUpdate(message, key)
147
+ }
148
+ batch.prototype.flush = function(sendFn) {
149
+ if (typeof sendFn !== 'function') {
150
+ sendFn = this.request.send
151
+ }
152
+ if (this.bufferMessagesCount !== 0) {
153
+ var messages = this.pushOnlyBuffer.concat(values(this.upsertBuffer))
154
+ var bytesCount = this.bufferBytesCount
155
+ this.pushOnlyBuffer = []
156
+ this.upsertBuffer = {}
157
+ this.bufferBytesCount = 0
158
+ this.bufferMessagesCount = 0
159
+ if (messages.length > 0) {
160
+ sendFn({ data: messages.join('\n'), bytesCount: bytesCount })
161
+ }
162
+ }
163
+ }
164
+ batch.prototype.flushOnExit = function() {
165
+ this.flush(this.request.sendOnExit)
166
+ }
167
+ batch.prototype.computeBytesCount = function(candidate) {
168
+ // Accurate bytes count computations can degrade performances when there is a lot of events to process
169
+ if (!HAS_MULTI_BYTES_CHARACTERS.test(candidate)) {
170
+ return candidate.length
171
+ }
172
+
173
+ if (window.TextEncoder !== undefined) {
174
+ return new TextEncoder().encode(candidate).length
175
+ }
176
+
177
+ return new Blob([candidate]).size
178
+ }
179
+ batch.prototype.addOrUpdate = function(message, key) {
180
+ var _process = this.process(message)
181
+ var processedMessage = _process.processedMessage
182
+ var messageBytesCount = _process.messageBytesCount
183
+ if (messageBytesCount >= this.messageBytesLimit) {
184
+ display.warn(
185
+ 'Discarded a message whose size was bigger than the maximum allowed size ' + this.messageBytesLimit + 'KB.'
186
+ )
187
+ return
188
+ }
189
+ if (this.hasMessageFor(key)) {
190
+ this.remove(key)
191
+ }
192
+ if (this.willReachedBytesLimitWith(messageBytesCount)) {
193
+ this.flush()
194
+ }
195
+
196
+ this.push(processedMessage, messageBytesCount, key)
197
+ if (this.isFull()) {
198
+ this.flush()
199
+ }
200
+ }
201
+ batch.prototype.process = function(message) {
202
+ var processedMessage = processedMessageByDataMap(message).rowStr
203
+ var messageBytesCount = this.computeBytesCount(processedMessage)
204
+ return { processedMessage: processedMessage, messageBytesCount:messageBytesCount }
205
+ }
206
+
207
+ batch.prototype.push = function(processedMessage, messageBytesCount, key) {
208
+ if (this.bufferMessagesCount > 0) {
209
+ // \n separator at serialization
210
+ this.bufferBytesCount += 1
211
+ }
212
+ if (key !== undefined) {
213
+ this.upsertBuffer[key] = processedMessage
214
+ } else {
215
+ this.pushOnlyBuffer.push(processedMessage)
216
+ }
217
+ this.bufferBytesCount += messageBytesCount
218
+ this.bufferMessagesCount += 1
219
+ }
220
+
221
+ batch.prototype.remove = function(key) {
222
+ var removedMessage = this.upsertBuffer[key]
223
+ delete this.upsertBuffer[key]
224
+ var messageBytesCount = this.computeBytesCount(removedMessage)
225
+ this.bufferBytesCount -= messageBytesCount
226
+ this.bufferMessagesCount -= 1
227
+ if (this.bufferMessagesCount > 0) {
228
+ this.bufferBytesCount -= 1
229
+ }
230
+ }
231
+
232
+ batch.prototype.hasMessageFor = function(key) {
233
+ return key !== undefined && this.upsertBuffer[key] !== undefined
234
+ }
235
+
236
+ batch.prototype.willReachedBytesLimitWith = function(messageBytesCount) {
237
+ // byte of the separator at the end of the message
238
+ return this.bufferBytesCount + messageBytesCount + 1 >= this.batchBytesLimit
239
+ }
240
+
241
+ batch.prototype.isFull = function() {
242
+ return this.bufferMessagesCount === this.batchMessagesLimit || this.bufferBytesCount >= this.batchBytesLimit
243
+ }
244
+
245
+ batch.prototype.flushPeriodically = function() {
246
+ var _this = this
247
+ setTimeout(
248
+ function(){
249
+ _this.flush()
250
+ _this.flushPeriodically()
251
+ },
252
+ this.flushTimeout
253
+ )
254
+ }
255
+
256
+ batch.prototype.setupFlushOnExit = function() {
257
+ /**
258
+ * With sendBeacon, requests are guaranteed to be successfully sent during document unload
259
+ */
260
+ // @ts-ignore this function is not always defined
261
+ var _this = this
262
+ if (navigator.sendBeacon) {
263
+ /**
264
+ * beforeunload is called before visibilitychange
265
+ * register first to be sure to be called before flush on beforeunload
266
+ * caveat: unload can still be canceled by another listener
267
+ */
268
+ addEventListener(window, DOM_EVENT.BEFORE_UNLOAD, this.beforeUnloadCallback)
269
+
270
+ /**
271
+ * Only event that guarantee to fire on mobile devices when the page transitions to background state
272
+ * (e.g. when user switches to a different application, goes to homescreen, etc), or is being unloaded.
273
+ */
274
+ addEventListener(document, DOM_EVENT.VISIBILITY_CHANGE, function() {
275
+ if (document.visibilityState === 'hidden') {
276
+ _this.flushOnExit()
277
+ }
278
+ })
279
+ /**
280
+ * Safari does not support yet to send a request during:
281
+ * - a visibility change during doc unload (cf: https://bugs.webkit.org/show_bug.cgi?id=194897)
282
+ * - a page hide transition (cf: https://bugs.webkit.org/show_bug.cgi?id=188329)
283
+ */
284
+ addEventListener(window, DOM_EVENT.BEFORE_UNLOAD, function() {
285
+ return _this.flushOnExit()
286
+ })
287
+ }
288
+ }
289
+ export var Batch = batch
@@ -0,0 +1,109 @@
1
+ import { newRetryState, sendWithRetryStrategy } from './sendWithRetryStrategy'
2
+
3
+ /**
4
+ * Use POST request without content type to:
5
+ * - avoid CORS preflight requests
6
+ * - allow usage of sendBeacon
7
+ *
8
+ * multiple elements are sent separated by \n in order
9
+ * to be parsed correctly without content type header
10
+ */
11
+ function addBatchPrecision(url) {
12
+ if (!url) return url
13
+ return url + (url.indexOf('?') === -1 ? '?' : '&') + 'precision=ms'
14
+ }
15
+ export function createHttpRequest(
16
+ endpointUrl,
17
+ bytesLimit,
18
+ reportError
19
+ ) {
20
+ var retryState = newRetryState()
21
+ var sendStrategyForRetry = function(payload, onResponse) {
22
+ return fetchKeepAliveStrategy(endpointUrl, bytesLimit, payload, onResponse)
23
+ }
24
+
25
+ return {
26
+ send: function(payload) {
27
+ sendWithRetryStrategy(payload, retryState, sendStrategyForRetry, reportError)
28
+ },
29
+ /**
30
+ * Since fetch keepalive behaves like regular fetch on Firefox,
31
+ * keep using sendBeaconStrategy on exit
32
+ */
33
+ sendOnExit: function(payload) {
34
+ sendBeaconStrategy(endpointUrl, bytesLimit, payload)
35
+ },
36
+ }
37
+ }
38
+
39
+ function sendBeaconStrategy(endpointUrl, bytesLimit, payload) {
40
+ var data = payload.data
41
+ var bytesCount = payload.bytesCount
42
+ var url = addBatchPrecision(endpointUrl)
43
+ var canUseBeacon = !!navigator.sendBeacon && bytesCount < bytesLimit
44
+ if (canUseBeacon) {
45
+ try {
46
+ var isQueued = navigator.sendBeacon(url, data)
47
+
48
+ if (isQueued) {
49
+ return
50
+ }
51
+ } catch (e) {
52
+ // reportBeaconError(e)
53
+ }
54
+ }
55
+ sendXHR(url, data)
56
+ }
57
+
58
+
59
+
60
+ export function fetchKeepAliveStrategy(
61
+ endpointUrl,
62
+ bytesLimit,
63
+ payload,
64
+ onResponse
65
+ ) {
66
+ var data = payload.data
67
+ var bytesCount = payload.bytesCount
68
+ var url = addBatchPrecision(endpointUrl)
69
+ var canUseKeepAlive = isKeepAliveSupported() && bytesCount < bytesLimit
70
+ if (canUseKeepAlive) {
71
+ fetch(url, { method: 'POST', body: data, keepalive: true })
72
+ .then(
73
+ function(response) {
74
+ if (typeof onResponse === 'function') {
75
+ onResponse({ status: response.status })
76
+ }
77
+ },
78
+ function() {
79
+ // failed to queue the request
80
+ sendXHR(url, data, onResponse)
81
+ }
82
+ )
83
+ } else {
84
+ sendXHR(url, data, onResponse)
85
+ }
86
+ }
87
+
88
+ function isKeepAliveSupported() {
89
+ // Request can throw, cf https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#errors
90
+ try {
91
+ return window.Request && 'keepalive' in new Request('http://a')
92
+ } catch {
93
+ return false
94
+ }
95
+ }
96
+
97
+ function sendXHR(url, data, onResponse) {
98
+ const request = new XMLHttpRequest()
99
+ request.open('POST', url, true)
100
+ request.send(data)
101
+ request.addEventListener(
102
+ 'loadend',
103
+ function(){
104
+ if (typeof onResponse === 'function') {
105
+ onResponse({ status: request.status })
106
+ }
107
+ }
108
+ )
109
+ }
@@ -0,0 +1,3 @@
1
+ export { createHttpRequest } from './httpRequest'
2
+ export { Batch } from './batch'
3
+ export { startBatchWithReplica } from './startBatchWithReplica'
@@ -0,0 +1,186 @@
1
+ import { clocksNow, ONE_KIBI_BYTE, ONE_MEBI_BYTE, ONE_SECOND } from '../helper/tools'
2
+ import { ErrorSource } from '../helper/errorTools'
3
+
4
+ export var MAX_ONGOING_BYTES_COUNT = 80 * ONE_KIBI_BYTE
5
+ export var MAX_ONGOING_REQUESTS = 32
6
+ export var MAX_QUEUE_BYTES_COUNT = 3 * ONE_MEBI_BYTE
7
+ export var MAX_BACKOFF_TIME = 256 * ONE_SECOND
8
+ export var INITIAL_BACKOFF_TIME = ONE_SECOND
9
+
10
+ var TransportStatus = {
11
+ UP: 'UP',
12
+ FAILURE_DETECTED: 'FAILURE_DETECTED',
13
+ DOWN: 'DOWN',
14
+ }
15
+
16
+ var RetryReason = {
17
+ AFTER_SUCCESS: 'AFTER_SUCCESS',
18
+ AFTER_RESUME: 'AFTER_RESUME',
19
+ }
20
+
21
+ export function sendWithRetryStrategy(
22
+ payload,
23
+ state,
24
+ sendStrategy,
25
+ reportError
26
+ ) {
27
+ if (
28
+ state.transportStatus === TransportStatus.UP &&
29
+ state.queuedPayloads.size() === 0 &&
30
+ state.bandwidthMonitor.canHandle(payload)
31
+ ) {
32
+ send(payload, state, sendStrategy, {
33
+ onSuccess: function() { return retryQueuedPayloads(RetryReason.AFTER_SUCCESS, state, sendStrategy, reportError) },
34
+ onFailure: function() {
35
+ state.queuedPayloads.enqueue(payload)
36
+ scheduleRetry(state, sendStrategy, reportError)
37
+ },
38
+ })
39
+ } else {
40
+ state.queuedPayloads.enqueue(payload)
41
+ }
42
+ }
43
+
44
+ function scheduleRetry(
45
+ state,
46
+ sendStrategy,
47
+ reportError
48
+ ) {
49
+ if (state.transportStatus !== TransportStatus.DOWN) {
50
+ return
51
+ }
52
+ setTimeout(
53
+ function(){
54
+ var payload = state.queuedPayloads.first()
55
+ send(payload, state, sendStrategy, {
56
+ onSuccess: function() {
57
+ state.queuedPayloads.dequeue()
58
+ // if (state.lastFailureStatus !== 0) {
59
+ // addTelemetryDebug('resuming after transport down', {
60
+ // failureStatus: state.lastFailureStatus,
61
+ // })
62
+ // }
63
+ state.currentBackoffTime = INITIAL_BACKOFF_TIME
64
+ retryQueuedPayloads(RetryReason.AFTER_RESUME, state, sendStrategy, reportError)
65
+ },
66
+ onFailure: function() {
67
+ state.currentBackoffTime = Math.min(MAX_BACKOFF_TIME, state.currentBackoffTime * 2)
68
+ scheduleRetry(state, sendStrategy, reportError)
69
+ },
70
+ })
71
+ },
72
+ state.currentBackoffTime
73
+ )
74
+ }
75
+
76
+ function send(
77
+ payload,
78
+ state,
79
+ sendStrategy,
80
+ responseData
81
+ ) {
82
+ var onSuccess = responseData.onSuccess
83
+ var onFailure = responseData.onFailure
84
+ state.bandwidthMonitor.add(payload)
85
+ sendStrategy(payload, function(response) {
86
+ state.bandwidthMonitor.remove(payload)
87
+ if (wasRequestSuccessful(response)) {
88
+ state.transportStatus = TransportStatus.UP
89
+ onSuccess()
90
+ } else {
91
+ // do not consider transport down if another ongoing request could succeed
92
+ state.transportStatus =
93
+ state.bandwidthMonitor.ongoingRequestCount > 0 ? TransportStatus.FAILURE_DETECTED : TransportStatus.DOWN
94
+ state.lastFailureStatus = response.status
95
+ onFailure()
96
+ }
97
+ })
98
+ }
99
+
100
+ function retryQueuedPayloads(
101
+ reason,
102
+ state,
103
+ sendStrategy,
104
+ reportError
105
+ ) {
106
+ if (reason === RetryReason.AFTER_SUCCESS && state.queuedPayloads.isFull() && !state.queueFullReported) {
107
+ reportError({
108
+ message: 'Reached max events size queued for upload: '+ MAX_QUEUE_BYTES_COUNT / ONE_MEBI_BYTE + 'MiB',
109
+ source: ErrorSource.AGENT,
110
+ startClocks: clocksNow(),
111
+ })
112
+ state.queueFullReported = true
113
+ }
114
+ var previousQueue = state.queuedPayloads
115
+ state.queuedPayloads = newPayloadQueue()
116
+ while (previousQueue.size() > 0) {
117
+ sendWithRetryStrategy(previousQueue.dequeue(), state, sendStrategy, reportError)
118
+ }
119
+ }
120
+
121
+ function wasRequestSuccessful(response) {
122
+ return response.status !== 0 && response.status < 500
123
+ }
124
+
125
+ export function newRetryState() {
126
+ return {
127
+ transportStatus: TransportStatus.UP,
128
+ lastFailureStatus: 0,
129
+ currentBackoffTime: INITIAL_BACKOFF_TIME,
130
+ bandwidthMonitor: newBandwidthMonitor(),
131
+ queuedPayloads: newPayloadQueue(),
132
+ queueFullReported: false,
133
+ }
134
+ }
135
+
136
+ function newPayloadQueue() {
137
+ var queue = []
138
+ return {
139
+ bytesCount: 0,
140
+ enqueue: function(payload) {
141
+ if (this.isFull()) {
142
+ return
143
+ }
144
+ queue.push(payload)
145
+ this.bytesCount += payload.bytesCount
146
+ },
147
+ first() {
148
+ return queue[0]
149
+ },
150
+ dequeue() {
151
+ var payload = queue.shift()
152
+ if (payload) {
153
+ this.bytesCount -= payload.bytesCount
154
+ }
155
+ return payload
156
+ },
157
+ size() {
158
+ return queue.length
159
+ },
160
+ isFull() {
161
+ return this.bytesCount >= MAX_QUEUE_BYTES_COUNT
162
+ },
163
+ }
164
+ }
165
+
166
+ function newBandwidthMonitor() {
167
+ return {
168
+ ongoingRequestCount: 0,
169
+ ongoingByteCount: 0,
170
+ canHandle: function(payload) {
171
+ return (
172
+ this.ongoingRequestCount === 0 ||
173
+ (this.ongoingByteCount + payload.bytesCount <= MAX_ONGOING_BYTES_COUNT &&
174
+ this.ongoingRequestCount < MAX_ONGOING_REQUESTS)
175
+ )
176
+ },
177
+ add: function(payload) {
178
+ this.ongoingRequestCount += 1
179
+ this.ongoingByteCount += payload.bytesCount
180
+ },
181
+ remove:function(payload) {
182
+ this.ongoingRequestCount -= 1
183
+ this.ongoingByteCount -= payload.bytesCount
184
+ },
185
+ }
186
+ }